import crypto from 'node:crypto'

import { runtimeConfig } from '../../../../config/runtime.js'
import { getAgisoMessagingDefaults, getAgisoShopConfigMap } from '../shop-config-service.js'
import {
  createMessageDelivery,
  findLatestSuccessfulMessageDelivery,
  updateMessageDelivery,
} from '../../../../repositories/message-delivery-repo.js'
import { nowIso } from '../../../../utils/time.js'

const AGISO_XIANYU_MESSAGE_CHANNEL = 'agiso_im'
const AGISO_XIANYU_AUTO_DELIVERY_MESSAGE_CHANNEL = 'agiso_im_auto_delivery'

type JsonObject = Record<string, unknown>

type MessageOrder = {
  id?: number | string
  platform?: string
  shop_id?: string
  shop_name?: string
  platform_order_id?: string
  [key: string]: unknown
}

type MessageTask = {
  id?: number | string
  task_no?: string
  [key: string]: unknown
}

type AgisoMessagingConfig = {
  enabled?: boolean
  sendMessageEndpoint?: string
  accessToken?: string
  appSecret?: string
  apiVersion?: string
  messageTemplate?: string
  autoDeliveryMessageTemplate?: string
  shopName?: string
  [key: string]: unknown
}

type EnsureClaimMessageInput = {
  order?: MessageOrder | null
  task?: MessageTask | null
  claimUrl?: string
  expiredAt?: string
}

type EnsureAutoDeliveryMessageInput = {
  order?: MessageOrder | null
  task?: MessageTask | null
}

type DeliverMessageInput = EnsureAutoDeliveryMessageInput & {
  channel?: string
  messageContent?: string
  claimUrl?: string
}

type FetchResponseLike = {
  status: number
  text: () => Promise<string>
}

type FetchLike = (
  input: string | URL,
  init?: RequestInit,
) => Promise<FetchResponseLike>

type MessageDeliveryLike = {
  id: number | string
  [key: string]: unknown
}

type MessageDeliveryCreateInput = {
  provider: string
  platform: string
  shopId: string
  shopName: string
  channel: string
  orderId?: number | string | null
  taskId?: number | string | null
  platformOrderId?: string
  recipientKey?: string
  messageContent: string
  claimUrl: string
  status: string
  requestUrl: string
  requestHeadersJson: string
  requestBodyJson: string
  responseStatus: number
  responseJson: string
  errorMessage: string
  sentAt: string | null
  createdAt: string
  updatedAt: string
}

type MessageDeliveryPatch = {
  status?: string
  response_status?: number
  response_json?: string
  error_message?: string
  sent_at?: string | null
  updated_at?: string
}

type FindSuccessfulDeliveryInput = {
  provider: string
  platform: string
  shopId: string
  platformOrderId: string
  channel: string
  claimUrl: string
}

type DeliverMessageDeps = {
  nowIso?: () => string
  findLatestSuccessfulMessageDelivery?: (
    input: FindSuccessfulDeliveryInput,
  ) => Promise<MessageDeliveryLike | null>
  createMessageDelivery?: (
    input: MessageDeliveryCreateInput,
  ) => Promise<MessageDeliveryLike | null>
  updateMessageDelivery?: (
    deliveryId: number | string,
    patch: MessageDeliveryPatch,
  ) => Promise<MessageDeliveryLike | null>
  fetch?: FetchLike
}

type RenderClaimMessageInput = EnsureClaimMessageInput & {
  template?: string
  shopName?: string
}

type RenderAutoDeliveryMessageInput = EnsureAutoDeliveryMessageInput & {
  template?: string
  shopName?: string
}

type RenderMessageTemplateInput = RenderAutoDeliveryMessageInput & {
  template?: string
  claimUrl?: string
  expiredAt?: string
  resultMessage?: string
}

export async function ensureAgisoXianyuClaimMessageDeliveredForTask({
  order,
  task,
  claimUrl,
  expiredAt,
}: EnsureClaimMessageInput = {}) {
  const config = resolveAgisoXianyuMessagingConfig(order)
  const messageContent = renderAgisoClaimMessage({
    order,
    task,
    claimUrl,
    expiredAt,
    template: config.messageTemplate,
    shopName: config.shopName,
  })

  return deliverAgisoXianyuMessageForTask({
    order,
    task,
    channel: AGISO_XIANYU_MESSAGE_CHANNEL,
    messageContent,
    claimUrl,
  })
}

export async function ensureAgisoXianyuAutoDeliveryMessageDeliveredForTask({
  order,
  task,
}: EnsureAutoDeliveryMessageInput = {}) {
  const config = resolveAgisoXianyuMessagingConfig(order)
  const messageContent = renderAgisoAutoDeliveryMessage({
    order,
    task,
    template: config.autoDeliveryMessageTemplate,
    shopName: config.shopName,
  })

  return deliverAgisoXianyuMessageForTask({
    order,
    task,
    channel: AGISO_XIANYU_AUTO_DELIVERY_MESSAGE_CHANNEL,
    messageContent,
  })
}

export function normalizeAgisoMessageTemplate(value: unknown): string {
  return String(value || '')
    .replaceAll('\\r\\n', '\n')
    .replaceAll('\\n', '\n')
    .replaceAll('\r\n', '\n')
}

async function deliverAgisoXianyuMessageForTask({
  order,
  task,
  channel,
  messageContent,
  claimUrl = '',
}: DeliverMessageInput = {}) {
  return deliverAgisoXianyuMessageForTaskWithDeps({
    order,
    task,
    channel,
    messageContent,
    claimUrl,
  })
}

export async function deliverAgisoXianyuMessageForTaskWithDeps({
  order,
  task,
  channel,
  messageContent,
  claimUrl = '',
}: DeliverMessageInput = {}, deps: DeliverMessageDeps = {}) {
  const now = deps.nowIso || nowIso
  const findSuccessfulDelivery = deps.findLatestSuccessfulMessageDelivery || findLatestSuccessfulMessageDelivery
  const insertMessageDelivery = deps.createMessageDelivery || createMessageDelivery
  const patchMessageDelivery = deps.updateMessageDelivery || updateMessageDelivery
  const sendRequest = deps.fetch || (fetch as FetchLike)

  if (!order || !task || !messageContent) {
    return { sent: false, skipped: true, reason: 'missing_message_context' }
  }

  const config = resolveAgisoXianyuMessagingConfig(order)
  if (!config.enabled) {
    return { sent: false, skipped: true, reason: 'messaging_disabled' }
  }

  const endpoint = String(config.sendMessageEndpoint || '').trim()
  const accessToken = String(config.accessToken || '').trim()
  const appSecret = String(config.appSecret || runtimeConfig.platforms?.agiso?.appSecret || '').trim()
  if (!endpoint || !accessToken || !appSecret) {
    return { sent: false, skipped: true, reason: 'missing_endpoint_or_access_token_or_app_secret' }
  }

  const successful = await findSuccessfulDelivery({
    provider: 'agiso',
    platform: String(order.platform || '').trim() || 'unknown',
    shopId: String(order.shop_id || '').trim(),
    platformOrderId: String(order.platform_order_id || '').trim(),
    channel,
    claimUrl: String(claimUrl || ''),
  })
  if (successful) {
    return { sent: false, skipped: true, reason: 'already_sent', deliveryId: successful.id }
  }

  const url = buildRequestUrl(endpoint)
  const requestBody = buildRequestBody({
    tid: String(order.platform_order_id || ''),
    msg: messageContent,
    appSecret,
  })
  const requestHeaders = buildRequestHeaders({
    accessToken,
    apiVersion: String(config.apiVersion || '1').trim() || '1',
  })
  const createdAt = now()
  const delivery = await insertMessageDelivery({
    provider: 'agiso',
    platform: String(order.platform || '').trim() || 'unknown',
    shopId: String(order.shop_id || '').trim(),
    shopName: String(order.shop_name || '').trim(),
    channel,
    orderId: order.id,
    taskId: task.id,
    platformOrderId: order.platform_order_id,
    recipientKey: order.platform_order_id,
    messageContent,
    claimUrl,
    status: 'pending',
    requestUrl: url,
    requestHeadersJson: JSON.stringify(maskHeadersForStorage(requestHeaders)),
    requestBodyJson: JSON.stringify(maskBodyForStorage(requestBody)),
    responseStatus: 0,
    responseJson: '{}',
    errorMessage: '',
    sentAt: null,
    createdAt,
    updatedAt: createdAt,
  })
  if (!delivery) {
    return { sent: false, skipped: false, reason: 'delivery_create_failed' }
  }

  try {
    const response = await sendRequest(url, {
      method: 'POST',
      headers: requestHeaders,
      body: new URLSearchParams(requestBody).toString(),
    })
    const rawText = await response.text()
    const parsed = safeParseJson(rawText)
    const success = isAgisoSendSuccess(response.status, parsed)
    const errorMessage = success ? '' : resolveAgisoErrorMessage(parsed, rawText, response.status)
    const updated = await patchMessageDelivery(delivery.id, {
      status: success ? 'success' : 'failed',
      response_status: response.status,
      response_json: JSON.stringify(parsed ?? { rawText }),
      error_message: errorMessage,
      sent_at: success ? now() : null,
      updated_at: now(),
    })

    return {
      sent: success,
      skipped: false,
      deliveryId: updated?.id || delivery.id,
      responseStatus: response.status,
      response: parsed ?? { rawText },
      errorMessage,
    }
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error || '发送消息失败')
    await patchMessageDelivery(delivery.id, {
      status: 'failed',
      response_status: 0,
      response_json: '{}',
      error_message: message,
      sent_at: null,
      updated_at: now(),
    })

    return {
      sent: false,
      skipped: false,
      deliveryId: delivery.id,
      errorMessage: message,
    }
  }
}

function resolveAgisoXianyuMessagingConfig(order: MessageOrder | null | undefined): AgisoMessagingConfig {
  const baseConfig = runtimeConfig.platforms.agiso.messaging
  const fileDefaults = getAgisoMessagingDefaults()
  const shopId = String(order?.shop_id || '').trim()
  const shopConfigs = getAgisoShopConfigMap()
  const shopConfig = shopId && isPlainObject(shopConfigs[shopId]) ? shopConfigs[shopId] : {}

  return {
    ...baseConfig,
    ...fileDefaults,
    ...shopConfig,
  }
}

function buildRequestUrl(endpoint: string): string {
  return new URL(endpoint).toString()
}

function buildRequestHeaders({
  accessToken,
  apiVersion,
}: { accessToken: string; apiVersion: string }): Record<string, string> {
  return {
    Authorization: `Bearer ${accessToken}`,
    ApiVersion: apiVersion,
    'content-type': 'application/x-www-form-urlencoded; charset=utf-8',
  }
}

function buildRequestBody({
  tid,
  msg,
  appSecret,
}: { tid: string; msg: string; appSecret: string }): Record<string, string> {
  const timestamp = String(Math.floor(Date.now() / 1000))
  const payload: Record<string, string> = {
    tid,
    msg,
    timestamp,
  }

  payload.sign = generateSign(payload, appSecret)

  return payload
}

function generateSign(params: Record<string, string>, appSecret: string): string {
  const sortedEntries = Object.entries(params).sort(([left], [right]) => left.localeCompare(right))
  let raw = appSecret

  for (const [key, value] of sortedEntries) {
    raw += `${key}${value}`
  }

  raw += appSecret

  return crypto.createHash('md5').update(raw, 'utf8').digest('hex').toLowerCase()
}

function renderAgisoClaimMessage({
  order,
  task,
  claimUrl,
  expiredAt,
  template,
  shopName = '',
}: RenderClaimMessageInput): string {
  const source = normalizeAgisoMessageTemplate(String(template || '').trim())
    || '您的订单 {platformOrderId} 已创建领取链接，请在 {expiredAt} 前完成领取：{claimUrl}'

  return renderAgisoMessageTemplate({
    order,
    task,
    template: source,
    shopName,
    claimUrl,
    expiredAt: String(expiredAt || '尽快'),
  })
}

export function renderAgisoAutoDeliveryMessage({
  order,
  task,
  template,
  shopName = '',
}: RenderAutoDeliveryMessageInput): string {
  const source = normalizeAgisoMessageTemplate(String(template || '').trim())
    || '您的订单 {platformOrderId} 已完成自动发货，请注意查收。'

  return renderAgisoMessageTemplate({
    order,
    task,
    template: source,
    shopName,
    resultMessage: '自动发货成功',
  })
}

function renderAgisoMessageTemplate({
  order,
  task,
  template,
  shopName = '',
  claimUrl = '',
  expiredAt = '',
  resultMessage = '',
}: RenderMessageTemplateInput = {}): string {
  const resolvedShopName = String(shopName || order?.shop_name || order?.shop_id || '').trim()

  return normalizeAgisoMessageTemplate(template)
    .replaceAll('{platformOrderId}', String(order?.platform_order_id || ''))
    .replaceAll('{taskNo}', String(task?.task_no || ''))
    .replaceAll('{shopName}', resolvedShopName)
    .replaceAll('{shopId}', String(order?.shop_id || ''))
    .replaceAll('{claimUrl}', String(claimUrl || ''))
    .replaceAll('{expiredAt}', String(expiredAt || ''))
    .replaceAll('{resultMessage}', String(resultMessage || ''))
}

function isAgisoSendSuccess(statusCode: number, payload: unknown): boolean {
  if (statusCode < 200 || statusCode >= 300) {
    return false
  }

  if (!payload || typeof payload !== 'object') {
    return true
  }

  const normalizedPayload = payload as JsonObject
  if (normalizedPayload.IsSuccess === true) {
    return true
  }

  if (Number(normalizedPayload.Error_Code) === 0) {
    return true
  }

  if (Number(normalizedPayload.code) === 0) {
    return true
  }

  return false
}

function resolveAgisoErrorMessage(payload: unknown, rawText: string, statusCode: number): string {
  if (payload && typeof payload === 'object') {
    const normalizedPayload = payload as JsonObject
    for (const value of [
      normalizedPayload.Error_Msg,
      normalizedPayload.msg,
      normalizedPayload.message,
      normalizedPayload.error,
    ]) {
      const normalized = String(value || '').trim()
      if (normalized) {
        return normalized
      }
    }
  }

  const text = String(rawText || '').trim()
  return text || `Agiso 咸鱼发消息失败，HTTP ${statusCode}`
}

function safeParseJson(rawText: string): JsonObject | null {
  const normalized = String(rawText || '').trim()

  if (!normalized) {
    return null
  }

  try {
    return JSON.parse(normalized)
  } catch {
    return null
  }
}

function maskHeadersForStorage(headers: Record<string, string>): Record<string, string> {
  const output = { ...headers }

  if (output.Authorization) {
    output.Authorization = '[masked]'
  }

  return output
}

function maskBodyForStorage(body: Record<string, string>): Record<string, string> {
  const output = { ...body }

  if (output.sign) {
    output.sign = '[masked]'
  }

  return output
}

function isPlainObject(value: unknown): value is JsonObject {
  return Object.prototype.toString.call(value) === '[object Object]'
}
