import path from 'node:path'
import process from 'node:process'

import type {
  AdminDefaultUser,
  KuaishouFeifeiProductRule,
  RuntimeConfig,
} from '../types/runtime-config.js'
import { parseBoolean, parseInteger, parseJsonArray } from './runtime-env.js'
import { deepMerge } from './runtime-merge.js'

type RuntimeConfigPath = readonly [string, ...string[]]
type RuntimeConfigValue =
  | string
  | number
  | boolean
  | AdminDefaultUser[]
  | KuaishouFeifeiProductRule[]
  | string[]
  | Record<string, string>
type RuntimeEnv = Record<string, string | undefined>

type EnvOverride = {
  env: string
  path: RuntimeConfigPath
  read: (rawValue: unknown) => RuntimeConfigValue | null
}

export const ENV_OVERRIDES: readonly EnvOverride[] = [
  integerEnv('PORT', ['server', 'port']),
  trustProxyEnv('HTTP_TRUST_PROXY', ['server', 'trustProxy']),
  stringEnv('DATA_ROOT', ['data', 'root'], path.resolve),
  stringEnv('LOG_LEVEL', ['logging', 'level']),
  stringEnv('LOG_INTEGRATION_LEVEL', ['logging', 'integrationLevel']),
  integerEnv('LOG_RETENTION_DAYS', ['logging', 'retentionDays']),
  integerEnv('LOG_MAX_FILE_SIZE_MB', ['logging', 'maxFileSizeMb']),
  integerEnv('LOG_MAX_FILES', ['logging', 'maxFiles']),
  integerEnv('RAW_PAYLOAD_RETENTION_DAYS', ['retention', 'rawPayloadDays']),
  integerEnv('TASK_EVENT_RETENTION_DAYS', ['retention', 'taskEventDays']),
  integerEnv('WEBHOOK_EVENT_RETENTION_DAYS', ['retention', 'webhookEventDays']),
  integerEnv('AUDIT_LOG_RETENTION_DAYS', ['retention', 'auditLogDays']),
  stringEnv('DATABASE_URL', ['database', 'url']),
  booleanEnv('DATABASE_SSL', ['database', 'ssl']),
  integerEnv('DATABASE_MAX_CONNECTIONS', ['database', 'maxConnections']),
  integerEnv('DATABASE_IDLE_TIMEOUT_MS', ['database', 'idleTimeoutMs']),
  integerEnv('DATABASE_CONNECTION_TIMEOUT_MS', ['database', 'connectionTimeoutMs']),
  integerEnv('DATABASE_STATEMENT_TIMEOUT_MS', ['database', 'statementTimeoutMs']),
  integerEnv('DATABASE_SLOW_QUERY_THRESHOLD_MS', ['database', 'slowQueryThresholdMs']),
  stringEnv('STORAGE_MODE', ['storage', 'mode']),
  stringEnv('STORAGE_ENDPOINT', ['storage', 'endpoint']),
  stringEnv('STORAGE_BUCKET', ['storage', 'bucket']),
  stringEnv('STORAGE_ACCESS_KEY_ID', ['storage', 'accessKeyId']),
  stringEnv('STORAGE_SECRET_ACCESS_KEY', ['storage', 'secretAccessKey']),
  stringEnv('STORAGE_REGION', ['storage', 'region']),
  integerEnv('STORAGE_MAX_UPLOAD_SIZE_MB', ['storage', 'maxUploadSizeMb']),
  stringEnv('STORAGE_OSS_ENDPOINT', ['storage', 'oss', 'endpoint']),
  stringEnv('STORAGE_OSS_BUCKET', ['storage', 'oss', 'bucket']),
  stringEnv('STORAGE_OSS_ACCESS_KEY_ID', ['storage', 'oss', 'accessKeyId']),
  stringEnv('STORAGE_OSS_SECRET_ACCESS_KEY', ['storage', 'oss', 'secretAccessKey']),
  stringEnv('STORAGE_OSS_REGION', ['storage', 'oss', 'region']),
  integerEnv('CLAIM_TOKEN_TTL_HOURS', ['orders', 'tokenTtlHours']),
  stringEnv('ADMIN_SESSION_SECRET', ['admin', 'sessionSecret']),
  integerEnv('ADMIN_SESSION_TTL_HOURS', ['admin', 'sessionTtlHours']),
  adminUsersJsonEnv('ADMIN_DEFAULT_USERS_JSON', ['admin', 'defaultUsers']),
  stringEnv('KAQUAN91_USER_ID', ['platforms', 'ninetyone', 'userId']),
  stringEnv('KAQUAN91_SECRET', ['platforms', 'ninetyone', 'secret']),
  stringEnv('KAQUAN91_VERSION', ['platforms', 'ninetyone', 'version']),
  stringEnv('KAQUAN91_SHOP_ID', ['platforms', 'ninetyone', 'shopId']),
  stringEnv('KAQUAN91_SHOP_NAME', ['platforms', 'ninetyone', 'shopName']),
  integerEnv('KAQUAN91_TIMESTAMP_TOLERANCE_SECONDS', [
    'platforms',
    'ninetyone',
    'timestampToleranceSeconds',
  ]),
  stringEnv('KAQUAN91_CARDS_ENCODING', ['platforms', 'ninetyone', 'cardsEncoding']),
  stringEnv('CLOUDTENTACLES_BASE_URL', ['platforms', 'cloudtentacles', 'baseUrl']),
  integerEnv('CLOUDTENTACLES_TIMEOUT_MS', ['platforms', 'cloudtentacles', 'timeoutMs']),
  stringEnv('CLOUDTENTACLES_SEND_SMS_PATH', ['platforms', 'cloudtentacles', 'sendSmsPath']),
  stringEnv('CLOUDTENTACLES_LOGIN_PATH', ['platforms', 'cloudtentacles', 'loginPath']),
  stringEnv('CLOUDTENTACLES_USER_INFO_PATH', ['platforms', 'cloudtentacles', 'userInfoPath']),
  stringEnv('CLOUDTENTACLES_ASSET_PATH', ['platforms', 'cloudtentacles', 'assetPath']),
  stringEnv('CLOUDTENTACLES_PERMISSION_PATH', ['platforms', 'cloudtentacles', 'permissionPath']),
  stringEnv('CLOUDTENTACLES_CATEGORIES_PATH', ['platforms', 'cloudtentacles', 'categoriesPath']),
  stringEnv('CLOUDTENTACLES_SKU_LIST_PATH', ['platforms', 'cloudtentacles', 'skuListPath']),
  stringEnv('CLOUDTENTACLES_SKU_BUY_PATH', ['platforms', 'cloudtentacles', 'skuBuyPath']),
  stringEnv('CLOUDTENTACLES_KNAPSACK_PATH', ['platforms', 'cloudtentacles', 'knapsackPath']),
  stringEnv('CLOUDTENTACLES_VN_LIST_PATH', ['platforms', 'cloudtentacles', 'vnListPath']),
  stringEnv('CLOUDTENTACLES_VN_APPOINT_PATH', ['platforms', 'cloudtentacles', 'vnAppointPath']),
  stringEnv('CLOUDTENTACLES_VN_GENERATE_LOGIN_CODE_PATH', [
    'platforms',
    'cloudtentacles',
    'vnGenerateLoginCodePath',
  ]),
  stringEnv('CLOUDTENTACLES_VN_VERIF_CODE_PATH', [
    'platforms',
    'cloudtentacles',
    'vnVerifCodePath',
  ]),
  stringEnv('CLOUDTENTACLES_VN_VERIFY_LOGIN_CODE_PATH', [
    'platforms',
    'cloudtentacles',
    'vnVerifyLoginCodePath',
  ]),
  stringEnv('CLOUDTENTACLES_VN_BIND_URL_PATH', ['platforms', 'cloudtentacles', 'vnBindUrlPath']),
  stringEnv('CLOUDTENTACLES_VN_BIND_INFO_PATH', ['platforms', 'cloudtentacles', 'vnBindInfoPath']),
  stringEnv('CLOUDTENTACLES_VN_BACK_PATH', ['platforms', 'cloudtentacles', 'vnBackPath']),
  integerEnv('CLOUDTENTACLES_BIND_URL_TTL_SECONDS', [
    'platforms',
    'cloudtentacles',
    'bindUrlTtlSeconds',
  ]),
  integerEnv('CLOUDTENTACLES_BIND_URL_PROBE_INTERVAL_SECONDS', [
    'platforms',
    'cloudtentacles',
    'bindUrlProbeIntervalSeconds',
  ]),
  integerEnv('CLOUDTENTACLES_BIND_URL_PROBE_TIMEOUT_MS', [
    'platforms',
    'cloudtentacles',
    'bindUrlProbeTimeoutMs',
  ]),
  stringEnv('CLOUDTENTACLES_BIND_URL_PROBE_USER_AGENT', [
    'platforms',
    'cloudtentacles',
    'bindUrlProbeUserAgent',
  ]),
  stringEnv('CLOUDTENTACLES_BIND_URL_PROBE_ENDPOINT', [
    'platforms',
    'cloudtentacles',
    'bindUrlProbeEndpoint',
  ]),
  stringEnv('CLOUDTENTACLES_BIND_URL_PROBE_CHART_ID', [
    'platforms',
    'cloudtentacles',
    'bindUrlProbeChartId',
  ]),
  stringEnv('CLOUDTENTACLES_BIND_URL_PROBE_SUB_CHART_ID', [
    'platforms',
    'cloudtentacles',
    'bindUrlProbeSubChartId',
  ]),
  stringEnv('CLOUDTENTACLES_BIND_URL_PROBE_IDE_TOKEN', [
    'platforms',
    'cloudtentacles',
    'bindUrlProbeIdeToken',
  ]),
  stringEnv('CLOUDTENTACLES_BIND_URL_PROBE_ACTIVITY_URL', [
    'platforms',
    'cloudtentacles',
    'bindUrlProbeActivityUrl',
  ]),
  stringEnv('CLOUDTENTACLES_BIND_URL_PROBE_REFERER', [
    'platforms',
    'cloudtentacles',
    'bindUrlProbeReferer',
  ]),
  stringEnv('CLOUDTENTACLES_BIND_URL_PROBE_EXTRA_COOKIE', [
    'platforms',
    'cloudtentacles',
    'bindUrlProbeExtraCookie',
  ]),
  stringEnv('CLOUDTENTACLES_PUBLIC_KEY_PEM', ['platforms', 'cloudtentacles', 'publicKeyPem']),
  stringEnv('CLOUDTENTACLES_CLIENT_SOURCE', ['platforms', 'cloudtentacles', 'clientSource']),
  stringEnv('CLOUDTENTACLES_DEVICE_ID', ['platforms', 'cloudtentacles', 'deviceId']),
  integerEnv('CLOUDTENTACLES_DEVICE_TYPE', ['platforms', 'cloudtentacles', 'deviceType']),
  stringEnv('KUASHOU_INDUSTRY_APP_KEY', ['platforms', 'kuaishouIndustry', 'appKey']),
  stringEnv('KUASHOU_INDUSTRY_APP_SECRET', ['platforms', 'kuaishouIndustry', 'appSecret']),
  stringEnv('KUASHOU_INDUSTRY_SIGN_SECRET', ['platforms', 'kuaishouIndustry', 'signSecret']),
  stringEnv('KUASHOU_INDUSTRY_MESSAGE_SECRET', ['platforms', 'kuaishouIndustry', 'messageSecret']),
  stringEnv('KUASHOU_INDUSTRY_PROVIDER', ['platforms', 'kuaishouIndustry', 'provider']),
  stringEnv('KUASHOU_INDUSTRY_SHOP_ID', ['platforms', 'kuaishouIndustry', 'shopId']),
  stringEnv('KUASHOU_INDUSTRY_SHOP_NAME', ['platforms', 'kuaishouIndustry', 'shopName']),
  stringEnv('KUASHOU_INDUSTRY_VERSION', ['platforms', 'kuaishouIndustry', 'version']),
  stringEnv('KUAISHOU_FEIFEI_BASE_URL', ['platforms', 'kuaishouFeifei', 'baseUrl']),
  stringEnv('KUAISHOU_FEIFEI_APP_KEY', ['platforms', 'kuaishouFeifei', 'appKey']),
  stringEnv('KUAISHOU_FEIFEI_APP_SECRET', ['platforms', 'kuaishouFeifei', 'appSecret']),
  stringEnv('WORKER_SMS_PROVIDER', ['worker', 'sms', 'provider']),
  stringEnv('WORKER_SMS_ACCESS_KEY_ID', ['worker', 'sms', 'accessKeyId']),
  stringEnv('WORKER_SMS_ACCESS_KEY_SECRET', ['worker', 'sms', 'accessKeySecret']),
  stringEnv('WORKER_SMS_SIGN_NAME', ['worker', 'sms', 'signName']),
  stringEnv('WORKER_SMS_TEMPLATE_CODE', ['worker', 'sms', 'templateCode']),
  integerEnv('WORKER_SMS_CODE_TTL_SECONDS', ['worker', 'sms', 'codeTtlSeconds']),
  integerEnv('WORKER_SMS_COOLDOWN_SECONDS', ['worker', 'sms', 'cooldownSeconds']),
  integerEnv('WORKER_SMS_DAILY_LIMIT', ['worker', 'sms', 'dailyLimit']),
  booleanEnv('WORKER_ORDER_TIMELINE_VISIBLE', ['worker', 'orderTimelineVisible']),
  integerEnv('KUAISHOU_FEIFEI_TIMEOUT_MS', ['platforms', 'kuaishouFeifei', 'timeoutMs']),
  stringEnv('KUAISHOU_FEIFEI_NOTIFY_URL', ['platforms', 'kuaishouFeifei', 'notifyUrl']),
  kuaishouFeifeiProductRulesEnv('KUAISHOU_FEIFEI_PRODUCT_RULES_JSON', [
    'platforms',
    'kuaishouFeifei',
    'productRules',
  ]),
  stringEnv('AFFILIATE_DASH_BASE_URL', ['platforms', 'affiliateDash', 'baseUrl']),
  stringEnv('AFFILIATE_DASH_APP_KEY', ['platforms', 'affiliateDash', 'appKey']),
  stringEnv('AFFILIATE_DASH_APP_SECRET', ['platforms', 'affiliateDash', 'appSecret']),
  stringEnv('AFFILIATE_DASH_CALLBACK_SECRET', ['platforms', 'affiliateDash', 'callbackSecret']),
  integerEnv('AFFILIATE_DASH_TIMEOUT_MS', ['platforms', 'affiliateDash', 'timeoutMs']),
  stringEnv('AFFILIATE_DASH_NOTIFY_URL', ['platforms', 'affiliateDash', 'notifyUrl']),
  integerEnv('AFFILIATE_DASH_TIMESTAMP_TOLERANCE_SECONDS', [
    'platforms',
    'affiliateDash',
    'timestampToleranceSeconds',
  ]),
  affiliateDashSkuMappingEnv('AFFILIATE_DASH_SKU_MAPPING_JSON', [
    'platforms',
    'affiliateDash',
    'skuMapping',
  ]),
  corsOriginsEnv('CORS_ALLOWED_ORIGINS', ['cors', 'allowedOrigins']),
]

export function applyEnvOverrides(
  baseConfig: RuntimeConfig,
  env: RuntimeEnv = process.env,
): RuntimeConfig {
  const nextConfig = deepMerge(baseConfig, {})

  applyClaimBaseUrlOverride(nextConfig, env)

  for (const override of ENV_OVERRIDES) {
    const value = override.read(env[override.env])
    if (value !== null) {
      setConfigValue(nextConfig, override.path, value)
    }
  }

  return nextConfig
}

function applyClaimBaseUrlOverride(config: RuntimeConfig, env: RuntimeEnv): void {
  const claimBaseUrl = parseString(env.CLAIM_BASE_URL)
  if (claimBaseUrl !== null) {
    config.orders.claimBaseUrl = claimBaseUrl
    return
  }

  const appBaseUrl = parseString(env.APP_BASE_URL)
  if (appBaseUrl !== null) {
    config.orders.claimBaseUrl = `${appBaseUrl.replace(/\/+$/, '')}/#/claim`
  }
}

function stringEnv(
  env: string,
  configPath: RuntimeConfigPath,
  transform: (value: string) => string = (value) => value,
): EnvOverride {
  return {
    env,
    path: configPath,
    read(rawValue) {
      const value = parseString(rawValue)
      return value === null ? null : transform(value)
    },
  }
}

function integerEnv(env: string, configPath: RuntimeConfigPath): EnvOverride {
  return {
    env,
    path: configPath,
    read: parseInteger,
  }
}

function booleanEnv(env: string, configPath: RuntimeConfigPath): EnvOverride {
  return {
    env,
    path: configPath,
    read: parseBoolean,
  }
}

function trustProxyEnv(env: string, configPath: RuntimeConfigPath): EnvOverride {
  return {
    env,
    path: configPath,
    read: parseTrustProxy,
  }
}

/** HTTP_TRUST_PROXY 支持 true/false、反代层数（数字）与 loopback 等预定义子网，其余值回落到默认。 */
function parseTrustProxy(rawValue: unknown): RuntimeConfigValue | null {
  const value = parseString(rawValue)
  if (value === null) return null
  const normalized = value.trim().toLowerCase()
  if (normalized === 'true' || normalized === 'yes') return true
  if (normalized === 'false' || normalized === 'no') return false
  if (normalized === 'loopback' || normalized === 'uniquelocal' || normalized === 'linklocal') {
    return normalized
  }
  if (/^\d+$/.test(normalized)) {
    const hops = Number(normalized)
    return hops >= 0 ? hops : null
  }
  return null
}

function adminUsersJsonEnv(env: string, configPath: RuntimeConfigPath): EnvOverride {
  return {
    env,
    path: configPath,
    read(rawValue) {
      return parseJsonArray<AdminDefaultUser>(rawValue)
    },
  }
}

function kuaishouFeifeiProductRulesEnv(env: string, configPath: RuntimeConfigPath): EnvOverride {
  return {
    env,
    path: configPath,
    read(rawValue) {
      const parsed = parseJsonArray<KuaishouFeifeiProductRule>(rawValue)
      return parsed === null ? null : parsed
    },
  }
}

function affiliateDashSkuMappingEnv(env: string, configPath: RuntimeConfigPath): EnvOverride {
  return {
    env,
    path: configPath,
    read(rawValue) {
      const text = String(rawValue ?? '').trim()
      if (!text) {
        return null
      }
      try {
        const parsed = JSON.parse(text)
        if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
          return parsed as Record<string, string>
        }
      } catch {
        // 忽略非法 JSON
      }
      return null
    },
  }
}

function corsOriginsEnv(env: string, configPath: RuntimeConfigPath): EnvOverride {
  return {
    env,
    path: configPath,
    read(rawValue) {
      const value = parseString(rawValue)
      if (value === null) {
        return null
      }
      if (value.trim() === '*') {
        return ['*']
      }
      return value
        .split(',')
        .map((s) => s.trim())
        .filter(Boolean)
    },
  }
}

function parseString(rawValue: unknown): string | null {
  const value = String(rawValue || '').trim()
  return value ? value : null
}

function setConfigValue(
  config: RuntimeConfig,
  configPath: RuntimeConfigPath,
  value: RuntimeConfigValue,
): void {
  let current: Record<string, unknown> = config as unknown as Record<string, unknown>

  for (const key of configPath.slice(0, -1)) {
    const nextValue = current[key]
    if (!nextValue || typeof nextValue !== 'object' || Array.isArray(nextValue)) {
      current[key] = {}
    }
    current = current[key] as Record<string, unknown>
  }

  const lastKey = configPath[configPath.length - 1]
  if (!lastKey) {
    return
  }

  current[lastKey] = value
}
