import http from 'node:http'
import https from 'node:https'
import type { JsonObject } from '../../../types/json.js'

import { createHttpError } from '../../../utils/http.js'
import { cloudtentaclesRequest } from './http-client.js'
import { resolveCloudtentaclesConfig } from './helpers.js'
import { maskPhone } from '../../../utils/masking.js'

const BIND_URL_PROBE_MAX_BODY_BYTES = 64 * 1024
const AMS_SIGNATURE_EXPIRED_CODE = '99998'
/** 同一 vn 短时复用 bind_info，降低领取轮询撞 110001 概率 */
const BIND_INFO_CACHE_TTL_MS = 15_000
const BIND_INFO_RATE_LIMIT_INTERVAL_MS = 1_500
const BIND_INFO_RATE_LIMIT_RETRY_DELAY_MS = 2_500

type BindInfoResult = {
  baseUrl: string
  key: string
  id: number
  bindInfo: unknown
  rawItem: JsonObject
  raw: unknown
}

const bindInfoCache = new Map<string, { expiresAt: number; value: BindInfoResult }>()

type HeaderAdapter = {
  get(name: unknown): string | null
}
type AmsProbeResponse = {
  status: number
  headers: HeaderAdapter
  bodyText: string
}
type AmsSignatureParams = {
  userId: string
  timestamp: string
  nonce: string
  sign: string
}

export async function listCloudtentaclesVirtualNumbers(payload: JsonObject = {}) {
  const token = requireToken(
    payload.token,
    'cloudtentacles 虚拟号列表缺少 token',
    'cloudtentacles_vn_list_missing_token',
  )
  const key = requireKey(
    payload.key,
    'cloudtentacles 虚拟号列表缺少 key',
    'cloudtentacles_vn_list_missing_key',
  )
  const config = resolveCloudtentaclesConfig(payload)

  const result = await cloudtentaclesRequest(config.vnListPath, {
    ...config,
    method: 'POST',
    token,
    body: { key },
    sourceKey: payload.sourceKey || payload.resolvedSourceKey,
    accountLabel: payload.accountLabel,
    context: {
      operation: 'vn_list',
      vnKey: key,
      sourceKey: payload.sourceKey || payload.resolvedSourceKey || '',
    },
    businessErrorStatusCode: 401,
    businessErrorCode: 'cloudtentacles_vn_list_failed',
  })

  const items = Array.isArray(result.payload?.data)
    ? result.payload.data.map(mapVirtualNumberItem)
    : []
  const occupiedItemCount = items.filter(
    (item: ReturnType<typeof mapVirtualNumberItem>) => item.status !== 0,
  ).length

  return {
    baseUrl: config.baseUrl,
    key,
    itemCount: items.length,
    occupiedItemCount,
    items,
    rawItems: Array.isArray(result.payload?.data) ? result.payload.data : [],
  }
}

export async function appointCloudtentaclesVirtualNumber(payload: JsonObject = {}) {
  const token = requireToken(
    payload.token,
    'cloudtentacles 申请虚拟号缺少 token',
    'cloudtentacles_vn_appoint_missing_token',
  )
  const key = requireKey(
    payload.key,
    'cloudtentacles 申请虚拟号缺少 key',
    'cloudtentacles_vn_appoint_missing_key',
  )
  const config = resolveCloudtentaclesConfig(payload)

  const result = await cloudtentaclesRequest(config.vnAppointPath, {
    ...config,
    method: 'POST',
    token,
    body: { key },
    sourceKey: payload.sourceKey || payload.resolvedSourceKey,
    accountLabel: payload.accountLabel,
    context: {
      operation: 'vn_appoint',
      vnKey: key,
      sourceKey: payload.sourceKey || payload.resolvedSourceKey || '',
    },
    businessErrorStatusCode: 401,
    businessErrorCode: 'cloudtentacles_vn_appoint_failed',
  })

  return {
    baseUrl: config.baseUrl,
    key,
    item: mapVirtualNumberItem(result.payload?.data),
    rawItem: isPlainObject(result.payload?.data) ? result.payload.data : {},
  }
}

export async function generateCloudtentaclesLoginCode(payload: JsonObject = {}) {
  const token = requireToken(
    payload.token,
    'cloudtentacles 生成登录码缺少 token',
    'cloudtentacles_vn_generate_code_missing_token',
  )
  const key = requireKey(
    payload.key,
    'cloudtentacles 生成登录码缺少 key',
    'cloudtentacles_vn_generate_code_missing_key',
  )
  const id = requireId(
    payload.id,
    'cloudtentacles 生成登录码缺少 id',
    'cloudtentacles_vn_generate_code_missing_id',
  )
  const config = resolveCloudtentaclesConfig(payload)

  const result = await cloudtentaclesRequest(config.vnGenerateLoginCodePath, {
    ...config,
    method: 'POST',
    token,
    body: { key, id },
    sourceKey: payload.sourceKey || payload.resolvedSourceKey,
    accountLabel: payload.accountLabel,
    context: {
      operation: 'vn_generate_code',
      vnKey: key,
      vnId: id,
      sourceKey: payload.sourceKey || payload.resolvedSourceKey || '',
    },
    businessErrorStatusCode: 401,
    businessErrorCode: 'cloudtentacles_vn_generate_code_failed',
  })

  return {
    baseUrl: config.baseUrl,
    key,
    id,
    responseMessage: String(result.payload?.message || result.payload?.msg || 'success'),
  }
}

export async function fetchCloudtentaclesVirtualNumberCode(payload: JsonObject = {}) {
  const token = requireToken(
    payload.token,
    'cloudtentacles 获取验证码缺少 token',
    'cloudtentacles_vn_verif_code_missing_token',
  )
  const key = requireKey(
    payload.key,
    'cloudtentacles 获取验证码缺少 key',
    'cloudtentacles_vn_verif_code_missing_key',
  )
  const phone = String(payload.phone || '').trim()
  if (!phone) {
    throw createHttpError('cloudtentacles 获取验证码缺少手机号', {
      statusCode: 400,
      errorCode: 'cloudtentacles_vn_verif_code_missing_phone',
    })
  }

  const config = resolveCloudtentaclesConfig(payload)
  const result = await cloudtentaclesRequest(config.vnVerifCodePath, {
    ...config,
    method: 'POST',
    token,
    body: { key, phone },
    sourceKey: payload.sourceKey || payload.resolvedSourceKey,
    accountLabel: payload.accountLabel,
    context: {
      operation: 'vn_fetch_code',
      vnKey: key,
      phoneMasked: maskPhone(phone),
      sourceKey: payload.sourceKey || payload.resolvedSourceKey || '',
    },
    businessErrorStatusCode: 401,
    businessErrorCode: 'cloudtentacles_vn_verif_code_failed',
  })

  return {
    baseUrl: config.baseUrl,
    key,
    phone,
    code: String(result.payload?.data || '').trim(),
    responseMessage: String(result.payload?.message || result.payload?.msg || 'success'),
  }
}

export async function verifyCloudtentaclesLoginCode(payload: JsonObject = {}) {
  const token = requireToken(
    payload.token,
    'cloudtentacles 校验登录码缺少 token',
    'cloudtentacles_vn_verify_code_missing_token',
  )
  const key = requireKey(
    payload.key,
    'cloudtentacles 校验登录码缺少 key',
    'cloudtentacles_vn_verify_code_missing_key',
  )
  const id = requireId(
    payload.id,
    'cloudtentacles 校验登录码缺少 id',
    'cloudtentacles_vn_verify_code_missing_id',
  )
  const code = String(payload.code || '').trim()
  if (!code) {
    throw createHttpError('cloudtentacles 校验登录码缺少验证码', {
      statusCode: 400,
      errorCode: 'cloudtentacles_vn_verify_code_missing_code',
    })
  }

  const config = resolveCloudtentaclesConfig(payload)
  const result = await cloudtentaclesRequest(config.vnVerifyLoginCodePath, {
    ...config,
    method: 'POST',
    token,
    body: { key, id, code },
    sourceKey: payload.sourceKey || payload.resolvedSourceKey,
    accountLabel: payload.accountLabel,
    context: {
      operation: 'vn_verify_code',
      vnKey: key,
      vnId: id,
      sourceKey: payload.sourceKey || payload.resolvedSourceKey || '',
    },
    businessErrorStatusCode: 401,
    businessErrorCode: 'cloudtentacles_vn_verify_code_failed',
  })

  return {
    baseUrl: config.baseUrl,
    key,
    id,
    code,
    responseMessage: String(result.payload?.message || result.payload?.msg || 'success'),
  }
}

export async function getCloudtentaclesBindUrl(payload: JsonObject = {}) {
  const token = requireToken(
    payload.token,
    'cloudtentacles 获取兑换链接缺少 token',
    'cloudtentacles_vn_bind_url_missing_token',
  )
  const key = requireKey(
    payload.key,
    'cloudtentacles 获取兑换链接缺少 key',
    'cloudtentacles_vn_bind_url_missing_key',
  )
  const id = requireId(
    payload.id,
    'cloudtentacles 获取兑换链接缺少 id',
    'cloudtentacles_vn_bind_url_missing_id',
  )
  const config = resolveCloudtentaclesConfig(payload)

  const result = await cloudtentaclesRequest(config.vnBindUrlPath, {
    ...config,
    method: 'POST',
    token,
    body: { id, key },
    businessErrorStatusCode: 409,
    businessErrorCode: 'cloudtentacles_vn_bind_url_failed',
    sourceKey: payload.sourceKey || payload.resolvedSourceKey,
    accountLabel: payload.accountLabel,
    context: {
      operation: 'vn_bind_url',
      vnKey: key,
      vnId: id,
      sourceKey: payload.sourceKey || payload.resolvedSourceKey || '',
    },
  })

  return {
    baseUrl: config.baseUrl,
    key,
    id,
    bindUrl: String(result.payload?.data || '').trim(),
    responseMessage: String(result.payload?.message || result.payload?.msg || 'success'),
  }
}

export async function probeCloudtentaclesBindUrl(payload: JsonObject = {}) {
  const bindUrl = String(payload.bindUrl || '').trim()
  if (!bindUrl) {
    return {
      valid: false,
      status: 0,
      finalUrl: '',
      reason: 'empty_url',
    }
  }

  const config = resolveCloudtentaclesConfig(payload)
  const signatureParams = extractBindUrlSignatureParams(bindUrl)
  if (!signatureParams) {
    return {
      valid: false,
      expired: false,
      status: 0,
      finalUrl: bindUrl,
      reason: 'missing_signature_params',
      roleInfo: normalizeAmsBindRoleInfo(null),
      raw: null as null,
    }
  }

  const timeoutMs = Number(payload.timeoutMs || config.bindUrlProbeTimeoutMs || 5000)
  const userAgent = String(payload.userAgent || config.bindUrlProbeUserAgent || '').trim()
  const endpoint = String(
    payload.endpoint || config.bindUrlProbeEndpoint || 'https://comm.ams.game.qq.com/ide/',
  ).trim()
  const startedAt = Date.now()

  try {
    const response = await requestAmsIdeProbe(endpoint, {
      timeoutMs,
      body: buildAmsIdeProbeBody(signatureParams, config, payload),
      headers: buildAmsIdeProbeHeaders(signatureParams, config, {
        userAgent,
      }),
    })
    const raw = tryParseJson(response.bodyText)
    const expired = isAmsSignatureExpired(raw)
    const bindInfo = extractAmsBindInfo(raw)
    const roleInfo = normalizeAmsBindRoleInfo(bindInfo)
    const ok = response.status >= 200 && response.status < 300 && !expired && isAmsBusinessOk(raw)

    return {
      valid: ok,
      expired,
      status: response.status,
      finalUrl: endpoint,
      ret: String(raw?.ret ?? raw?.iRet ?? ''),
      iRet: String(raw?.iRet ?? raw?.jData?.iRet ?? ''),
      message: String(raw?.sMsg || raw?.jData?.sMsg || ''),
      reason: expired ? 'signature_expired' : ok ? 'ok' : 'ams_business_not_ok',
      roleInfo,
      raw,
      durationMs: Date.now() - startedAt,
    }
  } catch (error) {
    return {
      valid: false,
      expired: false,
      status: 0,
      finalUrl: bindUrl,
      reason: error instanceof Error ? error.message : String(error || 'probe_failed'),
      roleInfo: normalizeAmsBindRoleInfo(null),
      raw: null as null,
      durationMs: Date.now() - startedAt,
    }
  }
}

export async function getCloudtentaclesBindInfo(payload: JsonObject = {}) {
  const token = requireToken(
    payload.token,
    'cloudtentacles 获取绑定信息缺少 token',
    'cloudtentacles_vn_bind_info_missing_token',
  )
  const key = requireKey(
    payload.key,
    'cloudtentacles 获取绑定信息缺少 key',
    'cloudtentacles_vn_bind_info_missing_key',
  )
  const id = requireId(
    payload.id,
    'cloudtentacles 获取绑定信息缺少 id',
    'cloudtentacles_vn_bind_info_missing_id',
  )
  const config = resolveCloudtentaclesConfig(payload)
  const sourceKey = String(payload.sourceKey || '').trim()
  const cacheKey = `${sourceKey || token}|${id}|${key}`
  const skipCache = payload.skipCache === true
  const now = Date.now()

  if (!skipCache) {
    const cached = bindInfoCache.get(cacheKey)
    if (cached && cached.expiresAt > now) {
      return cached.value
    }
  }

  const result = await cloudtentaclesRequest(config.vnBindInfoPath, {
    ...config,
    method: 'POST',
    token,
    body: [{ id, key }],
    businessErrorStatusCode: 401,
    businessErrorCode: 'cloudtentacles_vn_bind_info_failed',
    // bind_info 是 110001 高发接口：加长间隔、独立路径桶、更长退避
    rateLimitIntervalMs: BIND_INFO_RATE_LIMIT_INTERVAL_MS,
    rateLimitRetryDelayMs: BIND_INFO_RATE_LIMIT_RETRY_DELAY_MS,
    rateLimitRetries: 2,
    rateLimitIsolatePath: true,
    sourceKey: payload.sourceKey || payload.resolvedSourceKey,
    accountLabel: payload.accountLabel,
    context: {
      operation: 'vn_bind_info',
      vnKey: key,
      vnId: id,
      sourceKey: payload.sourceKey || payload.resolvedSourceKey || '',
    },
  })

  const items = Array.isArray(result.payload?.data) ? result.payload.data : []
  const matchedItem =
    items.find((item: JsonObject) => Number(item?.id || 0) === id) || items[0] || {}
  const bindInfo = parseBindInfo(matchedItem?.bind_info)

  const value: BindInfoResult = {
    baseUrl: config.baseUrl,
    key,
    id,
    bindInfo,
    rawItem: isPlainObject(matchedItem) ? matchedItem : {},
    raw: result.payload ?? null,
  }

  bindInfoCache.set(cacheKey, {
    expiresAt: now + BIND_INFO_CACHE_TTL_MS,
    value,
  })

  // 简单淘汰过期项，避免 map 无限增长
  if (bindInfoCache.size > 500) {
    for (const [entryKey, entry] of bindInfoCache) {
      if (entry.expiresAt <= now) {
        bindInfoCache.delete(entryKey)
      }
    }
  }

  return value
}

export async function backCloudtentaclesVirtualNumber(payload: JsonObject = {}) {
  const token = requireToken(
    payload.token,
    'cloudtentacles 退还号码缺少 token',
    'cloudtentacles_vn_back_missing_token',
  )
  const key = requireKey(
    payload.key,
    'cloudtentacles 退还号码缺少 key',
    'cloudtentacles_vn_back_missing_key',
  )
  const id = requireId(
    payload.id,
    'cloudtentacles 退还号码缺少 id',
    'cloudtentacles_vn_back_missing_id',
  )
  const config = resolveCloudtentaclesConfig(payload)

  const result = await cloudtentaclesRequest(config.vnBackPath, {
    ...config,
    method: 'POST',
    token,
    body: { key, id },
    // 409：业务失败，避免前端/客服误判为后台登录或角色权限问题
    businessErrorStatusCode: 409,
    businessErrorCode: 'cloudtentacles_vn_back_failed',
    sourceKey: payload.sourceKey || payload.resolvedSourceKey,
    accountLabel: payload.accountLabel,
    context: {
      operation: 'vn_back',
      vnKey: key,
      vnId: id,
      sourceKey: payload.sourceKey || payload.resolvedSourceKey || '',
    },
  })

  return {
    baseUrl: config.baseUrl,
    key,
    id,
    responseMessage: String(result.payload?.message || result.payload?.msg || 'success'),
    raw: result.payload ?? null,
  }
}

function mapVirtualNumberItem(item: unknown) {
  const source = isPlainObject(item) ? item : {}
  const bindInfo = parseBindInfo(source.bind_info)

  return {
    id: Number(source.id || 0),
    phone: String(source.phone || '').trim(),
    status: Number(source.status || 0),
    countTime: Number(source.count_time || 0),
    bindInfo,
    raw: source,
  }
}

function requireToken(value: unknown, message: string, errorCode: string) {
  const token = String(value || '').trim()
  if (!token) {
    throw createHttpError(message, {
      statusCode: 400,
      errorCode,
    })
  }

  return token
}

function requireKey(value: unknown, message: string, errorCode: string) {
  const key = String(value || '').trim()
  if (!key) {
    throw createHttpError(message, {
      statusCode: 400,
      errorCode,
    })
  }

  return key
}

function requireId(value: unknown, message: string, errorCode: string) {
  const id = Number(value)
  if (!Number.isInteger(id) || id <= 0) {
    throw createHttpError(message, {
      statusCode: 400,
      errorCode,
    })
  }

  return id
}

function tryParseJson(text: string) {
  try {
    return JSON.parse(text)
  } catch {
    return null
  }
}

function parseBindInfo(value: unknown) {
  const bindInfoText = String(value || '').trim()
  return tryParseJson(bindInfoText) || bindInfoText || null
}

async function requestAmsIdeProbe(
  endpoint: string,
  options: JsonObject = {},
): Promise<AmsProbeResponse> {
  const url = new URL(endpoint)
  if (!['http:', 'https:'].includes(url.protocol)) {
    throw new Error('unsupported_ams_probe_protocol')
  }

  const timeoutMs = Number(options.timeoutMs || 5000)
  const isHttps = url.protocol === 'https:'
  const transport = isHttps ? https : http
  const body =
    options.body instanceof URLSearchParams ? options.body.toString() : String(options.body || '')

  return new Promise((resolve, reject) => {
    const request = transport.request(
      url,
      {
        method: 'POST',
        rejectUnauthorized: false,
        headers: {
          ...normalizeHeaderMap(options.headers),
          'content-length': Buffer.byteLength(body),
          'accept-encoding': 'identity',
        },
      },
      (response) => {
        const chunks: Buffer[] = []
        let receivedBytes = 0

        response.on('data', (chunk) => {
          if (receivedBytes >= BIND_URL_PROBE_MAX_BODY_BYTES) {
            return
          }

          const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
          const remainingBytes = BIND_URL_PROBE_MAX_BODY_BYTES - receivedBytes
          chunks.push(buffer.length > remainingBytes ? buffer.subarray(0, remainingBytes) : buffer)
          receivedBytes += Math.min(buffer.length, remainingBytes)
        })

        response.on('end', () => {
          clearTimeout(timer)
          resolve({
            status: Number(response.statusCode || 0),
            headers: createHeaderAdapter(response.headers),
            bodyText: Buffer.concat(chunks).toString('utf8'),
          })
        })
      },
    )

    const timer = setTimeout(() => {
      request.destroy(new Error('bind_url_probe_timeout'))
    }, timeoutMs)

    request.on('error', (error) => {
      clearTimeout(timer)
      reject(error)
    })

    request.write(body)
    request.end()
  })
}

function createHeaderAdapter(headers: http.IncomingHttpHeaders): HeaderAdapter {
  const normalized = new Map()

  for (const [key, value] of Object.entries(headers || {})) {
    if (Array.isArray(value)) {
      normalized.set(String(key || '').toLowerCase(), value.join(', '))
      continue
    }

    if (typeof value === 'string') {
      normalized.set(String(key || '').toLowerCase(), value)
    }
  }

  return {
    get(name: unknown) {
      return normalized.get(String(name || '').toLowerCase()) || null
    },
  }
}

function buildAmsIdeProbeBody(
  signatureParams: AmsSignatureParams,
  config: JsonObject,
  payload: JsonObject = {},
) {
  const body = new URLSearchParams()
  const chartId = String(payload.chartId || config.bindUrlProbeChartId || '323794').trim()
  const subChartId = String(payload.subChartId || config.bindUrlProbeSubChartId || chartId).trim()

  body.set('iChartId', chartId)
  body.set('iSubChartId', subChartId)
  body.set('sIdeToken', String(payload.ideToken || config.bindUrlProbeIdeToken || 'z90Syo').trim())
  body.set('e_code', '0')
  body.set('g_code', '0')
  body.set(
    'eas_url',
    String(
      payload.easUrl ||
        config.bindUrlProbeActivityUrl ||
        'http%3A%2F%2Fgp.qq.com%2Fcp%2Fa20240828cmcc%2F',
    ).trim(),
  )
  body.set(
    'eas_refer',
    String(
      payload.easRefer || `http%3A%2F%2Fnoreferrer%2F%3Freqid%3D${Date.now()}%26version%3D27`,
    ).trim(),
  )
  body.set('sMiloTag', String(payload.miloTag || `AMS-gp-${Date.now()}`).trim())
  body.set('userId', signatureParams.userId)
  body.set('timestamp', signatureParams.timestamp)
  body.set('nonce', signatureParams.nonce)
  body.set('sign', signatureParams.sign)

  return body
}

function buildAmsIdeProbeHeaders(
  signatureParams: AmsSignatureParams,
  config: JsonObject,
  options: JsonObject = {},
) {
  const userAgent = String(options.userAgent || '').trim()
  const referer = String(config.bindUrlProbeReferer || 'https://gp.qq.com/').trim()

  return {
    accept: 'application/json, text/plain, */*',
    'content-type': 'application/x-www-form-urlencoded',
    'accept-language': 'zh-CN,zh;q=0.9,fr;q=0.8,de;q=0.7,en;q=0.6',
    'cache-control': 'no-cache',
    origin: 'https://gp.qq.com',
    pragma: 'no-cache',
    referer,
    'sec-ch-ua': '""',
    'sec-ch-ua-mobile': '?1',
    'sec-ch-ua-platform': '""',
    'sec-fetch-dest': 'empty',
    'sec-fetch-mode': 'cors',
    'sec-fetch-site': 'same-site',
    ...(userAgent ? { 'user-agent': userAgent } : {}),
    cookie: buildAmsIdeProbeCookie(signatureParams, config),
  }
}

function buildAmsIdeProbeCookie(signatureParams: AmsSignatureParams, config: JsonObject) {
  const tokenParams = `?userId=${signatureParams.userId}&timestamp=${signatureParams.timestamp}&nonce=${signatureParams.nonce}&sign=${signatureParams.sign}`
  const cookies = [
    `tokenParams=${encodeURIComponent(tokenParams)}`,
    'gpqqcomrouteLine=a20240828cmcc_a20240828cmcc_a20240828cmcc_a20240828cmcc_a20240828cmcc_a20240828cmcc_a20240828cmcc',
    String(config.bindUrlProbeExtraCookie || '').trim(),
  ].filter(Boolean)

  return cookies.join('; ')
}

function extractBindUrlSignatureParams(bindUrl: string): AmsSignatureParams | null {
  try {
    const url = new URL(bindUrl)
    const params = new URLSearchParams(url.search)
    const hashQueryIndex = String(url.hash || '').indexOf('?')
    if (hashQueryIndex >= 0) {
      const hashParams = new URLSearchParams(String(url.hash || '').slice(hashQueryIndex + 1))
      for (const [key, value] of hashParams.entries()) {
        if (!params.has(key)) {
          params.set(key, value)
        }
      }
    }
    const tokenParams = String(params.get('tokenParams') || '').trim()
    if (tokenParams) {
      const nested = new URLSearchParams(
        tokenParams.startsWith('?') ? tokenParams.slice(1) : tokenParams,
      )
      for (const [key, value] of nested.entries()) {
        if (!params.has(key)) {
          params.set(key, value)
        }
      }
    }

    const userId = String(params.get('userId') || '').trim()
    const timestamp = String(params.get('timestamp') || '').trim()
    const nonce = String(params.get('nonce') || '').trim()
    const sign = String(params.get('sign') || '').trim()

    if (!userId || !timestamp || !nonce || !sign) {
      return null
    }

    return {
      userId,
      timestamp,
      nonce,
      sign,
    }
  } catch {
    return null
  }
}

function isAmsBusinessOk(raw: any) {
  const ret = String(raw?.ret ?? '').trim()
  const iRet = String(raw?.iRet ?? raw?.jData?.iRet ?? '').trim()
  return ret === '0' || iRet === '0'
}

function isAmsSignatureExpired(raw: any) {
  const codes = [
    raw?.ret,
    raw?.iRet,
    raw?.jData?.ret,
    raw?.jData?.iRet,
    raw?.jData?.jData?.iRet,
    raw?.arrErrNodeInfo?.errorCode,
    raw?.jData?.arrErrNodeInfo?.errorCode,
  ].map((value) => String(value ?? '').trim())

  const messages = [raw?.sMsg, raw?.jData?.sMsg, raw?.jData?.jData?.sMsg].map((value) =>
    String(value ?? '').trim(),
  )

  return (
    codes.includes(AMS_SIGNATURE_EXPIRED_CODE) ||
    messages.some((message) => message.includes('签名已过期'))
  )
}

function extractAmsBindInfo(raw: any) {
  const candidates = [raw?.jData?.sBindInfo, raw?.jData?.jData?.sBindInfo, raw?.sBindInfo]

  return candidates.find((item) => isPlainObject(item)) || null
}

function normalizeAmsBindRoleInfo(bindInfo: unknown) {
  const source = isPlainObject(bindInfo) ? bindInfo : {}

  return {
    name: String(source.sRoleName || source.roleName || source.name || '').trim(),
    rid: String(source.sRoleId || source.roleId || source.rid || '').trim(),
    rawInfo: isPlainObject(bindInfo) ? bindInfo : null,
  }
}

function normalizeHeaderMap(headers: unknown) {
  if (!headers || typeof headers !== 'object') {
    return {}
  }

  return Object.fromEntries(
    Object.entries(headers)
      .map(([key, value]) => [
        String(key || '')
          .trim()
          .toLowerCase(),
        String(value || '').trim(),
      ])
      .filter(([key, value]) => key && value),
  )
}

function isPlainObject(value: unknown): value is JsonObject {
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
}
