import {
  type WorkProductRuleMappingRow,
  type WorkProductRuleRow,
} from '../../repositories/worker-platform/index.js'
import {
  normalizeWorkOrderAcceptanceMode,
  normalizeWorkOrderGiftCooldownHours,
  normalizeWorkOrderGiftCooldownMinutes,
} from '../../domain/work-order-acceptance-mode.js'
import type { JsonObject } from '../../types/json.js'
import type { OrderItemRow, OrderRow } from '../../types/repository/rows.js'
import { safeParseJson } from '../admin/admin-query-utils.js'
import { normalizeRequirementFields } from './work-order-requirement-fields.js'

export function mapWorkProductRule(rule: WorkProductRuleRow | null | undefined) {
  if (!rule) return null
  const requirement = safeParseJson(rule.requirement_json)
  const match = safeParseJson(rule.match_json)
  return {
    ruleId: Number(rule.id),
    ruleKey: rule.rule_key,
    provider: rule.provider || '',
    platform: rule.platform || '',
    shopId: rule.shop_id || '',
    skuCode: rule.sku_code || '',
    productName: rule.product_name || '',
    matchType: rule.match_type || 'contains',
    match: {
      sellerId: String(match.sellerId || '').trim(),
      itemId: String(match.itemId || '').trim(),
      relItemId: String(match.relItemId || '').trim(),
      skuId: String(match.skuId || '').trim(),
      relSkuId: String(match.relSkuId || '').trim(),
      itemTitle: String(match.itemTitle || '').trim(),
      skuNick: String(match.skuNick || '').trim(),
      itemTitleMatchType: match.itemTitleMatchType === 'exact' ? 'exact' : 'contains',
      skuNickMatchType: match.skuNickMatchType === 'exact' ? 'exact' : 'contains',
    },
    categoryId: rule.category_id ? Number(rule.category_id) : null,
    categoryName: rule.category_name || '',
    enabled: Boolean(rule.enabled),
    autoCreate: Boolean(rule.auto_create),
    rewardAmount: Number(rule.reward_amount || 0),
    unitPriceFen: Number(rule.unit_price_fen || 0),
    sharing: {
      enabled: rule.sharing_enabled === true,
      totalQuantity: Number(rule.sharing_total_quantity || 1),
      unitReward: Number(rule.sharing_unit_reward || 0),
      autoFromOrder: rule.sharing_auto_from_order === true,
    },
    timeoutMinutes: Number(rule.timeout_minutes || 0),
    timeoutPolicy: String(rule.timeout_policy || 'reopen').trim(),
    acceptanceMode: normalizeWorkOrderAcceptanceMode(rule.acceptance_mode),
    giftCooldownHours: normalizeWorkOrderGiftCooldownHours(rule.gift_cooldown_hours),
    giftCooldownMinutes: normalizeWorkOrderGiftCooldownMinutes(rule.gift_cooldown_minutes),
    requirement: {
      fields: normalizeRequirementFields(requirement.fields),
    },
    sortOrder: Number(rule.sort_order || 100),
    createdAt: rule.created_at,
    updatedAt: rule.updated_at,
  }
}

export function resolveMatchingProductRule(
  order: OrderRow,
  item: OrderItemRow,
  rules: WorkProductRuleRow[],
  mappings: WorkProductRuleMappingRow[] = [],
  options: { legacyFallbackEnabled?: boolean } = {},
) {
  return resolveMatchingProductRuleDecision(order, item, rules, mappings, options).rule
}

export type WorkProductRuleMatchDecision = {
  rule: WorkProductRuleRow | null
  mappingId: number | null
  reason: 'matched' | 'unmatched' | 'ambiguous'
  score: number
  candidates: Array<{ ruleKey: string; score: number; mappingId: number | null }>
}

export type KuaishouWorkProductMatchContext = {
  sellerId: string
  itemId: string
  relItemId: string
  skuId: string
  relSkuId: string
  itemTitle: string
  skuNick: string
}

export function resolveMatchingProductRuleDecision(
  order: OrderRow,
  item: OrderItemRow,
  rules: WorkProductRuleRow[],
  mappings: WorkProductRuleMappingRow[] = [],
  options: { legacyFallbackEnabled?: boolean } = {},
): WorkProductRuleMatchDecision {
  const context = resolveKuaishouWorkProductMatchContext(item)
  const rulesById = new Map(rules.map((rule) => [Number(rule.id), rule]))
  // 快手发码订单默认只允许确定性商品映射；非快手订单保留原有规则，避免影响其他来源。
  const useLegacyFallback = !context.sellerId || options.legacyFallbackEnabled === true
  const candidates = [
    ...mappings.map((mapping) => scoreWorkProductRuleMapping(context, mapping, rulesById)),
    ...(useLegacyFallback ? rules.map((rule) => scoreWorkProductRule(order, item, rule)) : []),
  ]
    .filter(
      (candidate): candidate is { rule: WorkProductRuleRow; score: number; mappingId?: number } =>
        Boolean(candidate),
    )
    .sort((left, right) => right.score - left.score || Number(right.rule.id) - Number(left.rule.id))

  const top = candidates[0]
  if (!top) {
    return { rule: null, mappingId: null, reason: 'unmatched', score: 0, candidates: [] }
  }

  const tied = candidates.filter((candidate) => candidate.score === top.score)
  if (tied.length > 1) {
    return {
      rule: null,
      mappingId: null,
      reason: 'ambiguous',
      score: top.score,
      candidates: tied.map((candidate) => ({
        ruleKey: candidate.rule.rule_key,
        score: candidate.score,
        mappingId: candidate.mappingId || null,
      })),
    }
  }

  return {
    rule: top.rule,
    mappingId: top.mappingId || null,
    reason: 'matched',
    score: top.score,
    candidates: candidates.slice(0, 5).map((candidate) => ({
      ruleKey: candidate.rule.rule_key,
      score: candidate.score,
      mappingId: candidate.mappingId || null,
    })),
  }
}

export function resolveKuaishouWorkProductMatchContext(
  item: Pick<OrderItemRow, 'item_snapshot_json'>,
): KuaishouWorkProductMatchContext {
  const snapshot = safeParseJson(item.item_snapshot_json)
  const source = safeParseJson(snapshot.kuaishouSendCode)
  return {
    sellerId: String(source.sellerId || '').trim(),
    itemId: String(source.itemId || '').trim(),
    relItemId: normalizeExternalMatchId(source.relItemId),
    skuId: String(source.skuId || '').trim(),
    relSkuId: normalizeExternalMatchId(source.relSkuId),
    itemTitle: String(source.itemTitle || '').trim(),
    skuNick: String(source.skuNick || source.skuName || snapshot.externalSkuName || '').trim(),
  }
}

function scoreWorkProductRuleMapping(
  context: KuaishouWorkProductMatchContext,
  mapping: WorkProductRuleMappingRow,
  rulesById: Map<number, WorkProductRuleRow>,
) {
  if (!mapping.enabled) return null
  const rule = rulesById.get(Number(mapping.rule_id))
  if (!rule || !rule.enabled) return null
  if (
    !mappingSellerIds(mapping.seller_ids_json).some((sellerId) =>
      sameMatchId(context.sellerId, sellerId),
    )
  ) {
    return null
  }

  const mappingType =
    mapping.mapping_type === 'sku_series'
      ? 'sku_series'
      : mapping.mapping_type === 'sku_exact' || mapping.mapping_type === 'sku_override'
        ? 'sku_exact'
        : 'product_default'
  const mappedItemIds = normalizeMatchIdList(mapping.rel_item_id)
  const mappedTitle = normalizeMatchText(mapping.item_title)
  const hasContextItemId = Boolean(normalizeMatchId(context.relItemId))
  const itemIdMatched = mappedItemIds.some((mappedItemId) =>
    sameMatchId(context.relItemId, mappedItemId),
  )
  const titleMatched = Boolean(
    mappedTitle && matchNameCondition(normalizeMatchText(context.itemTitle), mappedTitle, true),
  )
  const hasProductScope = Boolean(mappedItemIds.length > 0 || mappedTitle)
  if (mappingType === 'product_default' || hasProductScope) {
    // 配置了商品 ID 时必须要求订单也提供商品 ID，避免缺少 ID 时误落入同店其他商品。
    if (
      (mappedItemIds.length > 0 && (!hasContextItemId || !itemIdMatched)) ||
      (mappedItemIds.length === 0 && !titleMatched)
    ) {
      return null
    }
  }

  let score = mappingType === 'sku_exact' ? 10_000 : mappingType === 'sku_series' ? 8_000 : 5_000
  if (itemIdMatched) score += 400
  if (titleMatched) score += 200
  if (mappingType === 'sku_exact') {
    const mappedSkuId = normalizeMatchId(mapping.rel_sku_id)
    const mappedSkuNick = normalizeMatchText(mapping.sku_nick)
    if (!mappedSkuId && !mappedSkuNick) return null
    const skuIdMatched = Boolean(mappedSkuId && sameMatchId(context.relSkuId, mappedSkuId))
    const skuNickMatched = Boolean(
      mappedSkuNick && matchNameCondition(normalizeMatchText(context.skuNick), mappedSkuNick, true),
    )
    if (!skuIdMatched && !skuNickMatched) {
      return null
    }
    if (skuIdMatched) score += 400
    if (skuNickMatched) score += 200
  }
  if (mappingType === 'sku_series') {
    const skuMatchMode = mapping.sku_match_mode === 'exact' ? 'exact' : 'fuzzy'
    if (!matchSkuSeriesName(context.skuNick, mapping.sku_nick, skuMatchMode)) return null
    score += 200
  }
  return { rule, score, mappingId: Number(mapping.id) }
}

function scoreWorkProductRule(order: OrderRow, item: OrderItemRow, rule: WorkProductRuleRow) {
  if (!matchesOptionalText(rule.provider, order.provider)) return null
  if (!matchesOptionalText(rule.platform, order.platform)) return null

  const snapshot = safeParseJson(item.item_snapshot_json)
  const source = safeParseJson(snapshot.kuaishouSendCode)
  const context = {
    sellerId: normalizeMatchId(source.sellerId),
    itemId: normalizeMatchId(source.itemId),
    relItemId: normalizeMatchId(source.relItemId),
    skuId: normalizeMatchId(source.skuId),
    relSkuId: normalizeMatchId(source.relSkuId),
    itemTitle: normalizeMatchText(source.itemTitle),
    skuNick: normalizeMatchText(source.skuNick || source.skuName || snapshot.externalSkuName),
  }
  const ruleShopId = normalizeMatchId(rule.shop_id)
  if (
    ruleShopId &&
    ruleShopId !== normalizeMatchId(order.shop_id) &&
    ruleShopId !== context.sellerId
  ) {
    return null
  }
  const match = safeParseJson(rule.match_json)
  const hasStructuredMatch = Object.entries(match).some(
    ([key, value]) =>
      !['itemTitleMatchType', 'skuNickMatchType'].includes(key) && String(value || '').trim(),
  )

  if (hasStructuredMatch) {
    let score = 100
    if (!matchCondition(context.sellerId, match.sellerId)) return null
    if (match.sellerId) score += 1000
    if (!matchCondition(context.itemId, match.itemId)) return null
    if (match.itemId) score += 800
    if (!matchCondition(context.relItemId, match.relItemId)) return null
    if (match.relItemId) score += 700
    if (!matchCondition(context.skuId, match.skuId)) return null
    if (match.skuId) score += 1000
    if (!matchCondition(context.relSkuId, match.relSkuId)) return null
    if (match.relSkuId) score += 900

    const itemTitle = normalizeMatchText(match.itemTitle)
    if (itemTitle) {
      const exact = match.itemTitleMatchType === 'exact'
      if (!matchNameCondition(context.itemTitle, itemTitle, exact)) return null
      score += exact ? 600 : 250
    }
    const skuNick = normalizeMatchText(match.skuNick)
    if (skuNick) {
      const exact = match.skuNickMatchType === 'exact'
      if (!matchNameCondition(context.skuNick, skuNick, exact)) return null
      score += exact ? 800 : 350
    }
    return { rule, score }
  }

  const skuCodes = [item.sku_code, snapshot.externalSkuCode, snapshot.externalItemId]
    .map(normalizeMatchId)
    .filter(Boolean)
  const skuNames = [
    item.sku_name,
    snapshot.externalSkuName,
    snapshot.externalSkuNameNormalized,
    source.skuNick,
    source.itemTitle,
  ]
    .map(normalizeMatchText)
    .filter(Boolean)
  const ruleSkuCode = normalizeMatchId(rule.sku_code)
  const ruleProductName = normalizeMatchText(rule.product_name)

  if (ruleSkuCode && !skuCodes.includes(ruleSkuCode)) return null
  if (!ruleProductName) {
    return ruleSkuCode ? { rule, score: 120 } : null
  }
  if (rule.match_type === 'exact') {
    return skuNames.includes(ruleProductName) || skuCodes.includes(ruleProductName)
      ? { rule, score: ruleSkuCode ? 300 : 150 }
      : null
  }
  return skuNames.some((value) => value.includes(ruleProductName)) ||
    skuCodes.some((value) => value.includes(ruleProductName))
    ? { rule, score: ruleSkuCode ? 220 : 100 }
    : null
}

function matchCondition(actual: string, expected: unknown) {
  const value = normalizeMatchId(expected)
  return !value || actual === value
}

function matchNameCondition(actual: string, expected: string, exact: boolean) {
  return Boolean(actual) && (exact ? actual === expected : actual.includes(expected))
}

function normalizeMatchId(value: unknown) {
  const normalized = String(value || '').trim()
  return normalized === '0' ? '' : normalized.toLowerCase()
}

function normalizeMatchIdList(value: unknown) {
  const rawValues = Array.isArray(value) ? value : [value]
  return Array.from(
    new Set(
      rawValues
        .flatMap((entry) => String(entry || '').split(/[,，;；\s\n\r]+/))
        .map(normalizeMatchId)
        .filter(Boolean),
    ),
  )
}

function normalizeExternalMatchId(value: unknown) {
  const normalized = String(value || '').trim()
  return normalized === '0' ? '' : normalized
}

function sameMatchId(left: unknown, right: unknown) {
  const normalizedLeft = normalizeMatchId(left)
  const normalizedRight = normalizeMatchId(right)
  return Boolean(normalizedLeft) && normalizedLeft === normalizedRight
}

function normalizeMatchText(value: unknown) {
  return String(value || '')
    .normalize('NFKC')
    .toLowerCase()
    .replace(/[\s\u3000]+/g, '')
    .trim()
}

function mappingSellerIds(value: unknown) {
  const parsed = Array.isArray(value) ? value : safeParseJson(value)
  const values = Array.isArray(parsed) ? parsed : []
  return Array.from(new Set(values.map(normalizeMatchId).filter(Boolean)))
}

/**
 * 去除 SKU 中的数量规格后得到系列名。
 * 例如“指挥官密钥10个”和“1个盛夏礼卡”分别归一为“指挥官密钥”“盛夏礼卡”。
 */
function normalizeSkuSeriesName(value: unknown) {
  return normalizeMatchText(value).replace(
    /(?:\d+|[零一二三四五六七八九十百千两]+)(?:个|份|张|枚)/g,
    '',
  )
}

function matchSkuSeriesName(actual: unknown, expected: unknown, matchMode: 'fuzzy' | 'exact') {
  const normalizedActual = normalizeSkuSeriesName(actual)
  const normalizedExpected = normalizeSkuSeriesName(expected)
  if (!normalizedActual || !normalizedExpected) return false
  if (matchMode === 'exact') return normalizedActual === normalizedExpected

  const fuzzyActual = normalizeFuzzySkuSeriesName(normalizedActual)
  const fuzzyExpected = normalizeFuzzySkuSeriesName(normalizedExpected)
  return fuzzyActual.includes(fuzzyExpected) || fuzzyExpected.includes(fuzzyActual)
}

function normalizeFuzzySkuSeriesName(value: string) {
  return value.replace(/秘钥|密匙|秘匙/g, '密钥')
}

export function normalizeWorkProductRuleMatch(payload: JsonObject): JsonObject {
  const match =
    payload.match && typeof payload.match === 'object' && !Array.isArray(payload.match)
      ? (payload.match as JsonObject)
      : {}
  const source = {
    sellerId: payload.sellerId ?? payload.seller_id ?? match.sellerId,
    itemId: payload.itemId ?? payload.item_id ?? match.itemId,
    relItemId: payload.relItemId ?? payload.rel_item_id ?? match.relItemId,
    skuId: payload.skuId ?? payload.sku_id ?? match.skuId,
    relSkuId: payload.relSkuId ?? payload.rel_sku_id ?? match.relSkuId,
    itemTitle: payload.itemTitle ?? payload.item_title ?? match.itemTitle,
    skuNick: payload.skuNick ?? payload.sku_nick ?? match.skuNick,
  }
  const result: JsonObject = {}
  for (const [key, value] of Object.entries(source)) {
    const normalized = key.endsWith('Id') ? normalizeMatchId(value) : normalizeMatchText(value)
    if (normalized) result[key] = normalized
  }
  result.itemTitleMatchType =
    (payload.itemTitleMatchType ?? payload.item_title_match_type ?? match.itemTitleMatchType) ===
    'exact'
      ? 'exact'
      : 'contains'
  result.skuNickMatchType =
    (payload.skuNickMatchType ?? payload.sku_nick_match_type ?? match.skuNickMatchType) === 'exact'
      ? 'exact'
      : 'contains'
  return result
}

export function matchesOptionalText(ruleValue: unknown, sourceValue: unknown) {
  const ruleText = String(ruleValue || '')
    .trim()
    .toLowerCase()
  if (!ruleText) {
    return true
  }
  return (
    ruleText ===
    String(sourceValue || '')
      .trim()
      .toLowerCase()
  )
}
