pipeline {
    agent any

    options {
        skipDefaultCheckout(true)
    }

    parameters {
        booleanParam(name: 'RUN_FULL_E2E', defaultValue: false, description: '是否在部署后额外执行全量 E2E（失败仅标记 UNSTABLE）')
        booleanParam(name: 'RUN_COVERAGE', defaultValue: false, description: '是否在主链路执行覆盖率测试（关闭可提速）')
    }

    environment {
        APP_NAME = 'no-whatever'
        CI_IMAGE = 'mcr.microsoft.com/playwright:v1.51.1-jammy'
        NPM_REGISTRY = 'https://registry.npmmirror.com'
        BUILDX_CACHE_DIR = '.buildx-cache'
    }

    triggers {
        GenericTrigger(tokenCredentialId: 'no-whatever-deploy-token')
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        stage('Runtime Check') {
            steps {
                sh 'docker --version'
            }
        }

        stage('Prepare Test Images') {
            options {
                timeout(time: 15, unit: 'MINUTES')
            }
            steps {
                retry(2) {
                    sh '''
                        set -e

                        if docker image inspect ${CI_IMAGE} >/dev/null 2>&1; then
                          echo "${CI_IMAGE} already exists, skip pull"
                        else
                          docker pull ${CI_IMAGE}
                        fi
                    '''
                }
            }
        }

        stage('Prepare CI Deps Image') {
            options {
                timeout(time: 20, unit: 'MINUTES')
            }
            steps {
                script {
                    env.LOCK_HASH = sh(script: "sha256sum package-lock.json | cut -d ' ' -f1", returnStdout: true).trim()
                    env.CI_DEPS_IMAGE = "${APP_NAME}-ci-deps:${env.LOCK_HASH}"
                }
                sh '''
                    set -e
                    if docker image inspect "${CI_DEPS_IMAGE}" >/dev/null 2>&1; then
                      echo "deps image cache hit: ${CI_DEPS_IMAGE}"
                    else
                      echo "deps image cache miss, building: ${CI_DEPS_IMAGE}"
                      docker build \
                        --build-arg CI_IMAGE=${CI_IMAGE} \
                        --build-arg NPM_REGISTRY=${NPM_REGISTRY} \
                        -f Dockerfile.ci-deps \
                        -t "${CI_DEPS_IMAGE}" .
                    fi
                '''
            }
        }

        stage('CI Gate (Lint + Unit + Smoke E2E)') {
            options {
                timeout(time: 30, unit: 'MINUTES')
            }
            steps {
                sh '''
                    set -e
                    rm -rf playwright-report test-results
                    cid=$(docker create --ipc=host \
                      -e HOME=/tmp \
                      -e RUN_COVERAGE=${RUN_COVERAGE} \
                      ${CI_DEPS_IMAGE} \
                      sh -lc 'set -e; cd /workspace; npx prisma generate; npm run lint; if [ "$RUN_COVERAGE" = "true" ]; then npm run test:coverage; else npm run test; fi; npm run test:e2e:smoke')
                    cleanup() {
                      docker rm -f "$cid" >/dev/null 2>&1 || true
                    }
                    trap cleanup EXIT
                    git archive --format=tar HEAD | docker cp - "$cid":/workspace
                    set +e
                    docker start -a "$cid"
                    status=$?
                    set -e
                    docker cp "$cid":/workspace/playwright-report ./playwright-report 2>/dev/null || true
                    docker cp "$cid":/workspace/test-results ./test-results 2>/dev/null || true
                    exit $status
                '''
            }
            post {
                always {
                    archiveArtifacts artifacts: 'playwright-report/**,test-results/**', allowEmptyArchive: true
                }
            }
        }

        stage('Build Docker Image') {
            steps {
                withCredentials([
                    string(credentialsId: 'amap-api-key', variable: 'AMAP_KEY')
                ]) {
                    sh '''
                        set -e
                        CACHE_DIR="${BUILDX_CACHE_DIR}"
                        CACHE_NEW_DIR="${BUILDX_CACHE_DIR}-new"
                        rm -rf "$CACHE_NEW_DIR"

                        if docker buildx version >/dev/null 2>&1; then
                          echo "using docker buildx with local cache"
                          docker buildx create --name "${APP_NAME}-builder" --use >/dev/null 2>&1 || true
                          docker buildx use "${APP_NAME}-builder" >/dev/null 2>&1 || true
                          mkdir -p "$CACHE_DIR"

                          set +e
                          docker buildx build \
                            --load \
                            --build-arg NEXT_PUBLIC_AMAP_API_KEY="$AMAP_KEY" \
                            --cache-from type=local,src="$CACHE_DIR" \
                            --cache-to type=local,dest="$CACHE_NEW_DIR",mode=max \
                            -t "${APP_NAME}:${BUILD_NUMBER}" \
                            -t "${APP_NAME}:latest" \
                            .
                          bx_status=$?
                          set -e

                          if [ $bx_status -eq 0 ]; then
                            rm -rf "$CACHE_DIR"
                            mv "$CACHE_NEW_DIR" "$CACHE_DIR"
                          else
                            echo "buildx build failed, fallback to classic docker build"
                            docker build \
                              --build-arg NEXT_PUBLIC_AMAP_API_KEY="$AMAP_KEY" \
                              -t "${APP_NAME}:${BUILD_NUMBER}" \
                              -t "${APP_NAME}:latest" \
                              .
                          fi
                        else
                          echo "docker buildx unavailable, fallback to classic docker build"
                          docker build \
                            --build-arg NEXT_PUBLIC_AMAP_API_KEY="$AMAP_KEY" \
                            -t "${APP_NAME}:${BUILD_NUMBER}" \
                            -t "${APP_NAME}:latest" \
                            .
                        fi
                    '''
                }
            }
        }

        stage('Deploy') {
            steps {
                withCredentials([
                    string(credentialsId: 'amap-api-key', variable: 'AMAP_KEY'),
                    string(credentialsId: 'deepseek-api-key', variable: 'DEEPSEEK_KEY')
                ]) {
                    sh """
                        docker stop ${APP_NAME} || true
                        docker rm ${APP_NAME} || true
                        mkdir -p /data/${APP_NAME}
                        chown 1001:1001 /data/${APP_NAME}
                        docker run -d \
                            --name ${APP_NAME} \
                            --network nginx \
                            -p 3721:3721 \
                            -v /data/${APP_NAME}:/app/data \
                            -e DATABASE_URL=file:/app/data/prod.db \
                            -e AMAP_API_KEY=${AMAP_KEY} \
                            -e DEEPSEEK_API_KEY=${DEEPSEEK_KEY} \
                            --restart unless-stopped \
                            ${APP_NAME}:latest
                    """
                }
            }
        }

        stage('Full E2E (Optional, Non-Blocking)') {
            when {
                expression { return params.RUN_FULL_E2E }
            }
            options {
                timeout(time: 30, unit: 'MINUTES')
            }
            steps {
                catchError(buildResult: 'UNSTABLE', stageResult: 'UNSTABLE') {
                    sh '''
                        set -e
                        rm -rf playwright-report-full test-results-full
                        cid=$(docker create --ipc=host \
                          -e HOME=/tmp \
                          ${CI_DEPS_IMAGE} \
                          sh -lc 'set -e; cd /workspace; npx prisma generate; npm run test:e2e')
                        cleanup() {
                          docker rm -f "$cid" >/dev/null 2>&1 || true
                        }
                        trap cleanup EXIT
                        git archive --format=tar HEAD | docker cp - "$cid":/workspace
                        set +e
                        docker start -a "$cid"
                        status=$?
                        set -e
                        docker cp "$cid":/workspace/playwright-report ./playwright-report-full 2>/dev/null || true
                        docker cp "$cid":/workspace/test-results ./test-results-full 2>/dev/null || true
                        exit $status
                    '''
                }
            }
            post {
                always {
                    archiveArtifacts artifacts: 'playwright-report-full/**,test-results-full/**', allowEmptyArchive: true
                }
            }
        }
    }

    post {
        success {
            echo "Deployed ${APP_NAME} build #${BUILD_NUMBER} successfully"
        }
        failure {
            echo "Build #${BUILD_NUMBER} failed"
        }
    }
}
