// Jenkinsfile —— jp-rakuten（抓取服务 + 有状态端）
//
// 一次构建出两个镜像，两者用途不同、不能互相替代：
//   git.jerryyan.net/jp/rakuten-api      ← Dockerfile          抓取服务（无状态可多开）
//   git.jerryyan.net/jp/rakuten-trading  ← Dockerfile.trading  有状态端（下单购买+订单查询）
// 有状态端单独出镜像是因为下单/结算必须用**有头** Chromium（headless 会让结算 SPA
// 失灵），镜像里要带 Xvfb + 日文字体 + 可选 x11vnc，抓取镜像没有这些。
// 网关（app.gateway.main）复用 rakuten-trading 镜像，只是换 command。
//
// 构建参数说明见各 stage 顶部；首次使用前请在 Jenkins 里配置凭据：
//   - git-credentials : 拉代码的 SSH / HTTPS 凭据
//   - gitea-registry  : docker login git.jerryyan.net 的凭据（username/password）

pipeline {
    agent any

    options {
        timestamps()
        timeout(time: 30, unit: 'MINUTES')
        disableConcurrentBuilds()
        buildDiscarder(logRotator(numToKeepStr: '20', daysToKeepStr: '14'))
    }

    parameters {
        string(name: 'IMAGE_NAME',
               defaultValue: 'git.jerryyan.net/jp/rakuten-api',
               description: '抓取服务镜像完整名（含 registry host）')
        string(name: 'TRADING_IMAGE_NAME',
               defaultValue: 'git.jerryyan.net/jp/rakuten-trading',
               description: '有状态端（下单+订单查询，含网关）镜像完整名')
        string(name: 'IMAGE_TAG',
               defaultValue: '',
               description: '自定义 tag；留空则用 <BUILD_NUMBER>-<git short sha>。两个镜像共用同一 tag')
        booleanParam(name: 'BUILD_SCRAPING',
                     defaultValue: true,
                     description: '构建并推送抓取服务镜像')
        booleanParam(name: 'BUILD_TRADING',
                     defaultValue: true,
                     description: '构建并推送有状态端镜像（体积大：含 Chromium + Xvfb，约 +700MB）')
        booleanParam(name: 'SKIP_TEST',
                     defaultValue: false,
                     description: '跳过单测（紧急发版用，正常构建不要勾）')
    }

    environment {
        // 让 docker build 看到 BuildKit 缓存挂载等特性
        DOCKER_BUILDKIT = '1'
        // 禁掉 pip 警告、Jenkins 写 .pyc
        PYTHONUNBUFFERED = '1'
        PYTHONDONTWRITEBYTECODE = '1'
        // 国内镜像：uv / pip 拉包走阿里云 PyPI 镜像，避免拉取缓慢
        UV_INDEX_URL = 'https://mirrors.aliyun.com/pypi/simple/'
    }

    stages {

        stage('Checkout') {
            steps {
                checkout scm
                script {
                    env.GIT_SHA = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
                    env.GIT_TAG = sh(script: 'git describe --tags --always --dirty 2>/dev/null || echo ""', returnStdout: true).trim()
                    if (!params.IMAGE_TAG?.trim()) {
                        env.IMAGE_TAG = "${env.BUILD_NUMBER}-${env.GIT_SHA}"
                    }
                    echo "镜像目标: ${env.IMAGE_NAME}:${env.IMAGE_TAG}（git=${env.GIT_SHA}）"
                }
            }
        }

        stage('Test') {
            // 跳过逻辑：SKIP_TEST=true 时整个 stage 不跑
            when {
                expression { return !params.SKIP_TEST }
            }
            // 在官方 Python 镜像里跑测试，避免污染宿主机；只装 dev extra，不装 browser
            // 用 -u root 是因为 Jenkins 默认把容器用户映射到 jenkins uid，HOME=/，
            // pip user-site 会解析到 /.local 直接报错；测试容器跑完即销毁，跑 root 无副作用
            agent {
                docker {
                    image 'registry.jerryyan.top/library/python:3.13-slim'
                    reuseNode true
                    args '-u root:root'
                }
            }
            steps {
                sh '''
                    set -e
                    export HOME=/root
                    export PIP_CACHE_DIR=/root/.cache/pip
                    # 阿里云 PyPI 镜像，UV_INDEX_URL 由 pipeline environment 注入
                    python -m pip install --quiet --upgrade pip -i "$UV_INDEX_URL"
                    pip install --quiet uv -i "$UV_INDEX_URL"
                    uv sync --frozen --extra dev
                    .venv/bin/python -m pytest -q
                '''
            }
        }

        stage('Build image: scraping') {
            when {
                expression { return params.BUILD_SCRAPING }
            }
            steps {
                sh """
                    docker build \
                        -t ${env.IMAGE_NAME}:${env.IMAGE_TAG} \
                        -t ${env.IMAGE_NAME}:latest \
                        --label org.opencontainers.image.revision=${env.GIT_SHA} \
                        --label org.opencontainers.image.version=${env.IMAGE_TAG} \
                        -f Dockerfile .
                """
            }
        }

        stage('Build image: trading') {
            when {
                expression { return params.BUILD_TRADING }
            }
            // 有状态端镜像：Chromium + Xvfb + 日文字体，比抓取镜像明显大也明显慢，
            // 首次构建（无缓存）拉 Chromium 二进制约 300MB
            steps {
                sh """
                    docker build \
                        -t ${params.TRADING_IMAGE_NAME}:${env.IMAGE_TAG} \
                        -t ${params.TRADING_IMAGE_NAME}:latest \
                        --label org.opencontainers.image.revision=${env.GIT_SHA} \
                        --label org.opencontainers.image.version=${env.IMAGE_TAG} \
                        -f Dockerfile.trading .
                """
            }
        }

        stage('Push images') {
            when {
                expression { return params.BUILD_SCRAPING || params.BUILD_TRADING }
            }
            steps {
                withCredentials([usernamePassword(
                    credentialsId: 'gitea-registry',
                    usernameVariable: 'REGISTRY_USER',
                    passwordVariable: 'REGISTRY_PASS',
                )]) {
                    sh 'echo "$REGISTRY_PASS" | docker login git.jerryyan.net -u "$REGISTRY_USER" --password-stdin'
                    script {
                        if (params.BUILD_SCRAPING) {
                            sh """
                                docker push ${env.IMAGE_NAME}:${env.IMAGE_TAG}
                                docker push ${env.IMAGE_NAME}:latest
                            """
                        }
                        if (params.BUILD_TRADING) {
                            sh """
                                docker push ${params.TRADING_IMAGE_NAME}:${env.IMAGE_TAG}
                                docker push ${params.TRADING_IMAGE_NAME}:latest
                            """
                        }
                    }
                }
            }
        }
    }

    post {
        always {
            // 清理本次构建产生的镜像，避免 Jenkins agent 磁盘膨胀
            sh """
                docker rmi -f ${env.IMAGE_NAME}:${env.IMAGE_TAG} 2>/dev/null || true
                docker rmi -f ${env.IMAGE_NAME}:latest 2>/dev/null || true
                docker rmi -f ${params.TRADING_IMAGE_NAME}:${env.IMAGE_TAG} 2>/dev/null || true
                docker rmi -f ${params.TRADING_IMAGE_NAME}:latest 2>/dev/null || true
            """
        }
        success {
            echo "构建成功：tag=${env.IMAGE_TAG}" +
                 (params.BUILD_SCRAPING ? " ${env.IMAGE_NAME}" : "") +
                 (params.BUILD_TRADING ? " ${params.TRADING_IMAGE_NAME}" : "")
        }
        failure {
            echo "构建失败，请查看上方日志"
        }
    }
}