import { spawn } from 'node:child_process'
import { randomBytes, scryptSync, timingSafeEqual } from 'node:crypto'
import fs from 'node:fs'
import net from 'node:net'
import path from 'node:path'
import { fileURLToPath } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const rootDir = path.resolve(__dirname, '..')
const envFile = path.join(rootDir, '.env')
const envExampleFile = path.join(rootDir, '.env.example')
const preferPollingWatcher = fs.existsSync('/.dockerenv') || process.env.VITE_USE_POLLING === 'true'
const adminSecretDir = path.join(rootDir, 'server', '.secrets')
const adminSecretFile = path.join(adminSecretDir, 'admin.json')
const adminScryptOptions = { N: 16384, r: 8, p: 1 }
const adminScryptKeyLength = 64
let backendStartPromise = null
let backendStartState = createBackendStartState()
let backendStartAuthorized = false

function createBackendStartState() {
  return {
    running: false,
    completed: false,
    failed: false,
    detail: '',
    logTail: '',
    steps: [
      { id: 'config', label: '第一步：读取初始化配置', status: 'pending', detail: '等待配置写入完成。' },
      { id: 'deps', label: '第二步：安装/检查后端虚拟环境', status: 'pending', detail: '等待执行 server/server_start.sh deps。' },
      { id: 'server', label: '第三步：启动 FastAPI 服务', status: 'pending', detail: '等待启动 uvicorn。' },
      { id: 'health', label: '第四步：检测后端健康状态', status: 'pending', detail: '等待 /api/v1/health 返回正常。' },
      { id: 'done', label: '第五步：配置完成', status: 'pending', detail: '后端就绪后进入登录页。' }
    ]
  }
}

function cloneBackendStartState() {
  return {
    ...backendStartState,
    steps: backendStartState.steps.map((step) => ({ ...step }))
  }
}

function updateBackendStep(id, status, detail = '') {
  backendStartState.steps = backendStartState.steps.map((step) => {
    if (step.id !== id) {
      return step
    }

    return {
      ...step,
      status,
      detail: detail || step.detail
    }
  })
}

function ensureEnvFile() {
  if (fs.existsSync(envFile)) {
    return
  }

  if (fs.existsSync(envExampleFile)) {
    fs.copyFileSync(envExampleFile, envFile)
    return
  }

  fs.writeFileSync(envFile, '', 'utf8')
}

function ensureAdminSecretDir() {
  fs.mkdirSync(adminSecretDir, { recursive: true })
}

function parseEnv(text) {
  const result = {}

  for (const line of text.split(/\r?\n/u)) {
    const trimmed = line.trim()

    if (!trimmed || trimmed.startsWith('#')) {
      continue
    }

    const separatorIndex = trimmed.indexOf('=')

    if (separatorIndex === -1) {
      continue
    }

    const key = trimmed.slice(0, separatorIndex).trim()
    let value = trimmed.slice(separatorIndex + 1).trim()

    if (
      (value.startsWith('"') && value.endsWith('"')) ||
      (value.startsWith("'") && value.endsWith("'"))
    ) {
      value = value.slice(1, -1)
    }

    result[key] = value
  }

  return result
}

const envOverridePrefixes = ['APP_', 'WEB_', 'SERVER_', 'POSTGRES_', 'VITE_', 'LOG_']
const envOverrideKeys = new Set([
  'API_V1_PREFIX',
  'SETUP_COMPLETED',
  'COMPANY_NAME',
  'COMPANY_CODE',
  'ADMIN_EMAIL',
  'DATABASE_URL',
  'SQLALCHEMY_ECHO',
  'REDIS_URL',
  'CORS_ORIGINS'
])

function shouldOverlayEnvKey(key) {
  return envOverrideKeys.has(key) || envOverridePrefixes.some((prefix) => key.startsWith(prefix))
}

function readEnvState() {
  ensureEnvFile()
  const state = parseEnv(fs.readFileSync(envFile, 'utf8'))

  for (const [key, value] of Object.entries(process.env)) {
    if (!shouldOverlayEnvKey(key) || value == null || value === '') {
      continue
    }

    state[key] = String(value)
  }

  return state
}

function readAdminSecret() {
  if (!fs.existsSync(adminSecretFile)) {
    return null
  }

  try {
    const payload = JSON.parse(fs.readFileSync(adminSecretFile, 'utf8'))

    if (
      payload &&
      payload.algorithm === 'scrypt' &&
      typeof payload.username === 'string' &&
      typeof payload.salt === 'string' &&
      typeof payload.derived_key === 'string'
    ) {
      return payload
    }
  } catch {
    return null
  }

  return null
}

function hashAdminPassword(password, salt, keyLength = adminScryptKeyLength, options = adminScryptOptions) {
  return scryptSync(password, Buffer.from(salt, 'hex'), keyLength, options)
}

function persistAdminCredentials(payload) {
  ensureAdminSecretDir()

  const existing = readAdminSecret()
  const salt = randomBytes(16).toString('hex')
  const now = new Date().toISOString()
  const derivedKey = hashAdminPassword(String(payload.admin_password || ''), salt)
  const record = {
    version: 1,
    algorithm: 'scrypt',
    username: String(payload.admin_username || '').trim(),
    salt,
    derived_key: derivedKey.toString('hex'),
    key_length: adminScryptKeyLength,
    ...adminScryptOptions,
    created_at: existing?.created_at || now,
    updated_at: now
  }

  fs.writeFileSync(adminSecretFile, `${JSON.stringify(record, null, 2)}\n`, {
    encoding: 'utf8',
    mode: 0o600
  })
}

function verifyAdminCredentials(username, password) {
  const record = readAdminSecret()

  if (!record) {
    throw new Error('管理员账号尚未初始化，请先完成初始化配置。')
  }

  if (record.username !== String(username || '').trim()) {
    return false
  }

  const derivedKey = hashAdminPassword(
    String(password || ''),
    record.salt,
    Number(record.key_length || adminScryptKeyLength),
    {
      N: Number(record.N || adminScryptOptions.N),
      r: Number(record.r || adminScryptOptions.r),
      p: Number(record.p || adminScryptOptions.p)
    }
  )
  const storedKey = Buffer.from(record.derived_key, 'hex')

  if (storedKey.length !== derivedKey.length) {
    return false
  }

  return timingSafeEqual(storedKey, derivedKey)
}

function normalizeLoopbackHost(host) {
  const normalized = String(host || '').trim().toLowerCase()

  if (normalized === 'localhost' || normalized === '::1') {
    return '127.0.0.1'
  }

  if (normalized === '::') {
    return '0.0.0.0'
  }

  return normalized
}

function resolveClientHost(host) {
  const normalizedHost = normalizeLoopbackHost(host)

  if (!normalizedHost || normalizedHost === '0.0.0.0') {
    return '127.0.0.1'
  }

  return String(host || '').trim()
}

function resolveBrowserApiHost(serverHost, webHost) {
  const normalizedServerHost = normalizeLoopbackHost(serverHost)
  const normalizedWebHost = normalizeLoopbackHost(webHost)

  if (
    (normalizedServerHost === '0.0.0.0' || normalizedServerHost === '127.0.0.1') &&
    normalizedWebHost &&
    normalizedWebHost !== '0.0.0.0' &&
    normalizedWebHost !== '127.0.0.1'
  ) {
    return String(webHost || '').trim()
  }

  if (normalizedServerHost === '0.0.0.0') {
    return '127.0.0.1'
  }

  return String(serverHost || '').trim()
}

function hostsConflict(left, right) {
  const normalizedLeft = normalizeLoopbackHost(left)
  const normalizedRight = normalizeLoopbackHost(right)

  if (!normalizedLeft || !normalizedRight) {
    return false
  }

  if (normalizedLeft === normalizedRight) {
    return true
  }

  return normalizedLeft === '0.0.0.0' || normalizedRight === '0.0.0.0'
}

function serializeEnvValue(value) {
  const stringValue = value == null ? '' : String(value)

  if (stringValue === '') {
    return ''
  }

  if (/^[A-Za-z0-9_./:-]+$/u.test(stringValue)) {
    return stringValue
  }

  return `'${stringValue.replace(/'/gu, `'\\''`)}'`
}

function updateEnvFile(updates) {
  ensureEnvFile()

  let content = fs.readFileSync(envFile, 'utf8')
  const existingLines = content ? content.split(/\r?\n/u) : []
  const remainingKeys = new Set(Object.keys(updates))
  const nextLines = existingLines.map((line) => {
    const trimmed = line.trim()

    if (!trimmed || trimmed.startsWith('#')) {
      return line
    }

    const separatorIndex = line.indexOf('=')

    if (separatorIndex === -1) {
      return line
    }

    const key = line.slice(0, separatorIndex).trim()

    if (!remainingKeys.has(key)) {
      return line
    }

    remainingKeys.delete(key)
    return `${key}=${serializeEnvValue(updates[key])}`
  })

  for (const key of remainingKeys) {
    nextLines.push(`${key}=${serializeEnvValue(updates[key])}`)
  }

  content = `${nextLines.join('\n').replace(/\n+$/u, '')}\n`
  fs.writeFileSync(envFile, content, 'utf8')
}

function buildDatabaseUrl(payload) {
  const username = encodeURIComponent(payload.postgres_user)
  const password = encodeURIComponent(payload.postgres_password)
  return `postgresql+psycopg://${username}:${password}@${payload.postgres_host}:${payload.postgres_port}/${payload.postgres_db}`
}

function buildCorsOrigins(payload) {
  const webHost = String(payload.web_host || '').trim()
  const webPort = String(payload.web_port || '').trim()
  const origins = new Set()
  const normalizedHost = normalizeLoopbackHost(webHost)

  if (normalizedHost === '0.0.0.0') {
    origins.add(`http://127.0.0.1:${webPort}`)
    origins.add(`http://localhost:${webPort}`)
    origins.add(`http://0.0.0.0:${webPort}`)
  } else {
    origins.add(`http://${webHost}:${webPort}`)

    if (normalizedHost === '127.0.0.1') {
      origins.add(`http://127.0.0.1:${webPort}`)
      origins.add(`http://localhost:${webPort}`)
    }
  }

  return JSON.stringify([...origins])
}

function buildApiBaseUrl(payload, currentEnv) {
  const apiPrefix = currentEnv.API_V1_PREFIX || '/api/v1'
  return apiPrefix
}

function buildServerHealthUrl(env) {
  const apiPrefix = env.API_V1_PREFIX || '/api/v1'
  const host = resolveClientHost(env.SERVER_HOST || '127.0.0.1')
  const port = String(env.SERVER_PORT || 8000).trim()
  return `http://${host}:${port}${apiPrefix}/health`
}

function buildBrowserReachableServerHealthUrl(env) {
  const apiPrefix = env.API_V1_PREFIX || '/api/v1'
  const serverHost = String(env.SERVER_HOST || '127.0.0.1').trim()
  const webHost = String(env.WEB_HOST || '').trim()
  const host = resolveBrowserApiHost(serverHost, webHost)
  const port = String(env.SERVER_PORT || 8000).trim()
  return `http://${host}:${port}${apiPrefix}/health`
}

function buildClientEnvUpdates(payload, apiBaseUrl) {
  return {
    VITE_SETUP_COMPLETED: 'true',
    VITE_COMPANY_NAME: String(payload.company_name || '').trim(),
    VITE_COMPANY_CODE: String(payload.company_code || '').trim(),
    VITE_ADMIN_EMAIL: String(payload.admin_email || '').trim(),
    VITE_WEB_HOST: String(payload.web_host || '').trim(),
    VITE_WEB_PORT: String(payload.web_port || '').trim(),
    VITE_SERVER_HOST: String(payload.server_host || '').trim(),
    VITE_SERVER_PORT: String(payload.server_port || '').trim(),
    VITE_POSTGRES_HOST: String(payload.postgres_host || '').trim(),
    VITE_POSTGRES_PORT: String(payload.postgres_port || '').trim(),
    VITE_POSTGRES_DB: String(payload.postgres_db || '').trim(),
    VITE_POSTGRES_USER: String(payload.postgres_user || '').trim(),
    VITE_REDIS_URL: String(payload.redis_url || '').trim(),
    VITE_API_BASE_URL: apiBaseUrl
  }
}

export function isSetupCompletedState(env, adminConfigured) {
  return String(env.SETUP_COMPLETED || '').toLowerCase() === 'true' && adminConfigured
}

function isSetupCompleted() {
  return isSetupCompletedState(readEnvState(), Boolean(readAdminSecret()))
}

export function normalizeState(env, options = {}) {
  const adminConfigured = options.adminConfigured ?? Boolean(readAdminSecret())
  const initialized = isSetupCompletedState(env, adminConfigured)
  const redactInfrastructure = options.redactInfrastructure ?? initialized

  return {
    initialized,
    company: {
      name: env.COMPANY_NAME || '',
      code: env.COMPANY_CODE || '',
      admin_email: env.ADMIN_EMAIL || ''
    },
    admin: {
      configured: adminConfigured
    },
    web: {
      host: env.WEB_HOST || '0.0.0.0',
      port: Number(env.WEB_PORT || 5273)
    },
    server: {
      host: env.SERVER_HOST || '0.0.0.0',
      port: Number(env.SERVER_PORT || 8000)
    },
    database: {
      driver: 'postgresql',
      host: redactInfrastructure ? '' : env.POSTGRES_HOST || '127.0.0.1',
      port: Number(env.POSTGRES_PORT || 5432),
      name: env.POSTGRES_DB || 'x_financial',
      username: redactInfrastructure ? '' : env.POSTGRES_USER || 'postgres',
      password_configured: Boolean(env.POSTGRES_PASSWORD)
    },
    redis: {
      enabled: Boolean(env.REDIS_URL),
      url: redactInfrastructure ? '' : env.REDIS_URL || ''
    }
  }
}

function rejectCompletedSetup(res) {
  if (!isSetupCompleted()) {
    return false
  }
  sendJson(res, 403, { detail: '系统已完成初始化，本地初始化桥已锁定。' })
  return true
}

async function readJsonBody(req) {
  const chunks = []

  for await (const chunk of req) {
    chunks.push(chunk)
  }

  const raw = Buffer.concat(chunks).toString('utf8')
  return raw ? JSON.parse(raw) : {}
}

function sendJson(res, statusCode, payload) {
  res.statusCode = statusCode
  res.setHeader('Content-Type', 'application/json; charset=utf-8')
  res.end(JSON.stringify(payload))
}

function validateRuntimePayload(payload) {
  const fields = [
    ['server_host', 'Server Host']
  ]

  for (const [field, label] of fields) {
    if (!String(payload[field] ?? '').trim()) {
      return `请填写 ${label}。`
    }
  }

  const portFields = [
    ['server_port', 'Server Port']
  ]

  for (const [field, label] of portFields) {
    const value = Number(payload[field])

    if (!Number.isInteger(value) || value < 1 || value > 65535) {
      return `${label} 必须在 1 到 65535 之间。`
    }
  }

  return ''
}

function resolveRuntimePayload(payload, currentEnv) {
  const webHost = String(payload.web_host || currentEnv.WEB_HOST || '0.0.0.0').trim()
  const serverHost = String(payload.server_host || currentEnv.SERVER_HOST || '0.0.0.0').trim()
  const normalizedWebHost = normalizeLoopbackHost(webHost)
  const normalizedServerHost = normalizeLoopbackHost(serverHost)

  return {
    ...payload,
    web_host: webHost,
    web_port: Number(payload.web_port || currentEnv.WEB_PORT || 5273),
    server_host:
      normalizedWebHost &&
      normalizedWebHost !== '127.0.0.1' &&
      normalizedWebHost !== '0.0.0.0' &&
      normalizedServerHost === '127.0.0.1'
        ? '0.0.0.0'
        : serverHost
  }
}

function validateDatabasePayload(payload) {
  const fields = [
    ['postgres_host', 'PostgreSQL Host'],
    ['postgres_db', '数据库名称'],
    ['postgres_user', '数据库用户']
  ]

  for (const [field, label] of fields) {
    if (!String(payload[field] ?? '').trim()) {
      return `请填写 ${label}。`
    }
  }

  const port = Number(payload.postgres_port)

  if (!Number.isInteger(port) || port < 1 || port > 65535) {
    return 'PostgreSQL Port 必须在 1 到 65535 之间。'
  }

  if (!String(payload.postgres_password || '').length) {
    return '请填写数据库密码。'
  }

  return ''
}

function validateIdentityPayload(payload) {
  const companyName = String(payload.company_name || '').trim()
  const adminEmail = String(payload.admin_email || '').trim()
  const adminUsername = String(payload.admin_username || '').trim()
  const adminPassword = String(payload.admin_password || '')
  const adminPasswordConfirm = String(payload.admin_password_confirm || '')

  if (companyName.length < 2) {
    return '企业名称至少 2 个字符。'
  }

  if (!adminEmail) {
    return '请填写管理员邮箱。'
  }

  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/u.test(adminEmail)) {
    return '管理员邮箱格式不正确。'
  }

  if (adminUsername.length < 4) {
    return '管理员账号至少 4 位。'
  }

  if (!/^[A-Za-z0-9._@-]+$/u.test(adminUsername)) {
    return '管理员账号仅允许字母、数字、点、下划线、中划线和 @。'
  }

  if (adminPassword.length < 5) {
    return '管理员密码当前至少 5 位。'
  }

  if (adminPassword !== adminPasswordConfirm) {
    return '两次输入的管理员密码不一致。'
  }

  return ''
}

function validateSetupPayload(payload) {
  return validateIdentityPayload(payload) || validateRuntimePayload(payload) || validateDatabasePayload(payload)
}

async function assertPortAvailable(host, port) {
  await new Promise((resolve, reject) => {
    const tester = net.createServer()

    tester.once('error', (error) => {
      tester.close()
      reject(error)
    })

    tester.once('listening', () => {
      tester.close(() => resolve())
    })

    tester.listen(port, host)
  })
}

async function testRuntimePorts(payload) {
  const webPort = Number(payload.web_port)
  const serverPort = Number(payload.server_port)
  const webHost = String(payload.web_host || '').trim()
  const serverHost = String(payload.server_host || '').trim()

  if (webPort === serverPort && hostsConflict(webHost, serverHost)) {
    throw new Error('Web 与 Server 不能使用同一个主机与端口组合。')
  }

  try {
    await assertPortAvailable(serverHost, serverPort)
  } catch {
    throw new Error(`Server 端口 ${serverHost}:${serverPort} 已被占用。`)
  }
}

async function loadPgClient() {
  try {
    const module = await import('pg')
    return module.Client
  } catch {
    throw new Error('缺少 Node 侧 PostgreSQL 驱动 pg（web/node_modules/pg）。请先执行 bash start.sh，或进入 web 目录执行 npm install。')
  }
}

async function testDatabaseConnection(payload) {
  const Client = await loadPgClient()
  const requestedHost = String(payload.postgres_host || '').trim()
  const requestedHostNormalized = normalizeLoopbackHost(requestedHost)
  const dockerPostgresHost = String(process.env.POSTGRES_HOST || '').trim()
  const containerPostgresPort = Number(process.env.POSTGRES_PORT || 5432)
  const shouldUseDockerPostgres =
    dockerPostgresHost === 'postgres' &&
    ['127.0.0.1', 'localhost', '0.0.0.0', '::1', '::'].includes(requestedHostNormalized)
  const effectiveHost =
    shouldUseDockerPostgres ? 'postgres' : requestedHost
  const effectivePort =
    shouldUseDockerPostgres ? containerPostgresPort : Number(payload.postgres_port)

  const client = new Client({
    host: effectiveHost,
    port: effectivePort,
    database: String(payload.postgres_db || '').trim(),
    user: String(payload.postgres_user || '').trim(),
    password: String(payload.postgres_password || ''),
    connectionTimeoutMillis: 5000
  })

  try {
    await client.connect()
    await client.query('SELECT 1')
  } finally {
    await client.end().catch(() => {})
  }
}

async function probeBackendHealth(env) {
  const controller = new AbortController()
  const timeout = setTimeout(() => controller.abort(), 2000)

  try {
    const response = await fetch(buildServerHealthUrl(env), {
      signal: controller.signal
    })

    if (!response.ok) {
      return false
    }

    const payload = await response.json().catch(() => null)
    return payload?.status === 'ok'
  } catch {
    return false
  } finally {
    clearTimeout(timeout)
  }
}

async function probeBrowserReachableBackendHealth(env) {
  const controller = new AbortController()
  const timeout = setTimeout(() => controller.abort(), 2000)

  try {
    const response = await fetch(buildBrowserReachableServerHealthUrl(env), {
      signal: controller.signal
    })

    if (!response.ok) {
      return false
    }

    const payload = await response.json().catch(() => null)
    return payload?.status === 'ok'
  } catch {
    return false
  } finally {
    clearTimeout(timeout)
  }
}

async function waitForBackendReady(env) {
  const timeoutSeconds = Number(env.SERVER_STARTUP_TIMEOUT || 300)
  const maxAttempts = Number.isFinite(timeoutSeconds) && timeoutSeconds > 0 ? timeoutSeconds : 300
  let localOnlyAttempts = 0

  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
    const localReady = await probeBackendHealth(env)
    const browserReady = await probeBrowserReachableBackendHealth(env)

    if (localReady && browserReady) {
      return {
        ok: true,
        detail: 'FastAPI 后端已启动。'
      }
    }

    if (localReady && !browserReady) {
      localOnlyAttempts += 1

      if (localOnlyAttempts >= 5) {
        throw new Error(
          'FastAPI 仅在本机地址可用，浏览器访问地址不可达。通常是旧后端仍以 127.0.0.1 启动并占用端口，请停止旧后端后重新完成初始化。'
        )
      }
    } else {
      localOnlyAttempts = 0
    }

    await new Promise((resolve) => setTimeout(resolve, 1000))
  }

  throw new Error(`FastAPI 未在 ${maxAttempts}s 内完成启动，请查看 server/logs/bootstrap-backend.log。`)
}

function readBackendLogTail(logFile) {
  if (!fs.existsSync(logFile)) {
    return ''
  }

  const content = fs.readFileSync(logFile, 'utf8')
  const lines = content.trimEnd().split(/\r?\n/u)
  return lines.slice(-30).join('\n')
}

function completeBackendStartup(detail) {
  backendStartState.running = false
  backendStartState.completed = true
  backendStartState.failed = false
  backendStartState.detail = detail
  updateBackendStep('config', 'success', '初始化配置已写入。')
  updateBackendStep('deps', 'success', '后端依赖和虚拟环境检查完成。')
  updateBackendStep('server', 'success', 'FastAPI 进程已启动。')
  updateBackendStep('health', 'success', '健康检查通过。')
  updateBackendStep('done', 'success', '配置成功，准备进入登录页。')
}

function failBackendStartup(error, logFile) {
  backendStartState.running = false
  backendStartState.completed = false
  backendStartState.failed = true
  backendStartState.detail = error instanceof Error ? error.message : 'FastAPI 后端启动失败。'
  backendStartState.logTail = readBackendLogTail(logFile)
  updateBackendStep('done', 'error', backendStartState.detail)
}

async function startBackendAndWait() {
  const env = readEnvState()
  const logDir = path.join(rootDir, 'server', 'logs')
  const logFile = path.join(logDir, 'bootstrap-backend.log')

  if ((await probeBackendHealth(env)) && (await probeBrowserReachableBackendHealth(env))) {
    backendStartState = createBackendStartState()
    completeBackendStartup('FastAPI 后端已就绪。')
    backendStartState.logTail = readBackendLogTail(logFile)
    return cloneBackendStartState()
  }

  if (!backendStartPromise) {
    backendStartState = createBackendStartState()
    backendStartState.running = true
    backendStartState.detail = '正在启动 FastAPI 后端。'
    updateBackendStep('config', 'success', '初始化配置已写入。')
    updateBackendStep('deps', 'running', '正在创建/检查虚拟环境并安装依赖。')
    updateBackendStep('server', 'pending', '等待依赖检查完成后启动。')
    updateBackendStep('health', 'pending', '等待 FastAPI 响应。')

    backendStartPromise = (async () => {
      fs.mkdirSync(logDir, { recursive: true })

      const stdout = fs.openSync(logFile, 'a')
      const stderr = fs.openSync(logFile, 'a')
      const freshEnv = { ...process.env }
      const envFileContent = fs.readFileSync(envFile, 'utf-8')
      for (const line of envFileContent.split('\n')) {
        const trimmed = line.trim()
        if (!trimmed || trimmed.startsWith('#')) continue
        const eqIdx = trimmed.indexOf('=')
        if (eqIdx < 0) continue
        const key = trimmed.slice(0, eqIdx).trim()
        const val = trimmed.slice(eqIdx + 1).trim().replace(/^['"]|['"]$/g, '')
        freshEnv[key] = val
      }
      const child = spawn('bash', [path.join(rootDir, 'start.sh'), 'server'], {
        cwd: rootDir,
        detached: true,
        env: freshEnv,
        stdio: ['ignore', stdout, stderr]
      })

      child.unref()
      updateBackendStep('server', 'running', '后端启动命令已提交，等待 uvicorn 监听端口。')
      updateBackendStep('health', 'running', '正在轮询 /api/v1/health。')

      try {
        await waitForBackendReady(env)
        completeBackendStartup('FastAPI 后端已启动。')
      } catch (error) {
        failBackendStartup(error, logFile)
      } finally {
        backendStartState.logTail = readBackendLogTail(logFile)
      }

      return cloneBackendStartState()
    })().finally(() => {
      backendStartPromise = null
    })
  }

  backendStartState.logTail = readBackendLogTail(logFile)
  return cloneBackendStartState()
}

function localSetupPlugin() {
  return {
    name: 'local-setup-api',
    configureServer(server) {
      server.watcher.unwatch(envFile)
      server.watcher.unwatch(envExampleFile)
      server.watcher.unwatch(path.join(rootDir, 'server', 'logs'))
      server.watcher.unwatch(path.join(rootDir, 'server', 'storage'))
      server.watcher.unwatch(path.join(rootDir, 'test-results'))

      server.middlewares.use('/__setup/auth/login', async (req, res) => {
        try {
          if (rejectCompletedSetup(res)) {
            return
          }
          if (req.method !== 'POST') {
            sendJson(res, 405, { detail: 'Method not allowed' })
            return
          }

          const payload = await readJsonBody(req)
          const username = String(payload.username || '').trim()
          const password = String(payload.password || '')

          if (!username || !password) {
            sendJson(res, 400, { detail: '请输入管理员账号和密码。' })
            return
          }

          const passed = verifyAdminCredentials(username, password)

          if (!passed) {
            sendJson(res, 401, { detail: '管理员账号或密码错误。' })
            return
          }

          sendJson(res, 200, {
            ok: true,
            detail: '登录成功。',
            user: {
              username
            }
          })
        } catch (error) {
          sendJson(res, 500, {
            detail: error instanceof Error ? error.message : '管理员登录校验失败。'
          })
        }
      })

      server.middlewares.use('/__setup/bootstrap/runtime', async (req, res) => {
        try {
          if (rejectCompletedSetup(res)) {
            return
          }
          if (req.method !== 'PUT') {
            sendJson(res, 405, { detail: 'Method not allowed' })
            return
          }

          const payload = resolveRuntimePayload(await readJsonBody(req), readEnvState())
          const validationError = validateRuntimePayload(payload)

          if (validationError) {
            sendJson(res, 400, { detail: validationError })
            return
          }

          try {
            await testRuntimePorts(payload)
            sendJson(res, 200, { ok: true, detail: 'Server 端口占用检测通过。' })
          } catch (error) {
            sendJson(res, 400, {
              ok: false,
              detail: error instanceof Error ? error.message : '端口占用检测失败。'
            })
          }
        } catch (error) {
          sendJson(res, 500, {
            detail: error instanceof Error ? error.message : '运行端口检测服务异常。'
          })
        }
      })

      server.middlewares.use('/__setup/bootstrap/database', async (req, res) => {
        try {
          if (rejectCompletedSetup(res)) {
            return
          }
          if (req.method !== 'PUT') {
            sendJson(res, 405, { detail: 'Method not allowed' })
            return
          }

          const payload = await readJsonBody(req)
          const validationError = validateDatabasePayload(payload)

          if (validationError) {
            sendJson(res, 400, { detail: validationError })
            return
          }

          try {
            await testDatabaseConnection(payload)
            sendJson(res, 200, { ok: true, detail: '数据库连接检测通过。' })
          } catch (error) {
            sendJson(res, 400, {
              ok: false,
              detail: error instanceof Error ? error.message : '数据库连接检测失败。'
            })
          }
        } catch (error) {
          sendJson(res, 500, {
            detail: error instanceof Error ? error.message : '数据库检测服务异常。'
          })
        }
      })

      server.middlewares.use('/__setup/bootstrap/backend', async (req, res) => {
        try {
          if (req.method === 'GET') {
            const logFile = path.join(rootDir, 'server', 'logs', 'bootstrap-backend.log')
            backendStartState.logTail = isSetupCompleted() ? '' : readBackendLogTail(logFile)
            sendJson(res, 200, cloneBackendStartState())
            return
          }

          if (req.method !== 'POST') {
            sendJson(res, 405, { detail: 'Method not allowed' })
            return
          }

          if (isSetupCompleted() && !backendStartAuthorized) {
            sendJson(res, 403, { detail: '系统已完成初始化，后端启动桥已锁定。' })
            return
          }

          try {
            const result = await startBackendAndWait()
            if (result.completed) {
              backendStartAuthorized = false
            }
            sendJson(res, 200, result)
          } catch (error) {
            sendJson(res, 500, {
              ok: false,
              detail: error instanceof Error ? error.message : 'FastAPI 后端启动失败。'
            })
          }
        } catch (error) {
          sendJson(res, 500, {
            detail: error instanceof Error ? error.message : '后端启动桥接服务异常。'
          })
        }
      })

      server.middlewares.use('/__setup/bootstrap', async (req, res) => {
        try {
          if (req.method === 'GET') {
            sendJson(res, 200, normalizeState(readEnvState()))
            return
          }

          if (req.method !== 'POST') {
            sendJson(res, 405, { detail: 'Method not allowed' })
            return
          }

          if (rejectCompletedSetup(res)) {
            return
          }

          const currentEnv = readEnvState()
          const payload = resolveRuntimePayload(await readJsonBody(req), currentEnv)
          const validationError = validateSetupPayload(payload)

          if (validationError) {
            sendJson(res, 400, { detail: validationError })
            return
          }

          try {
            await testRuntimePorts(payload)
            await testDatabaseConnection(payload)
          } catch (error) {
            sendJson(res, 400, {
              detail: error instanceof Error ? error.message : '初始化校验失败。'
            })
            return
          }

          persistAdminCredentials(payload)

          const apiBaseUrl = buildApiBaseUrl(payload, currentEnv)

          updateEnvFile({
            SETUP_COMPLETED: 'true',
            COMPANY_NAME: String(payload.company_name || '').trim(),
            COMPANY_CODE: String(payload.company_code || '').trim(),
            ADMIN_EMAIL: String(payload.admin_email || '').trim(),
            WEB_HOST: String(payload.web_host || '').trim(),
            WEB_PORT: String(payload.web_port || '').trim(),
            SERVER_HOST: String(payload.server_host || '').trim(),
            SERVER_PORT: String(payload.server_port || '').trim(),
            POSTGRES_HOST: String(payload.postgres_host || '').trim(),
            POSTGRES_PORT: String(payload.postgres_port || '').trim(),
            POSTGRES_DB: String(payload.postgres_db || '').trim(),
            POSTGRES_USER: String(payload.postgres_user || '').trim(),
            POSTGRES_PASSWORD: String(payload.postgres_password || ''),
            DATABASE_URL: buildDatabaseUrl(payload),
            REDIS_URL: String(payload.redis_url || '').trim(),
            CORS_ORIGINS: buildCorsOrigins(payload),
            VITE_API_BASE_URL: apiBaseUrl,
            ...buildClientEnvUpdates(payload, apiBaseUrl)
          })

          backendStartAuthorized = true
          sendJson(res, 201, normalizeState(readEnvState()))
        } catch (error) {
          sendJson(res, 500, {
            detail: error instanceof Error ? error.message : '初始化写入失败。'
          })
        }
      })
    }
  }
}

export default defineConfig({
  envDir: '..',
  server: {
    allowedHosts: ['www.caoxiaozhu.com', 'caoxiaozhu.com'],
    watch: {
      ...(preferPollingWatcher
        ? {
            // Docker bind mounts can miss fs events for Vue SFCs, which leaves Vite serving stale templates.
            usePolling: true,
            interval: 1000
          }
        : {}),
      ignored: [
        envFile,
        envExampleFile,
        path.join(rootDir, 'server', 'logs', '**'),
        path.join(rootDir, 'server', 'storage', '**'),
        path.join(rootDir, 'test-results', '**'),
        path.join(rootDir, '.codex-remote-attachments', '**')
      ]
    },
    proxy: {
      '/api': {
        target: `http://127.0.0.1:${process.env.SERVER_PORT || 8000}`,
        changeOrigin: true
      }
    }
  },
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          if (!id.includes('node_modules')) {
            return undefined
          }
          const normalizedId = id.replace(/\\/g, '/')
          if (
            normalizedId.includes('/node_modules/vue/') ||
            normalizedId.includes('/node_modules/@vue/') ||
            normalizedId.includes('/node_modules/vue-router/')
          ) {
            return 'vendor-vue'
          }
          if (normalizedId.includes('element-plus') || normalizedId.includes('@element-plus')) {
            return 'vendor-element-plus'
          }
          if (normalizedId.includes('echarts') || normalizedId.includes('zrender')) {
            return 'vendor-echarts'
          }
          if (normalizedId.includes('@antv/g6')) {
            return 'vendor-g6'
          }
          if (normalizedId.includes('markdown-it')) {
            return 'vendor-markdown'
          }
          if (normalizedId.includes('@vueuse')) {
            return 'vendor-vueuse'
          }
          return 'vendor'
        }
      }
    }
  },
  plugins: [vue(), localSetupPlugin()]
})
