def splitEmailAddrs(String raw) {
  return (raw ?: '')
    .split(/[,;\s]+/)
    .collect { it.trim() }
    .findAll { it.contains('@') }
}

def loadDefaultReportRecipients() {
  def path = 'config/report-email-recipients.txt'
  if (!fileExists(path)) {
    return []
  }
  return readFile(encoding: 'UTF-8', file: path)
    .split('\n')
    .collect { it.trim() }
    .findAll { it && !it.startsWith('#') && it.contains('@') }
}

def mergeEmailAddrs(List parts) {
  def seen = []
  def all = []
  parts.flatten().findAll { it }.each { addr ->
    def key = addr.toLowerCase()
    if (!seen.contains(key)) {
      seen.add(key)
      all.add(addr)
    }
  }
  return all
}

def sendReportEmail() {
  def extraTo = (params.REPORT_EMAIL_TO ?: '').trim()
  if (!extraTo) {
    extraTo = (env.REPORT_EMAIL_TO ?: '').trim()
  }
  def cc = (params.REPORT_EMAIL_CC ?: '').trim()
  if (!cc) {
    cc = (env.REPORT_EMAIL_CC ?: '').trim()
  }
  def recipients = mergeEmailAddrs([
    loadDefaultReportRecipients(),
    splitEmailAddrs(extraTo),
    splitEmailAddrs(cc),
  ])
  if (!recipients) {
    echo '未配置收件人，跳过邮件。默认名单见 config/report-email-recipients.txt，也可填任务参数 REPORT_EMAIL_TO。'
    return
  }
  def to = recipients.join(',')
  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}，未生成详细摘要。</p>
<p>测试范围：${params.SUITE}</p>
<p>Playwright 报告：<a href='${env.BUILD_URL}Playwright_20Report/'>打开 Playwright 报告</a></p>
<p>Allure 报告：<a href='${env.BUILD_URL}allure'>打开 Allure 报告</a></p>"""
  if (!fileExists('reports/latest/email-summary.html')) {
    writeFile encoding: 'UTF-8', file: 'reports/latest/email-summary.html', text: body
  }
  if (!fileExists('reports/latest/email-subject.txt')) {
    writeFile encoding: 'UTF-8', file: 'reports/latest/email-subject.txt', text: subject + '\n'
  }
  try {
    def credId = env.SMTP_CREDENTIALS_ID ?: 'sinopharm-smtp'
    withCredentials([
      usernamePassword(credentialsId: credId, usernameVariable: 'SMTP_USER', passwordVariable: 'SMTP_PASS')
    ]) {
      withEnv([
        "EMAIL_TO=${to}",
        'SMTP_HOST=mail.sinopharm.com',
        'SMTP_PORT=465',
        'SMTP_FROM=liguangyu@sinopharm.com'
      ]) {
        if (isUnix()) {
          sh 'python3 scripts/send-ci-email.py'
        } else {
          bat 'python scripts\\send-ci-email.py'
        }
      }
    }
    echo "已发送报告邮件至 ${to}"
  } catch (err) {
    echo "邮件发送失败（国药邮箱 SMTP LOGIN/465）：${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: ['full', 'nightly', 'smoke', 'login', 'center', 'dept', 'invoice', 'trace', 'consume', 'recon', 'cost', 'warn', 'pol', 'exc', 'ana', 'ops', 'reag', 'invoice-trace', 'recon-cost', 'extended', 'quarantine'],
      description: '执行方式（默认 full 全量回归，含 @full 全列排序）'
    )
    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: '追加收件人（逗号分隔，可选）。默认名单在 config/report-email-recipients.txt，构建时自动带上'
    )
    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'}"
    JAVA_HOME = "${env.JAVA_HOME ?: 'E:/Program Files/Java/jdk-11'}"
  }

  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')) {
            allure commandline: 'allure', includeProperties: false, jdk: '', results: [[path: 'reports/latest/allure-results']]
          } else {
            echo '无 allure-results，跳过 Allure 插件报告。'
          }
        } catch (Throwable err) {
          echo "Allure 插件不可用，回退 HTML：${err}"
          try {
            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'
              ])
            }
          } catch (Throwable ignored) {
            echo "跳过 Allure HTML：${ignored}"
          }
        }
        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}"
        }
      }
    }
  }
}
