// =====================================================================
// ANTOOZA Frontend Jenkinsfile (Next.js 15 standalone + WinSW + IIS reverse proxy)
//   - Branches:        main / dev  -> deploy
//                      others      -> build/test only
//   - Agent:           CTL-Window-Server (Node v24)
//   - Deploy path:     F:\IIS\ANTOOZA\Front
//   - Process:         WinSW service 'antooza-frontend' (node server.js on :3000)
//   - IIS:             ANTOOZA-Front site reverse-proxies HTTPS:443 -> 127.0.0.1:3000
// =====================================================================

pipeline {
  agent { label 'CTL-Window-Server' }

  options {
    timestamps()
    disableConcurrentBuilds()
    buildDiscarder(logRotator(numToKeepStr: '20'))
  }

  environment {
    DEPLOY_ROOT = 'F:\\IIS\\ANTOOZA\\Front'
    RELEASES    = 'F:\\IIS\\ANTOOZA\\_releases'
    SVC_NAME    = 'antooza-frontend'
  }

  stages {

    stage('Install') {
      steps {
        bat 'chcp 65001 >NUL && node -v && npm -v'
        script {
          // package-lock.json 변경 + node_modules 존재 여부 조합으로 npm ci 스킵.
          // 첫 빌드 또는 lock 변경 시에만 full install (캐시 활용 위해 --prefer-offline).
          def lockChanged = bat(returnStatus: true,
              script: '@git diff --quiet HEAD~1 HEAD -- package-lock.json 2>nul') != 0
          def hasModules  = fileExists('node_modules/.package-lock.json')
          if (!hasModules || lockChanged) {
            echo "Installing (lockChanged=${lockChanged}, hasModules=${hasModules})"
            bat 'chcp 65001 >NUL && npm ci --no-audit --no-fund --prefer-offline'
          } else {
            echo 'package-lock.json unchanged + node_modules present — skip npm ci'
          }
        }
      }
    }

    stage('Inject Env (Secret File)') {
      when { anyOf { branch 'main'; branch 'dev' } }
      steps {
        script {
          // Backend 패턴과 동일: 브랜치별로 Jenkins Secret File 주입
          //   dev  → DEV-ANTOOZA-FRONTEND-ENV  (dev-api.antooza.com)
          //   main → PROD-ANTOOZA-FRONTEND-ENV (api.antooza.com)
          // .env.production 은 .gitignore 됨 (Backend 의 appsettings.Production.json 처럼 빌드 시점에만 존재).
          def credId = (env.BRANCH_NAME == 'main') ? 'PROD-ANTOOZA-FRONTEND-ENV' : 'DEV-ANTOOZA-FRONTEND-ENV'
          echo "[env] Using Jenkins Secret File: ${credId}"
          withCredentials([file(credentialsId: credId, variable: 'CFG')]) {
            bat 'chcp 65001 >NUL && copy /Y "%CFG%" .env.production >NUL && for %%I in (.env.production) do @echo [env] .env.production injected (%%~zI bytes)'
          }
        }
      }
    }

    stage('Build') {
      steps {
        bat 'chcp 65001 >NUL && npm run build'
      }
    }

    stage('Deploy') {
      when { anyOf { branch 'main'; branch 'dev' } }
      steps {
        script { deployFrontend() }
      }
    }
  }

  post {
    success { echo "Frontend build & deploy completed (branch=${env.BRANCH_NAME ?: 'unknown'})" }
    failure { echo 'Frontend build/deploy failed - check stage logs' }
  }
}

def deployFrontend() {
  powershell """
    \$ErrorActionPreference = 'Stop'

    \$ts     = Get-Date -Format 'yyyyMMdd-HHmmss'
    \$dest   = '${env.DEPLOY_ROOT}'
    \$backup = Join-Path '${env.RELEASES}' ('${env.SVC_NAME}-' + \$ts)

    if (-not (Test-Path '${env.RELEASES}')) { New-Item -ItemType Directory -Path '${env.RELEASES}' -Force | Out-Null }

    Write-Host '[${env.SVC_NAME}] Stopping service...'
    Stop-Service ${env.SVC_NAME} -Force -ErrorAction SilentlyContinue
    Start-Sleep -Seconds 2

    # Preserve infrastructure files (WinSW + web.config + logs)
    \$keep = @('${env.SVC_NAME}.exe', '${env.SVC_NAME}.xml', 'web.config',
              '${env.SVC_NAME}.err.log', '${env.SVC_NAME}.out.log', '${env.SVC_NAME}.wrapper.log')

    if ((Test-Path \$dest) -and ((Get-ChildItem \$dest -Force | Measure-Object).Count -gt 0)) {
      Write-Host ('[${env.SVC_NAME}] Backing up current to ' + \$backup)
      New-Item -ItemType Directory -Path \$backup -Force | Out-Null
      Get-ChildItem \$dest -Force | Where-Object { \$_.Name -notin \$keep } |
        Move-Item -Destination \$backup -Force
    } elseif (-not (Test-Path \$dest)) {
      New-Item -ItemType Directory -Path \$dest -Force | Out-Null
    }

    # Next.js standalone layout:
    #   .next/standalone/  -> server.js + minimal node_modules + .next folder (server bundles)
    #   .next/static/      -> static assets (must be copied to <dest>/.next/static)
    #   public/            -> public assets (must be copied to <dest>/public)
    Write-Host '[${env.SVC_NAME}] Copying standalone server...'
    Copy-Item '.next\\standalone\\*' \$dest -Recurse -Force

    Write-Host '[${env.SVC_NAME}] Copying static assets...'
    New-Item -ItemType Directory -Path (Join-Path \$dest '.next\\static') -Force | Out-Null
    Copy-Item '.next\\static\\*' (Join-Path \$dest '.next\\static') -Recurse -Force

    Write-Host '[${env.SVC_NAME}] Copying public assets...'
    New-Item -ItemType Directory -Path (Join-Path \$dest 'public') -Force | Out-Null
    if (Test-Path 'public') {
      Copy-Item 'public\\*' (Join-Path \$dest 'public') -Recurse -Force
    }

    # Server-side env (.env.production) — Next.js standalone runtime reads from cwd.
    # NEXT_PUBLIC_* 은 빌드 시 inline 되지만, server-side (API_URL, SIGNALR_CHAT_URL 등) 는 런타임 로드.
    if (Test-Path '.env.production') {
      Write-Host '[${env.SVC_NAME}] Copying .env.production...'
      Copy-Item '.env.production' (Join-Path \$dest '.env.production') -Force
    } else {
      Write-Warning '[${env.SVC_NAME}] .env.production missing in workspace - server-side env will fall back to OS/WinSW env'
    }

    # Server-side env (.env.production) — Next.js standalone runtime reads from cwd
    # NEXT_PUBLIC_* 은 빌드 시 inline 되지만, server-side (API_URL, SIGNALR_CHAT_URL 등) 는 런타임 로드
    if (Test-Path '.env.production') {
      Write-Host '[${env.SVC_NAME}] Copying .env.production...'
      Copy-Item '.env.production' (Join-Path \$dest '.env.production') -Force
    } else {
      Write-Warning '[${env.SVC_NAME}] .env.production missing in workspace - server-side env will fall back to OS/WinSW env'
    }

    # First-deploy auto-install WinSW
    if (-not (Get-Service ${env.SVC_NAME} -ErrorAction SilentlyContinue)) {
      Write-Host '[${env.SVC_NAME}] Service not registered - installing'
      \$installer = Join-Path \$dest '${env.SVC_NAME}.exe'
      if (-not (Test-Path \$installer)) {
        throw ('[${env.SVC_NAME}] WinSW binary missing at ' + \$installer + ' — place ${env.SVC_NAME}.exe (renamed WinSW) + ${env.SVC_NAME}.xml in ' + \$dest)
      }
      & \$installer install
      if (\$LASTEXITCODE -ne 0) { throw ('[${env.SVC_NAME}] install failed (exit ' + \$LASTEXITCODE + ')') }
      Start-Sleep -Seconds 2
    }

    Write-Host '[${env.SVC_NAME}] Starting service...'
    Start-Service ${env.SVC_NAME}

    # Health check: HTTP 200/3xx on http://127.0.0.1:3000/
    \$ok = \$false
    1..15 | ForEach-Object {
      Start-Sleep -Seconds 3
      try {
        \$r = Invoke-WebRequest -Uri 'http://127.0.0.1:3000/' -UseBasicParsing -TimeoutSec 5 -MaximumRedirection 0
        if (\$r.StatusCode -ge 200 -and \$r.StatusCode -lt 400) {
          Write-Host ('[${env.SVC_NAME}] Healthy (HTTP ' + \$r.StatusCode + ') on attempt ' + \$_)
          \$ok = \$true; return
        }
      } catch {
        \$resp = \$_.Exception.Response
        if (\$resp) {
          \$code = [int]\$resp.StatusCode
          if (\$code -ge 200 -and \$code -lt 400) {
            Write-Host ('[${env.SVC_NAME}] Healthy (HTTP ' + \$code + ') on attempt ' + \$_)
            \$ok = \$true; return
          }
        }
        Write-Host ('[${env.SVC_NAME}] attempt ' + \$_ + ' failed: ' + \$_.Exception.Message)
      }
    }

    if (-not \$ok) {
      Write-Warning '[${env.SVC_NAME}] Verification failed - rolling back'
      Stop-Service ${env.SVC_NAME} -Force -ErrorAction SilentlyContinue
      if (Test-Path \$backup) {
        Get-ChildItem \$dest -Force | Where-Object { \$_.Name -notin \$keep } |
          Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
        Copy-Item (Join-Path \$backup '*') \$dest -Recurse -Force
        Start-Service ${env.SVC_NAME}
      }
      throw '${env.SVC_NAME} deployment failed; rolled back.'
    }

    # Retain only 5 recent backups
    Get-ChildItem '${env.RELEASES}' -Directory -ErrorAction SilentlyContinue |
      Where-Object { \$_.Name -like '${env.SVC_NAME}-*' } |
      Sort-Object LastWriteTime -Descending |
      Select-Object -Skip 5 |
      Remove-Item -Recurse -Force
  """
}
