pipeline {
  agent any

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

  // 设计原则：
  // - 无 parameters 块，所以"Build Now"直接跑、不弹窗
  // - 环境(APP_ENV) 与 分支(BRANCH) 都从 Jenkins 自动注入的 GIT_BRANCH 推断
  //   GIT_BRANCH 来自每个 Job 的 SCM 配置（Branch Specifier *​/dev、*​/sit、*​/uat 之一）
  // - 因此每台机器上 Job 唯一要配的就是 Branch Specifier，三台机器的 Jenkinsfile 内容完全一致

  stages {

    stage('Resolve Environment') {
      // 从 Jenkins 注入的 GIT_BRANCH 推断 APP_ENV
      // GIT_BRANCH 形如 "origin/uat" / "uat" / "refs/heads/uat"，统一去前缀
      steps {
        script {
          def raw = env.GIT_BRANCH ?: env.BRANCH_NAME ?: ''
          def branch = raw.replaceFirst('^origin/', '').replaceFirst('^refs/heads/', '').trim()

          if (!(branch in ['dev', 'sit', 'uat'])) {
            error """
              ============ 无法识别当前部署环境 ============
              Jenkins 注入的 GIT_BRANCH = '${env.GIT_BRANCH}'
              解析后的分支名             = '${branch}'
              期望分支必须是 dev / sit / uat 之一。

              请检查 Job 配置中的 Branch Specifier 是否正确：
                DEV 服务器  → */dev
                SIT 服务器  → */sit
                UAT 服务器  → */uat
              =============================================
            """.stripIndent()
          }

          // 把推断结果写回 env，后续 stage 用 env.APP_ENV 而不是 params.APP_ENV
          env.APP_ENV = branch
          env.BRANCH  = branch

          // 派生命名规则（容器名/网络/日志/镜像 TAG 全部以 APP_ENV 区分）
          env.IMAGE_TAG               = "${env.APP_ENV}-${env.BUILD_NUMBER}"
          env.IMAGE_STORE             = "alien_store:${env.IMAGE_TAG}"
          env.IMAGE_GATEWAY           = "alien_gateway:${env.IMAGE_TAG}"
          env.IMAGE_CONTRACT          = "alien_contract:${env.IMAGE_TAG}"
          env.CONTAINER_NAME_STORE    = "alien_store_py-${env.APP_ENV}"
          env.CONTAINER_NAME_GATEWAY  = "alien_gateway_py-${env.APP_ENV}"
          env.CONTAINER_NAME_CONTRACT = "alien_contract_py-${env.APP_ENV}"
          env.DOCKER_NET              = "alien_net_${env.APP_ENV}"
          env.LOG_ROOT                = "/docker/python-${env.APP_ENV}/logs"
        }
      }
    }

    stage('Show Build Info') {
      steps {
        echo "============================================================"
        echo " 部署环境  : ${env.APP_ENV}    （来自 GIT_BRANCH=${env.GIT_BRANCH}）"
        echo " 部署分支  : ${env.BRANCH}"
        echo " 镜像 TAG  : ${env.IMAGE_TAG}"
        echo " 容器网络  : ${env.DOCKER_NET}"
        echo " 日志根目录: ${env.LOG_ROOT}"
        echo "============================================================"
      }
    }

    stage('Load Env Port Mapping') {
      // 从 .env.${APP_ENV} 解析 GATEWAY_PORT，作为 docker -p 宿主端口映射用
      steps {
        script {
          def envFile = ".env.${env.APP_ENV}"
          if (!fileExists(envFile)) {
            error "缺少环境配置文件: ${envFile}（仓库里应该有这份；请检查 .gitignore 是否误屏蔽）"
          }
          def gatewayPort = sh(
            script: "grep -E '^GATEWAY_PORT=' ${envFile} | head -n1 | cut -d= -f2 | tr -d '\"' | tr -d \"'\"",
            returnStdout: true
          ).trim()
          if (!gatewayPort) {
            gatewayPort = "33333"
          }
          env.GATEWAY_PORT = gatewayPort
          echo "从 ${envFile} 解析到 GATEWAY_PORT=${env.GATEWAY_PORT}"
        }
      }
    }

    stage('Build Images') {
      steps {
        script {
          def buildArgs = "--build-arg APP_ENV=${env.APP_ENV}"
          sh "docker build ${buildArgs} -f alien_store/Dockerfile    -t ${env.IMAGE_STORE}    ."
          sh "docker build ${buildArgs} -f alien_gateway/Dockerfile  -t ${env.IMAGE_GATEWAY}  ."
          sh "docker build ${buildArgs} -f alien_contract/Dockerfile -t ${env.IMAGE_CONTRACT} ."
        }
      }
    }

    stage('Deploy') {
      steps {
        script {
          echo ">>> [${env.APP_ENV}] 部署镜像: ${env.IMAGE_STORE} / ${env.IMAGE_GATEWAY} / ${env.IMAGE_CONTRACT}"
          sh """
            set -e

            docker network create ${env.DOCKER_NET} 2>/dev/null || true
            mkdir -p ${env.LOG_ROOT}/store ${env.LOG_ROOT}/gateway ${env.LOG_ROOT}/contract

            docker rm -f ${env.CONTAINER_NAME_STORE} ${env.CONTAINER_NAME_GATEWAY} ${env.CONTAINER_NAME_CONTRACT} 2>/dev/null || true

            # 1) 下游：store
            #    APP_ENV=${env.APP_ENV} 让 config.py 加载镜像内的 .env.${env.APP_ENV}
            docker run -d --name ${env.CONTAINER_NAME_STORE} \\
              --network ${env.DOCKER_NET} \\
              -e APP_ENV=${env.APP_ENV} \\
              -v ${env.LOG_ROOT}/store:/app/common/logs/alien_store \\
              --restart unless-stopped \\
              ${env.IMAGE_STORE}

            # 2) 下游：contract
            docker run -d --name ${env.CONTAINER_NAME_CONTRACT} \\
              --network ${env.DOCKER_NET} \\
              -e APP_ENV=${env.APP_ENV} \\
              -v ${env.LOG_ROOT}/contract:/app/common/logs/alien_contract \\
              --restart unless-stopped \\
              ${env.IMAGE_CONTRACT}

            # 3) 网关：gateway（唯一对外端口）
            #    -e GATEWAY_PORT=... 是关键：必须覆盖 Dockerfile 的默认 33333，
            #    否则容器内 uvicorn 会监听默认端口，导致与宿主机映射端口对不上
            #    pydantic-settings 也是环境变量优先于 .env 文件
            docker run -d --name ${env.CONTAINER_NAME_GATEWAY} \\
              --network ${env.DOCKER_NET} \\
              -p ${env.GATEWAY_PORT}:${env.GATEWAY_PORT} \\
              -e APP_ENV=${env.APP_ENV} \\
              -e GATEWAY_PORT=${env.GATEWAY_PORT} \\
              -e STORE_BASE_URL=http://${env.CONTAINER_NAME_STORE}:8001 \\
              -e CONTRACT_BASE_URL=http://${env.CONTAINER_NAME_CONTRACT}:8002 \\
              -v ${env.LOG_ROOT}/gateway:/app/common/logs/alien_gateway \\
              --restart unless-stopped \\
              ${env.IMAGE_GATEWAY}
          """
        }
      }
    }

    stage('Smoke Test') {
      // 注意：Jenkins 本身可能跑在 Docker 容器里，无法直接 curl 宿主机端口。
      // 这里采取两层验证：
      //   1) 硬验证：3 个业务容器必须都在 running 状态（不通过则 fail）
      //   2) 软验证：通过 docker exec 进入 gateway 容器内部跑 /health 自检
      //      （HTTP 失败只 warn，不让 Pipeline 失败，因为这只代表 Jenkins 的网络位置看不到，
      //       不代表服务对外不可用，需要人工从其他位置验证）
      steps {
        script {
          sleep(time: 5, unit: 'SECONDS')

          def containers = [
            env.CONTAINER_NAME_STORE,
            env.CONTAINER_NAME_CONTRACT,
            env.CONTAINER_NAME_GATEWAY
          ]
          def allRunning = true
          containers.each { c ->
            def status = sh(
              returnStdout: true,
              script: "docker inspect -f '{{.State.Status}}' ${c} 2>/dev/null || echo missing"
            ).trim()
            echo "  ${c}: ${status}"
            if (status != 'running') {
              allRunning = false
              sh "docker logs --tail 50 ${c} || true"
            }
          }
          if (!allRunning) {
            error "存在容器未处于 running 状态，部署失败"
          }
          echo "✓ 3 个业务容器均在 running 状态"

          def healthCmd = """docker exec ${env.CONTAINER_NAME_GATEWAY} python -c 'import urllib.request as u; r=u.urlopen("http://localhost:${env.GATEWAY_PORT}/health", timeout=3); print("HTTP", r.status, r.read(200).decode())'"""
          def rc = sh(returnStatus: true, script: healthCmd)
          if (rc == 0) {
            echo "✓ gateway /health 通过"
          } else {
            echo "⚠️ gateway /health 未通过（容器在跑但 HTTP 自检失败，请从外部手动验证：curl http://<host>:${env.GATEWAY_PORT}/health）"
          }
        }
      }
    }
  }

  post {
    success {
      echo "[${env.APP_ENV}] 环境部署成功！请从外部访问 http://<host>:${env.GATEWAY_PORT}/health 验证"
    }
    failure {
      echo "[${env.APP_ENV}] 环境部署失败，请检查上面日志。"
    }
    always {
      cleanWs()
    }
  }
}
