// see https://www.jenkins.io/doc/book/pipeline/syntax/
//
// Monorepo pipeline for gadmin-test: 3 deployable modules (web / server / worker).
// 路径触发：哪个模块代码有改动就构建哪个，纯文档改动跳过所有构建。
// 风格沿用 erp-frontend / erp-backend 的 Jenkinsfile（声明式、Coding CI 环境变量、Spinnaker webhook）。

pipeline {
  agent any

  environment {
    CODING_DOCKER_REG_HOST = "${CCI_CURRENT_TEAM}-${CCI_DOCKER_REGISTRY_DOMAIN}"
    IMAGE_VERSION = "build-${env.CI_BUILD_NUMBER}"
    DEPLOY_ENV = "${env.BRANCH_NAME == 'test' ? 'test' : 'prod'}"
    IMAGE_NAME_PREFIX = "${PROJECT_NAME.toLowerCase()}/${DEPLOY_ENV}"

    // 镜像名后缀（Coding CI 通常用 ${CCI_JOB_NAME}，但 monorepo 需要 3 个不同名字，这里固定写死）
    // TODO: 确认这三个 image name 与 Spinnaker 应用 / K8s deployment 命名一致
    WEB_IMAGE_SHORT    = "gadmin2-web"
    SERVER_IMAGE_SHORT = "gadmin2-server"
    WORKER_IMAGE_SHORT = "gadmin2-worker"

    // 代码生成中间镜像（不推送到 registry，仅本地 docker daemon 临时存活）
    CODEGEN_IMAGE = "gadmin2-codegen:${IMAGE_VERSION}"
  }

  options {
    timeout(time: 30, unit: 'MINUTES')
  }

  stages {

    stage('检出Git') {
      when { anyOf { branch 'test'; branch 'master' } }
      steps {
        checkout([$class: 'GitSCM',
          branches: [[name: env.GIT_BUILD_REF]],
          userRemoteConfigs: [[url: env.GIT_REPO_URL, credentialsId: env.CREDENTIALS_ID]]
        ])
      }
    }

    stage('检测变更模块') {
      when { anyOf { branch 'test'; branch 'master' } }
      steps {
        script {
          // 对比基准：优先使用 Jenkins git plugin 的 GIT_PREVIOUS_SUCCESSFUL_COMMIT
          // 兜底：基准缺失或 commit 在当前 clone 中找不到 → 全量构建（保险策略）
          def base = env.GIT_PREVIOUS_SUCCESSFUL_COMMIT ?: ''
          def baseValid = false
          if (base) {
            // shallow clone 时旧 commit 可能不在本地，检测一下
            def rc = sh(script: "git cat-file -e ${base}^{commit} 2>/dev/null", returnStatus: true)
            baseValid = (rc == 0)
            if (!baseValid) {
              echo "GIT_PREVIOUS_SUCCESSFUL_COMMIT=${base} 在当前 clone 中不可达（可能是 shallow clone），全量构建"
            }
          } else {
            echo "无 GIT_PREVIOUS_SUCCESSFUL_COMMIT（首次构建？），全量构建"
          }

          def changedFiles = []
          if (baseValid) {
            def diffOut = sh(script: "git diff --name-only ${base} HEAD", returnStdout: true).trim()
            changedFiles = diffOut ? diffOut.split('\n') as List : []
            echo "对比基准 ${base}..HEAD，变更文件 ${changedFiles.size()} 个"
          }

          // 路径规则
          def matches = { List files, String prefix -> files.any { it.startsWith(prefix) } }
          def isDocOnly = { String f ->
            f.startsWith('docs/') || f == 'CLAUDE.md' || f == 'readme.md' ||
            f == '.gitignore' || f == 'Jenkinsfile' || f == 'sync-to-template.sh' ||
            f == 'sync-to-template.md' || f.startsWith('.claude/')
          }

          def buildWeb    = false
          def buildServer = false
          def buildWorker = false

          if (!baseValid) {
            buildWeb = true; buildServer = true; buildWorker = true
          } else if (changedFiles.isEmpty()) {
            echo '没有文件变更，全部跳过'
          } else {
            // 纯文档变更 → 全部跳过
            def nonDoc = changedFiles.findAll { !isDocOnly(it) }
            if (nonDoc.isEmpty()) {
              echo '仅文档/配置文件变更，全部跳过'
            } else {
              // 路径触发规则：
              //   web/**            → web
              //   config/ui/**      → web（前端 CRUD 代码由 config/ui 生成）
              //   config/prisma/**  → server + web（后端模型由 prisma 生成；前端 enums 通过 yarn sync:code 与后端同步）
              //   server/**         → server
              //   temporal/worker/**→ worker
              buildWeb    = matches(nonDoc, 'web/') || matches(nonDoc, 'config/ui/') || matches(nonDoc, 'config/prisma/')
              buildServer = matches(nonDoc, 'server/') || matches(nonDoc, 'config/prisma/')
              buildWorker = matches(nonDoc, 'temporal/worker/')
              // 注：本项目根目录没有共享的 package.json / tsconfig.json，
              //     各模块依赖独立在 web/ server/ temporal/worker/ 下，无需"全量兜底"逻辑。
            }
          }

          // 用 env 暴露给后续 stage（when 表达式只能读 env / params）
          env.BUILD_WEB    = buildWeb.toString()
          env.BUILD_SERVER = buildServer.toString()
          env.BUILD_WORKER = buildWorker.toString()

          echo "buildWeb=${buildWeb}, buildServer=${buildServer}, buildWorker=${buildWorker}"
        }
      }
    }

    stage('代码生成') {
      // 仅当需要构建 web 或 server 时执行；worker 模块与 gadmin2 codegen 无关
      when {
        anyOf {
          environment name: 'BUILD_WEB', value: 'true'
          environment name: 'BUILD_SERVER', value: 'true'
        }
      }
      steps {
        script {
          // 构建一个本地代码生成镜像：内部装好 gadmin2 CLI + server devDeps，
          // 并已执行 `gadmin2 g prisma`，产出包含生成代码的 /app/web 与 /app/server。
          //
          // 关键点：
          //   - 镜像不 push 到 registry，仅在本 Jenkins agent 的 docker daemon 中存活
          //   - 后续 Dockerfile.web / Dockerfile.server 通过 ARG CODEGEN_IMAGE + FROM ${CODEGEN_IMAGE}
          //     直接引用本地镜像，无需 registry 周转
          //   - 流水线末尾会清理本镜像
          docker.build(env.CODEGEN_IMAGE, "-f Dockerfile.codegen .")
        }
      }
    }

    stage('并行构建推送镜像') {
      when { anyOf { branch 'test'; branch 'master' } }
      parallel {

        stage('构建 Web') {
          when { environment name: 'BUILD_WEB', value: 'true' }
          steps {
            script {
              def imageName = "${env.IMAGE_NAME_PREFIX}/${env.WEB_IMAGE_SHORT}"
              def imageFull = "${env.CODING_DOCKER_REG_HOST}/${imageName}:${env.IMAGE_VERSION}"
              def buildArg  = (env.BRANCH_NAME == 'test') ? '--build-arg NODE_ENV=test' : '--build-arg NODE_ENV=production'

              docker.withRegistry(
                "${CCI_CURRENT_WEB_PROTOCOL}://${env.CODING_DOCKER_REG_HOST}",
                "${env.CODING_ARTIFACTS_CREDENTIALS_ID}"
              ) {
                // FROM ${CODEGEN_IMAGE} 在本地 docker daemon 解析，不会触发 registry 拉取
                docker.build(
                  "${imageName}:${env.IMAGE_VERSION}",
                  "${buildArg} --build-arg CODEGEN_IMAGE=${env.CODEGEN_IMAGE} -f Dockerfile.web ."
                ).push()
                sh "docker image rm ${imageFull}"
              }
              env.WEB_IMAGE_FULL = imageFull
            }
          }
        }

        stage('构建 Server') {
          when { environment name: 'BUILD_SERVER', value: 'true' }
          steps {
            script {
              def imageName = "${env.IMAGE_NAME_PREFIX}/${env.SERVER_IMAGE_SHORT}"
              def imageFull = "${env.CODING_DOCKER_REG_HOST}/${imageName}:${env.IMAGE_VERSION}"

              docker.withRegistry(
                "${CCI_CURRENT_WEB_PROTOCOL}://${env.CODING_DOCKER_REG_HOST}",
                "${env.CODING_ARTIFACTS_CREDENTIALS_ID}"
              ) {
                // FROM ${CODEGEN_IMAGE} 在本地 docker daemon 解析，不会触发 registry 拉取
                docker.build(
                  "${imageName}:${env.IMAGE_VERSION}",
                  "--build-arg CODEGEN_IMAGE=${env.CODEGEN_IMAGE} -f Dockerfile.server ."
                ).push()
                sh "docker image rm ${imageFull}"
              }
              env.SERVER_IMAGE_FULL = imageFull
            }
          }
        }

        stage('构建 Worker') {
          when { environment name: 'BUILD_WORKER', value: 'true' }
          steps {
            script {
              def imageName = "${env.IMAGE_NAME_PREFIX}/${env.WORKER_IMAGE_SHORT}"
              def imageFull = "${env.CODING_DOCKER_REG_HOST}/${imageName}:${env.IMAGE_VERSION}"

              docker.withRegistry(
                "${CCI_CURRENT_WEB_PROTOCOL}://${env.CODING_DOCKER_REG_HOST}",
                "${env.CODING_ARTIFACTS_CREDENTIALS_ID}"
              ) {
                // worker 的构建上下文是 temporal/worker/
                docker.build("${imageName}:${env.IMAGE_VERSION}", "-f temporal/worker/Dockerfile temporal/worker").push()
                sh "docker image rm ${imageFull}"
              }
              env.WORKER_IMAGE_FULL = imageFull
            }
          }
        }
      }
    }

    stage('触发 Spinnaker 部署') {
      when { anyOf { branch 'test'; branch 'master' } }
      steps {
        script {
          // 任一模块构建过就触发对应的 Spinnaker webhook
          // TODO: 确认 Spinnaker 上是按"每模块一个 application"还是"一个 application 接收所有模块"
          //       下面按"每模块一个 application"实现，与 erp-frontend / erp-backend 保持一致
          def env_ = env.DEPLOY_ENV  // test 或 prod
          def webhookBase = 'https://api-spinnaker.devops.intlgame.com/webhooks/webhook'

          def triggerOne = { String moduleSlug, String imageFull ->
            // TODO: 确认 webhook 路径名 oit-gadmin2-${moduleSlug}-${env_} 已在 Spinnaker 注册
            def url = "${webhookBase}/oit-gadmin2-${moduleSlug}-${env_}"
            def payload = """{"parameters": {"image_tag": "${env.IMAGE_VERSION}", "repository": "${imageFull}"}}"""
            sh "curl -sf '${url}' -X POST -H 'content-type: application/json' -d '${payload}'"
            echo "已触发 ${moduleSlug} 部署：${url}"
          }

          if (env.BUILD_WEB == 'true' && env.WEB_IMAGE_FULL) {
            triggerOne('web', env.WEB_IMAGE_FULL)
          }
          if (env.BUILD_SERVER == 'true' && env.SERVER_IMAGE_FULL) {
            triggerOne('server', env.SERVER_IMAGE_FULL)
          }
          if (env.BUILD_WORKER == 'true' && env.WORKER_IMAGE_FULL) {
            triggerOne('worker', env.WORKER_IMAGE_FULL)
          }

          if (env.BUILD_WEB != 'true' && env.BUILD_SERVER != 'true' && env.BUILD_WORKER != 'true') {
            echo '本次构建无模块更新，跳过 Spinnaker 触发'
          } else {
            echo '部署已经触发，可以在 Spinnaker 查看状态: https://api-spinnaker.devops.intlgame.com'
          }
        }
      }
    }
  }

  post {
    // 清理本地 codegen 中间镜像（不影响 web/server 已 push 的产物镜像）
    always {
      script {
        sh "docker image rm ${env.CODEGEN_IMAGE} || true"
      }
    }
  }
}
