import crypto from 'node:crypto'
import { asJsonObject, type JsonObject } from '../../../types/json.js'

import { createHttpError } from '../../../utils/http.js'
import { createRequestId, logExternalHttpPacket } from '../../../utils/logger.js'
import { assertKuaishouFeifeiConfig } from './config.js'

export async function kuaishouFeifeiRequest(pathname: string, payload: JsonObject = {}) {
  const config = assertKuaishouFeifeiConfig()
  const body = JSON.stringify(payload)
  const timestamp = String(Math.floor(Date.now() / 1000))
  const packetId = createRequestId('ff')
  const startedAt = Date.now()
  let upstreamPacketLogged = false
  const sign = signKuaishouFeifeiPayload({
    appKey: config.appKey,
    appSecret: config.appSecret,
    timestamp,
    body,
  })
  const controller = new AbortController()
  const timeout = setTimeout(() => controller.abort(), config.timeoutMs)
  const url = `${config.baseUrl}${pathname}`
  const requestHeaders = {
    'content-type': 'application/json',
    'x-app-key': config.appKey,
    'x-timestamp': timestamp,
    'x-sign': sign,
  }

  logExternalHttpPacket('[kuaishou-feifei/http]', '发送请求', {
    packetId,
    method: 'POST',
    pathname,
    url,
    timeoutMs: config.timeoutMs,
    request: {
      headers: requestHeaders,
      body: payload,
      rawBody: body,
    },
  })

  try {
    const response = await fetch(url, {
      method: 'POST',
      headers: requestHeaders,
      body,
      signal: controller.signal,
    })
    const text = await response.text()
    const json = parseJsonObject(text)
    const durationMs = Date.now() - startedAt
    const responsePacket = {
      packetId,
      method: 'POST',
      pathname,
      url,
      status: response.status,
      durationMs,
      request: {
        headers: requestHeaders,
        body: payload,
      },
      response: {
        ok: response.ok,
        headers: normalizeFetchHeaders(response.headers),
        summary: summarizeKuaishouFeifeiResponse(json),
        body: json,
        rawText: text,
      },
    }

    if (!response.ok) {
      upstreamPacketLogged = true
      logExternalHttpPacket('[kuaishou-feifei/http]', 'HTTP 响应失败', responsePacket, {
        level: 'warn',
      })
      throw createHttpError(`kuaishou-feifei 请求失败: HTTP ${response.status}`, {
        statusCode: 502,
        errorCode: 'kuaishou_feifei_http_failed',
        context: {
          status: response.status,
          body: text,
        },
      })
    }

    if (!isKuaishouFeifeiSuccessResponse(json)) {
      upstreamPacketLogged = true
      logExternalHttpPacket('[kuaishou-feifei/http]', '业务响应失败', responsePacket, {
        level: 'warn',
      })
      throw createHttpError(String(json.message || 'kuaishou-feifei 业务失败'), {
        statusCode: 502,
        errorCode: 'kuaishou_feifei_business_failed',
        context: json,
      })
    }

    upstreamPacketLogged = true
    logExternalHttpPacket('[kuaishou-feifei/http]', '请求完成', responsePacket)

    return json
  } catch (error) {
    if ((error as Error)?.name === 'AbortError') {
      logExternalHttpPacket(
        '[kuaishou-feifei/http]',
        '请求超时',
        {
          packetId,
          method: 'POST',
          pathname,
          url,
          timeoutMs: config.timeoutMs,
          durationMs: Date.now() - startedAt,
          request: {
            headers: requestHeaders,
            body: payload,
          },
        },
        {
          level: 'warn',
        },
      )
      throw createHttpError('kuaishou-feifei 请求超时', {
        statusCode: 504,
        errorCode: 'kuaishou_feifei_timeout',
      })
    }

    if (!upstreamPacketLogged) {
      logExternalHttpPacket(
        '[kuaishou-feifei/http]',
        '请求异常',
        {
          packetId,
          method: 'POST',
          pathname,
          url,
          timeoutMs: config.timeoutMs,
          durationMs: Date.now() - startedAt,
          request: {
            headers: requestHeaders,
            body: payload,
          },
          error,
        },
        {
          level: 'error',
        },
      )
    }

    throw error
  } finally {
    clearTimeout(timeout)
  }
}

export function signKuaishouFeifeiPayload({
  appKey,
  appSecret,
  timestamp,
  body,
}: {
  appKey: string
  appSecret: string
  timestamp: string
  body: string
}) {
  return crypto
    .createHmac('sha256', appSecret)
    .update(`${appKey}${timestamp}${body}`, 'utf8')
    .digest('hex')
    .toLowerCase()
}

export function isKuaishouFeifeiSuccessResponse(json: JsonObject) {
  const code = Number(json.code ?? 0)
  const message = String(json.message || json.msg || '')
    .trim()
    .toLowerCase()

  return code === 0 || (code === 10000 && isKuaishouFeifeiSuccessMessage(message))
}

function parseJsonObject(text: string): JsonObject {
  try {
    const parsed = JSON.parse(text || '{}')
    return asJsonObject(parsed)
  } catch {
    return {}
  }
}

function isKuaishouFeifeiSuccessMessage(message: string) {
  return message === 'success' || message === 'ok' || message.endsWith('成功')
}

function normalizeFetchHeaders(headers: Headers) {
  const result: Record<string, string> = {}
  headers.forEach((value, key) => {
    result[key] = value
  })
  return result
}

function summarizeKuaishouFeifeiResponse(json: JsonObject) {
  const data = json.data && typeof json.data === 'object' ? (json.data as JsonObject) : {}
  const order = data.order && typeof data.order === 'object' ? (data.order as JsonObject) : null
  const h5 = order?.h5 && typeof order.h5 === 'object' ? (order.h5 as JsonObject) : {}
  const list = Array.isArray(data.list) ? data.list : []

  return {
    code: json.code,
    message: String(json.message || json.msg || '').trim(),
    hasOrder: Boolean(order),
    orderNo: String(order?.order_no || '').trim(),
    platformOrderNo: String(order?.platform_order_no || '').trim(),
    productCode: String(order?.product_code || '').trim(),
    rechargeStatus: Number(order?.recharge_status ?? order?.status ?? 0) || 0,
    rechargeStatusLabel: String(order?.recharge_status_label || order?.status_label || '').trim(),
    hasH5EntryUrl: Boolean(String(h5.entry_url || '').trim()),
    hasH5RechargeUrl: Boolean(String(h5.recharge_url || '').trim()),
    listCount: list.length,
    total: Number(data.total || list.length || 0) || 0,
  }
}
