import process from 'node:process'

import type { AdminDefaultUser, RuntimeConfig } from '../types/runtime-config.js'

type RuntimeEnvironment = {
  NODE_ENV?: string
  CI?: string
}

type ValidationOptions = {
  env?: RuntimeEnvironment
}

export type RuntimeConfigValidationIssue = {
  path: string
  message: string
}

export class RuntimeConfigValidationError extends Error {
  issues: RuntimeConfigValidationIssue[]

  constructor(issues: RuntimeConfigValidationIssue[]) {
    super(formatRuntimeConfigValidationMessage(issues))
    this.name = 'RuntimeConfigValidationError'
    this.issues = issues
  }
}

export function assertRuntimeConfigValid(
  config: RuntimeConfig,
  options: ValidationOptions = {},
): void {
  const issues = validateRuntimeConfig(config, options)
  if (issues.length > 0) {
    throw new RuntimeConfigValidationError(issues)
  }
}

export function validateRuntimeConfig(
  config: RuntimeConfig,
  { env = process.env }: ValidationOptions = {},
): RuntimeConfigValidationIssue[] {
  const issues: RuntimeConfigValidationIssue[] = []
  const productionLike = isProductionLike(env)

  requireInteger(issues, 'server.port', config.server?.port, { min: 1, max: 65535 })
  const trustProxy = config.server?.trustProxy
  if (trustProxy !== undefined && trustProxy !== null && !isValidTrustProxy(trustProxy)) {
    issues.push({
      path: 'server.trustProxy',
      message: 'server.trustProxy 仅支持 true/false、非负整数或 loopback/uniquelocal/linklocal',
    })
  }
  requireInteger(issues, 'database.maxConnections', config.database?.maxConnections, { min: 1 })
  requireOptionalInteger(issues, 'logging.maxFileSizeMb', config.logging?.maxFileSizeMb, { min: 1 })
  requireOptionalInteger(issues, 'logging.maxFiles', config.logging?.maxFiles, { min: 2 })
  requireOptionalInteger(issues, 'database.idleTimeoutMs', config.database?.idleTimeoutMs, {
    min: 1,
  })
  requireOptionalInteger(
    issues,
    'database.connectionTimeoutMs',
    config.database?.connectionTimeoutMs,
    { min: 1 },
  )
  requireOptionalInteger(
    issues,
    'database.statementTimeoutMs',
    config.database?.statementTimeoutMs,
    {
      min: 1,
    },
  )
  requireOptionalInteger(
    issues,
    'database.slowQueryThresholdMs',
    config.database?.slowQueryThresholdMs,
    { min: 0 },
  )
  requireInteger(issues, 'storage.maxUploadSizeMb', config.storage?.maxUploadSizeMb, {
    min: 1,
    max: 100,
  })
  if (config.retention) {
    requireInteger(issues, 'retention.rawPayloadDays', config.retention.rawPayloadDays, { min: 1 })
    requireInteger(issues, 'retention.taskEventDays', config.retention.taskEventDays, { min: 1 })
    requireInteger(issues, 'retention.webhookEventDays', config.retention.webhookEventDays, {
      min: 1,
    })
    requireInteger(issues, 'retention.auditLogDays', config.retention.auditLogDays, { min: 1 })
  }
  const storageMode = normalizeStorageMode(config.storage?.mode)
  if (!storageMode) {
    issues.push({
      path: 'storage.mode',
      message: '仅支持 minio、dual 或 oss',
    })
  }
  requireInteger(issues, 'orders.tokenTtlHours', config.orders?.tokenTtlHours, { min: 0 })
  requireInteger(issues, 'admin.sessionTtlHours', config.admin?.sessionTtlHours, { min: 1 })
  requireInteger(
    issues,
    'platforms.cloudtentacles.timeoutMs',
    config.platforms?.cloudtentacles?.timeoutMs,
    { min: 1 },
  )
  if (config.platforms?.kuaishouFeifei) {
    requireInteger(
      issues,
      'platforms.kuaishouFeifei.timeoutMs',
      config.platforms.kuaishouFeifei.timeoutMs,
      { min: 1 },
    )
  }
  requireInteger(
    issues,
    'platforms.cloudtentacles.bindUrlTtlSeconds',
    config.platforms?.cloudtentacles?.bindUrlTtlSeconds,
    { min: 1 },
  )
  requireInteger(
    issues,
    'platforms.cloudtentacles.bindUrlProbeIntervalSeconds',
    config.platforms?.cloudtentacles?.bindUrlProbeIntervalSeconds,
    { min: 1 },
  )
  requireInteger(
    issues,
    'platforms.cloudtentacles.bindUrlProbeTimeoutMs',
    config.platforms?.cloudtentacles?.bindUrlProbeTimeoutMs,
    { min: 1 },
  )

  validateOptionalHttpUrl(issues, 'orders.claimBaseUrl', config.orders?.claimBaseUrl)
  validateOptionalStorageEndpoint(issues, 'storage.endpoint', config.storage?.endpoint)
  validateOptionalStorageEndpoint(issues, 'storage.oss.endpoint', config.storage?.oss?.endpoint)
  validateRequiredString(issues, 'data.root', config.data?.root)

  if (productionLike) {
    validateRequiredString(issues, 'database.url', config.database?.url)
    if (storageMode === 'minio' || storageMode === 'dual') {
      validateStorageCredentials(issues, 'storage', config.storage)
    }
    if (storageMode === 'oss' || storageMode === 'dual') {
      validateStorageCredentials(issues, 'storage.oss', config.storage?.oss)
    }
    validateRequiredHttpUrl(issues, 'orders.claimBaseUrl', config.orders?.claimBaseUrl)
    validateSecret(issues, 'admin.sessionSecret', config.admin?.sessionSecret, { minLength: 32 })
    validateAdminDefaultUsers(issues, config.admin?.defaultUsers)
    const allowedOrigins = config.cors?.allowedOrigins
    if (Array.isArray(allowedOrigins)) {
      if (allowedOrigins.length === 0 || allowedOrigins.includes('*')) {
        issues.push({
          path: 'cors.allowedOrigins',
          message: '生产环境必须配置显式 CORS 域名白名单，不能使用 *',
        })
      } else {
        allowedOrigins.forEach((origin, index) => {
          if (!isHttpUrl(String(origin || '').trim())) {
            issues.push({
              path: `cors.allowedOrigins[${index}]`,
              message: '必须是 http 或 https 来源 URL',
            })
          }
        })
      }
    }
  }

  return issues
}

function normalizeStorageMode(value: unknown): 'minio' | 'dual' | 'oss' | '' {
  const mode = String(value || 'minio')
    .trim()
    .toLowerCase()
  return mode === 'minio' || mode === 'dual' || mode === 'oss' ? mode : ''
}

function validateStorageCredentials(
  issues: RuntimeConfigValidationIssue[],
  configPath: string,
  storage:
    | {
        endpoint?: unknown
        bucket?: unknown
        accessKeyId?: unknown
        secretAccessKey?: unknown
      }
    | null
    | undefined,
): void {
  validateRequiredString(issues, `${configPath}.endpoint`, storage?.endpoint)
  validateOptionalStorageEndpoint(issues, `${configPath}.endpoint`, storage?.endpoint)
  validateRequiredString(issues, `${configPath}.bucket`, storage?.bucket)
  validateRequiredString(issues, `${configPath}.accessKeyId`, storage?.accessKeyId)
  validateSecret(issues, `${configPath}.secretAccessKey`, storage?.secretAccessKey, {
    minLength: 8,
  })
}

export function isProductionLike(env: RuntimeEnvironment = process.env): boolean {
  return env.NODE_ENV === 'production'
}

function validateAdminDefaultUsers(
  issues: RuntimeConfigValidationIssue[],
  users: AdminDefaultUser[] | undefined,
): void {
  if (!Array.isArray(users) || users.length === 0) {
    issues.push({
      path: 'admin.defaultUsers',
      message: '生产环境必须配置至少一个后台默认用户',
    })
    return
  }

  users.forEach((user, index) => {
    validateRequiredString(issues, `admin.defaultUsers[${index}].username`, user?.username)
    validateSecret(issues, `admin.defaultUsers[${index}].password`, user?.password, {
      minLength: 8,
    })
    validateRequiredString(issues, `admin.defaultUsers[${index}].role`, user?.role)
  })
}

function validateRequiredString(
  issues: RuntimeConfigValidationIssue[],
  configPath: string,
  value: unknown,
): void {
  if (!String(value || '').trim()) {
    issues.push({
      path: configPath,
      message: '必须配置非空字符串',
    })
  }
}

function validateSecret(
  issues: RuntimeConfigValidationIssue[],
  configPath: string,
  value: unknown,
  { minLength }: { minLength: number },
): void {
  const text = String(value || '').trim()
  if (!text) {
    issues.push({
      path: configPath,
      message: '必须配置密钥',
    })
    return
  }

  if (text.length < minLength) {
    issues.push({
      path: configPath,
      message: `长度不能少于 ${minLength} 个字符`,
    })
  }

  if (/change-me|example|password|secret/i.test(text)) {
    issues.push({
      path: configPath,
      message: '不能使用示例占位值',
    })
  }
}

function validateOptionalHttpUrl(
  issues: RuntimeConfigValidationIssue[],
  configPath: string,
  value: unknown,
): void {
  const text = String(value || '').trim()
  if (!text) {
    return
  }

  if (!isHttpUrl(text)) {
    issues.push({
      path: configPath,
      message: '必须是 http 或 https URL',
    })
  }
}

function validateRequiredHttpUrl(
  issues: RuntimeConfigValidationIssue[],
  configPath: string,
  value: unknown,
): void {
  validateRequiredString(issues, configPath, value)
  validateOptionalHttpUrl(issues, configPath, value)
}

function validateOptionalStorageEndpoint(
  issues: RuntimeConfigValidationIssue[],
  configPath: string,
  value: unknown,
): void {
  const text = String(value || '').trim()
  if (!text) {
    return
  }

  if (/^https?:\/\//i.test(text)) {
    validateOptionalHttpUrl(issues, configPath, text)
    return
  }

  if (!/^[a-z0-9.-]+(?::\d{1,5})?$/i.test(text)) {
    issues.push({
      path: configPath,
      message: '必须是 http/https URL 或 host:port',
    })
  }
}

function requireInteger(
  issues: RuntimeConfigValidationIssue[],
  configPath: string,
  value: unknown,
  { min, max }: { min?: number; max?: number } = {},
): void {
  const numberValue = Number(value)
  if (!Number.isInteger(numberValue)) {
    issues.push({
      path: configPath,
      message: '必须是整数',
    })
    return
  }

  if (typeof min === 'number' && numberValue < min) {
    issues.push({
      path: configPath,
      message: `不能小于 ${min}`,
    })
  }

  if (typeof max === 'number' && numberValue > max) {
    issues.push({
      path: configPath,
      message: `不能大于 ${max}`,
    })
  }
}

function requireOptionalInteger(
  issues: RuntimeConfigValidationIssue[],
  configPath: string,
  value: unknown,
  options: { min?: number; max?: number } = {},
): void {
  if (value === undefined || value === null || value === '') {
    return
  }
  requireInteger(issues, configPath, value, options)
}

function isValidTrustProxy(value: unknown): boolean {
  if (typeof value === 'boolean') return true
  if (typeof value === 'number') return Number.isInteger(value) && value >= 0
  return ['loopback', 'uniquelocal', 'linklocal'].includes(String(value))
}

function isHttpUrl(value: string): boolean {
  try {
    const url = new URL(value)
    return url.protocol === 'http:' || url.protocol === 'https:'
  } catch {
    return false
  }
}

function formatRuntimeConfigValidationMessage(issues: RuntimeConfigValidationIssue[]): string {
  const lines = issues.map((issue) => `- ${issue.path}: ${issue.message}`)
  return `运行时配置校验失败:\n${lines.join('\n')}`
}
