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>"
  if (cc) {
    to = [to, cc].findAll { it }.join(',')
  }
  try {
    emailext(
      from: 'liguangyu@sinopharm.com',
      replyTo: 'liguangyu@sinopharm.com',
      to: to,
      subject: subject,
      body: body,
      mimeType: 'text/html',
      attachLog: false,
      attachmentsPattern: 'reports/latest/email-summary.html,reports/latest/email-summary.txt'
    )
    echo "已发送报告邮件至 ${to}（正文为摘要+报告链接，附件为 email-summary.html）"
  } catch (err) {
    echo "邮件发送失败。请安装 Email Extension 插件，并在 Manage Jenkins → System 配置 SMTP：${err}"
  }
}

/** Windows 节点用 bat（cmd），不用 Git Bash。 */
def runCi() {
  if (isUnix()) {
    sh 'node scripts/run-ci.mjs --suite="$SUITE" --shards="$SHARDS"'
  } else {
    bat '''
      set PATH=C:\\Program Files\\nodejs;%PATH%
      node scripts\\run-ci-win.mjs --suite=%SUITE% --shards=%SHARDS%
    '''
  }
}

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: 'liguangyu@sinopharm.com',
      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"
    SPD_CREDENTIALS_ID = "${env.SPD_CREDENTIALS_ID ?: 'spd-ui-test-account'}"
  }

  stages {
    stage('Prepare') {
      steps {
        script {
          if (isUnix()) {
            sh '''
              set -e
              node -v
              npm -v
              node -e "if (parseInt(process.versions.node.split('.')[0], 10) < 20) process.exit(1)"
            '''
          } else {
            bat '''
              node -v
              npm -v
              node -e "if (parseInt(process.versions.node.split('.')[0], 10) < 20) process.exit(1)"
            '''
          }
        }
      }
    }

    stage('Install') {
      steps {
        script {
          if (isUnix()) {
            sh '''
              set -e
              npm ci
              npx playwright install-deps chromium || true
              npm run playwright:install
            '''
          } else {
            bat '''
              call npm ci
              if errorlevel 1 exit /b 1
              call npm run playwright:install
              if errorlevel 1 exit /b 1
            '''
          }
        }
      }
    }

    stage('Typecheck') {
      steps {
        script {
          if (isUnix()) {
            sh 'npm run typecheck'
          } else {
            bat 'call npm run typecheck'
          }
        }
      }
    }

    stage('Test') {
      steps {
        script {
          if (env.TEST_USER?.trim() || fileExists('.env')) {
            runCi()
          } else {
            withCredentials([
              usernamePassword(
                credentialsId: env.SPD_CREDENTIALS_ID,
                usernameVariable: 'TEST_USER',
                passwordVariable: 'TEST_PASS'
              )
            ]) {
              runCi()
            }
          }
        }
      }
    }
  }

  post {
    always {
      script {
        try {
          junit allowEmptyResults: true, testResults: 'reports/latest/junit.xml'
        } catch (err) {
          echo "跳过 JUnit（代码可能尚未检出）：${err}"
        }
        try {
          archiveArtifacts artifacts: 'reports/latest/**, reports/ci-*/summary.json, reports/ci-*/meta.txt', allowEmptyArchive: true, fingerprint: true
        } catch (err) {
          echo "跳过归档：${err}"
        }
        try {
          if (fileExists('reports/latest/summary.json')) {
            def descScript = 'node -e "var s=require(\'./reports/latest/summary.json\'); var c=s.counts||{}; console.log(s.suite+\' shards=\'+s.shards+\' workers=\'+s.workers+\' fail=\'+(c.failures||0)+\'/\'+(c.tests||0))"'
            def desc = isUnix()
              ? sh(script: descScript, returnStdout: true).trim()
              : bat(script: "@echo off\n${descScript}", returnStdout: true).trim()
            currentBuild.description = desc
          }
        } catch (err) {
          echo "跳过构建描述：${err}"
        }
        try {
          publishHTML([
            allowMissing: true,
            alwaysLinkToLastBuild: true,
            keepAll: true,
            reportDir: 'reports/latest/playwright-report',
            reportFiles: 'index.html',
            reportName: 'Playwright Report'
          ])
        } catch (err) {
          echo "跳过 HTML 报告：${err}"
        }
        try {
          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')) {
                try {
                  publishHTML([
                    allowMissing: true,
                    alwaysLinkToLastBuild: true,
                    keepAll: true,
                    reportDir: 'reports/latest/allure-report',
                    reportFiles: 'index.html',
                    reportName: 'Allure Report'
                  ])
                } catch (ignored) {
                  echo "跳过 Allure HTML：${ignored}"
                }
              }
            }
          }
        } catch (err) {
          echo "跳过 Allure：${err}"
        }
        try {
          withEnv(["BUILD_RESULT=${currentBuild.currentResult}"]) {
            if (isUnix()) {
              sh 'node scripts/ci-email-summary.mjs || true'
            } else {
              bat returnStatus: true, script: 'node scripts\\ci-email-summary.mjs'
            }
          }
          sendReportEmail()
        } catch (err) {
          echo "跳过邮件：${err}"
        }
      }
    }
  }
}
