import type { Request, Response } from 'express'
import { logDebug, logError, logWarn } from './logger.js'

const RESPONSE_TIME_ZONE = 'Asia/Shanghai'

export type HttpErrorLike = {
  statusCode?: number
  errorCode?: string
  cause?: unknown
  context?: unknown
  [key: string]: unknown
}

type CreateHttpErrorOptions = {
  statusCode?: number
  errorCode?: string
  cause?: unknown
  context?: unknown
}

export function buildSuccessPayload(data: unknown, msg: string = 'ok') {
  return {
    code: 0,
    msg,
    time: Math.floor(Date.now() / 1000),
    data: normalizeResponseDateTime(data),
  }
}

export function buildErrorPayload(error: unknown, fallbackMessage: string) {
  const classification = classifyRouteError(error)
  const message =
    classification.statusCode >= 500
      ? fallbackMessage || '服务内部错误'
      : error instanceof Error
        ? error.message
        : fallbackMessage

  return {
    code: 1,
    msg: message,
    errorCode: classification.errorCode,
    time: Math.floor(Date.now() / 1000),
    data: null as null,
  }
}

export function sendRouteError(
  res: Response,
  error: unknown,
  fallbackMessage = '服务内部错误',
  scope = '[route]',
): void {
  const classification = classifyRouteError(error)
  const logLevel = resolveRouteErrorLogLevel(classification)
  const logger = logLevel === 'error' ? logError : logLevel === 'debug' ? logDebug : logWarn
  logger(scope, fallbackMessage, {
    statusCode: classification.statusCode,
    errorCode: classification.errorCode,
    // 4xx 属预期失败（鉴权过期、参数错误等），带上完整错误对象只会刷十几行堆栈；仅 5xx 保留排查所需详情。
    ...(classification.statusCode >= 500 ? { error } : {}),
  })
  res.status(classification.statusCode).json(buildErrorPayload(error, fallbackMessage))
}

export function resolveRouteErrorLogLevel(classification: {
  statusCode: number
  errorCode: string
}): 'debug' | 'warn' | 'error' {
  if (classification.statusCode >= 500) return 'error'
  if (classification.errorCode === 'worker_auth_expired') return 'debug'
  return 'warn'
}

export function buildNotFoundPayload(req: Request) {
  return {
    code: 1,
    msg: `未实现接口: ${req.method} ${req.originalUrl}`,
    time: Math.floor(Date.now() / 1000),
    data: null as null,
  }
}

export function createHttpError(
  message: string,
  options: CreateHttpErrorOptions = {},
): Error & HttpErrorLike {
  const { statusCode = 500, errorCode = '', cause, context } = options
  const error = new Error(message) as Error & HttpErrorLike
  error.statusCode = statusCode
  error.errorCode = errorCode || defaultErrorCodeForStatus(statusCode)

  if (typeof cause !== 'undefined') {
    error.cause = cause
  }

  if (typeof context !== 'undefined') {
    error.context = context
  }

  return error
}

function classifyRouteError(error: unknown): {
  statusCode: number
  errorCode: string
} {
  if (error && typeof error === 'object') {
    const currentError = error as HttpErrorLike
    const statusCode = Number(currentError.statusCode)
    const errorCode = String(currentError.errorCode || '').trim()

    if (Number.isInteger(statusCode) && statusCode >= 400 && statusCode < 600) {
      return {
        statusCode,
        errorCode: errorCode || defaultErrorCodeForStatus(statusCode),
      }
    }
  }

  const message = error instanceof Error ? error.message : String(error || '')

  if (message.includes('缺少') || message.includes('无效') || message.includes('验签失败')) {
    return { statusCode: 400, errorCode: 'invalid_request' }
  }

  if (message.includes('不存在')) {
    return { statusCode: 404, errorCode: 'not_found' }
  }

  if (message.includes('已关闭')) {
    return { statusCode: 410, errorCode: 'gone' }
  }

  if (message.includes('不能') || message.includes('冲突')) {
    return { statusCode: 409, errorCode: 'conflict' }
  }

  return { statusCode: 500, errorCode: 'internal_error' }
}

function defaultErrorCodeForStatus(statusCode: number): string {
  if (statusCode === 400) {
    return 'invalid_request'
  }

  if (statusCode === 401) {
    return 'unauthorized'
  }

  if (statusCode === 403) {
    return 'forbidden'
  }

  if (statusCode === 404) {
    return 'not_found'
  }

  if (statusCode === 409) {
    return 'conflict'
  }

  if (statusCode === 410) {
    return 'gone'
  }

  if (statusCode === 503) {
    return 'dependency_unavailable'
  }

  return 'internal_error'
}

function normalizeResponseDateTime(value: unknown): unknown {
  if (value instanceof Date) {
    return formatResponseDateTime(value.toISOString())
  }

  if (Array.isArray(value)) {
    return value.map((item) => normalizeResponseDateTime(item))
  }

  if (value && typeof value === 'object') {
    return Object.fromEntries(
      Object.entries(value).map(([key, currentValue]) => [
        key,
        normalizeResponseDateTime(currentValue),
      ]),
    )
  }

  if (isIsoDateTimeString(value)) {
    return formatResponseDateTime(value as string)
  }

  return value
}

function isIsoDateTimeString(value: unknown): boolean {
  return (
    typeof value === 'string' &&
    /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$/.test(value.trim())
  )
}

function formatResponseDateTime(value: string): string {
  const date = new Date(value)

  if (Number.isNaN(date.getTime())) {
    return String(value || '')
      .replace('T', ' ')
      .replace('Z', '')
  }

  const parts = new Intl.DateTimeFormat('zh-CN', {
    timeZone: RESPONSE_TIME_ZONE,
    hour12: false,
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
  }).formatToParts(date)

  const tokens = Object.fromEntries(
    parts.filter((item) => item.type !== 'literal').map((item) => [item.type, item.value]),
  )

  return `${tokens.year}-${tokens.month}-${tokens.day} ${tokens.hour}:${tokens.minute}:${tokens.second}`
}

type RouteHandlerOptions = {
  successMessage?: string
  errorMessage?: string
  scope?: string
}

type RouteFileHandlerOptions = {
  errorMessage?: string
  scope?: string
}

export function createRouteHandler(
  action: (req: Request, res: Response) => unknown | Promise<unknown>,
  { successMessage = 'ok', errorMessage, scope }: RouteHandlerOptions = {},
) {
  return async (req: Request, res: Response): Promise<void> => {
    try {
      const data = await action(req, res)
      res.json(buildSuccessPayload(data, successMessage))
    } catch (error) {
      sendRouteError(res, error, errorMessage, scope)
    }
  }
}

export function createRouteFileHandler(
  action: (req: Request, res: Response) => string | Promise<string>,
  { errorMessage, scope }: RouteFileHandlerOptions = {},
) {
  return async (req: Request, res: Response): Promise<void> => {
    try {
      const filePath = await action(req, res)
      res.sendFile(filePath)
    } catch (error) {
      sendRouteError(res, error, errorMessage, scope)
    }
  }
}
