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

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

export type AffiliateDashRequestMethod = 'GET' | 'POST'

/**
 * affiliate_dash 商户开放接口请求封装。
 * 自动生成 X-App-Key / X-Timestamp / X-Nonce / X-Sign，统一超时与错误归一。
 */
export async function affiliateDashRequest(input: {
  method: AffiliateDashRequestMethod
  pathname: string
  payload?: JsonObject | undefined
  timeoutMs?: number | undefined
}) {
  const config = assertAffiliateDashConfig()
  const body = input.payload === undefined ? '' : JSON.stringify(input.payload)
  const pathname = normalizePathname(input.pathname)
  const url = new URL(pathname, `${config.baseUrl}/`)
  // affiliate_dash 签名要求 path 为「仅路径」（不含 query string）
  const signPath = url.pathname
  const timestamp = String(Math.floor(Date.now() / 1000))
  const headers = createClientSignHeaders({
    appKey: config.appKey,
    appSecret: config.appSecret,
    method: input.method,
    path: signPath,
    body,
    timestamp,
  })
  const timeoutMs = Math.max(1, Number(input.timeoutMs || config.timeoutMs) || config.timeoutMs)
  const packetId = createRequestId('ad')
  const startedAt = Date.now()
  const controller = new AbortController()
  const timeout = setTimeout(() => controller.abort(), timeoutMs)
  const requestUrl = url.toString()
  const requestHeaders: Record<string, string> = {
    'content-type': 'application/json',
    'x-app-key': headers['X-App-Key'],
    'x-timestamp': headers['X-Timestamp'],
    'x-nonce': headers['X-Nonce'],
    'x-sign': headers['X-Sign'],
  }
  let upstreamPacketLogged = false

  logExternalHttpPacket('[affiliate-dash/http]', '发送请求', {
    packetId,
    method: input.method,
    pathname,
    requestUrl,
    timeoutMs,
    request: {
      headers: requestHeaders,
      body: input.payload,
      rawBody: body,
    },
  })

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

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

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

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

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

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

    throw error
  } finally {
    clearTimeout(timeout)
  }
}

export function isAffiliateDashSuccessResponse(json: JsonObject) {
  return Number(json.code ?? 0) === 0
}

function normalizePathname(pathname: string) {
  const trimmed = String(pathname || '').trim()
  if (!trimmed.startsWith('/')) {
    return `/${trimmed}`
  }
  return trimmed
}

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

function summarizeAffiliateDashMessage(json: JsonObject) {
  const message = String(json.message || json.msg || '').trim()
  return message || ''
}

function summarizeAffiliateDashResponse(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 list = Array.isArray(data.list) ? data.list : []

  return {
    code: json.code,
    message: summarizeAffiliateDashMessage(json),
    hasOrder: Boolean(order),
    orderNo: String(order?.order_no || '').trim(),
    orderStatus: String(order?.order_status || '').trim(),
    canShip: Boolean(order?.can_ship),
    listCount: list.length,
    total: Number(data.total || list.length || 0) || 0,
  }
}

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