def sendReportEmail() {
  def to = (params.REPORT_EMAIL_TO ?: '').trim()
  if (!to) {
    to = (env.REPORT_EMAIL_TO ?: '').trim()
  }
  def cc = (params.REPORT_EMAIL_CC ?: '').trim()
  if (!cc) {
    cc = (env.REPORT_EMAIL_CC ?: '').trim()
  }
  if (!to) {
    echo '未配置 REPORT_EMAIL_TO，跳过邮件。在任务参数或环境变量里填写收件人（逗号分隔）。'
    return
  }
  def subject = fileExists('reports/latest/email-subject.txt')
    ? readFile(encoding: 'UTF-8', file: 'reports/latest/email-subject.txt').trim()
    : "【SPD UI自动化】${params.SUITE} ${currentBuild.currentResult} #${env.BUILD_NUMBER}"
  def body = fileExists('reports/latest/email-summary.html')
    ? readFile(encoding: 'UTF-8', file: 'reports/latest/email-summary.html')
    : "<p>构建 ${currentBuild.currentResult}，未生成摘要。请打开 <a href='${env.BUILD_URL}'>Jenkins</a></p>"
  try {
    emailext(
      to: to,
      cc: cc,
      subject: subject,
      body: body,
      mimeType: 'text/html',
      charset: 'UTF-8',
      attachLog: false,
      recipientProviders: []
    )
    echo "已发送报告邮件至 ${to}"
  } catch (err) {
    echo "邮件发送失败。请安装 Email Extension 插件，并在 Manage Jenkins → System 配置 SMTP：${err}"
  }
}

pipeline {
  agent any

  options {
    timestamps()
    disableConcurrentBuilds()
    timeout(time: 6, unit: 'HOURS')
    buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '10'))
  }

  parameters {
    choice(
      name: 'SUITE',
      choices: ['smoke', 'nightly', 'full', 'center', 'dept', 'invoice', 'trace', 'consume', 'recon', 'cost', 'warn', 'pol', 'exc', 'ana', 'ops', 'reag', 'invoice-trace', 'recon-cost', 'extended', 'quarantine'],
      description: '执行套件（见 config/suites.config.mjs）'
    )
    string(name: 'WORKERS', defaultValue: '3', description: '单进程内并行文件数（同一 spec 不拆开）')
    string(name: 'SHARDS', defaultValue: '1', description: '本机分片进程数；夜间建议 2，总浏览器约 SHARDS×WORKERS')
    string(name: 'RETRIES', defaultValue: '0', description: '整条用例重试次数，默认 0；瞬时加载由操作层重试，勿全局打开')
    booleanParam(name: 'RUN_FULL', defaultValue: false, description: '包含 @full 全列排序（耗时长）')
    string(
      name: 'REPORT_EMAIL_TO',
      defaultValue: '',
      description: '报告收件人，逗号分隔。留空则用任务环境变量 REPORT_EMAIL_TO；都空则不发信'
    )
    string(name: 'REPORT_EMAIL_CC', defaultValue: '', description: '抄送，逗号分隔，可选')
  }

  environment {
    CI = '1'
    SUITE = "${params.SUITE}"
    PW_WORKERS = "${params.WORKERS}"
    SHARDS = "${params.SHARDS}"
    PW_RETRIES = "${params.RETRIES}"
    RUN_FULL = "${params.RUN_FULL ? '1' : '0'}"
    PLAYWRIGHT_BROWSERS_PATH = "${WORKSPACE}/.playwright-browsers"
    // 在 Jenkins 凭据里创建 Username/Password，ID 与此一致；或在任务里覆盖
    SPD_CREDENTIALS_ID = "${env.SPD_CREDENTIALS_ID ?: 'spd-ui-test-account'}"
  }

  stages {
    stage('Prepare') {
      steps {
        sh '''
          set -e
          node -v
          npm -v
          test "$(node -p "process.versions.node.split('.')[0]")" -ge 20
        '''
      }
    }

    stage('Install') {
      steps {
        sh '''
          set -e
          npm ci
          npx playwright install-deps chromium || true
          npm run playwright:install
        '''
      }
    }

    stage('Typecheck') {
      steps {
        sh 'npm run typecheck'
      }
    }

    stage('Test') {
      steps {
        script {
          // 已注入账号，或 Agent 上有 .env 时不强制绑凭据
          if (env.TEST_USER?.trim() || fileExists('.env')) {
            sh 'node scripts/run-ci.mjs --suite="$SUITE" --shards="$SHARDS"'
          } else {
            withCredentials([
              usernamePassword(
                credentialsId: env.SPD_CREDENTIALS_ID,
                usernameVariable: 'TEST_USER',
                passwordVariable: 'TEST_PASS'
              )
            ]) {
              sh 'node scripts/run-ci.mjs --suite="$SUITE" --shards="$SHARDS"'
            }
          }
        }
      }
    }
  }

  post {
    always {
      junit allowEmptyResults: true, testResults: 'reports/latest/junit.xml'
      archiveArtifacts artifacts: 'reports/latest/**, reports/ci-*/summary.json, reports/ci-*/meta.txt', allowEmptyArchive: true, fingerprint: true
      script {
        if (fileExists('reports/latest/summary.json')) {
          def desc = sh(
            script: 'node -p "const s=require(\'./reports/latest/summary.json\'); const c=s.counts||{}; `${s.suite} shards=${s.shards} workers=${s.workers} fail=${c.failures||0}/${c.tests||0}`"',
            returnStdout: true,
          ).trim()
          currentBuild.description = desc
        }
      }
      publishHTML([
        allowMissing: true,
        alwaysLinkToLastBuild: true,
        keepAll: true,
        reportDir: 'reports/latest/playwright-report',
        reportFiles: 'index.html',
        reportName: 'Playwright Report'
      ])
      script {
        if (fileExists('reports/latest/allure-results')) {
          try {
            allure includeProperties: false, jdk: '', results: [[path: 'reports/latest/allure-results']]
          } catch (err) {
            echo "Allure 插件不可用，已保留 reports/latest/allure-report：${err}"
            if (fileExists('reports/latest/allure-report/index.html')) {
              publishHTML([
                allowMissing: true,
                alwaysLinkToLastBuild: true,
                keepAll: true,
                reportDir: 'reports/latest/allure-report',
                reportFiles: 'index.html',
                reportName: 'Allure Report'
              ])
            }
          }
        }
        withEnv(["BUILD_RESULT=${currentBuild.currentResult}"]) {
          sh 'node scripts/ci-email-summary.mjs || true'
        }
        sendReportEmail()
      }
    }
  }
}
