import { listKuaishouIndustryVoucherWorkOrderSyncSources } from '../../repositories/kuaishou-industry-voucher-repo.js'
import {
  deleteWorkProductRuleMapping,
  listWorkProductMatchLogs,
  listWorkProductRuleMappings,
  listWorkProductRuleMappingsPage,
  upsertWorkProductRuleMapping,
} from '../../repositories/worker-platform/product-match-repo.js'
import { listWorkProductRules } from '../../repositories/worker-platform/work-product-rule-repo.js'
import type {
  WorkProductMatchLogRow,
  WorkProductRuleMappingRow,
} from '../../repositories/worker-platform/types.js'
import type { JsonObject } from '../../types/json.js'
import type { OrderItemRow, OrderRow } from '../../types/repository/rows.js'
import { createHttpError } from '../../utils/http.js'
import { nowIso } from '../../utils/time.js'
import {
  normalizeDateString,
  normalizePage,
  normalizePageSize,
  safeParseJson,
} from '../admin/admin-query-utils.js'
import {
  mapWorkProductRule,
  normalizeBoolean,
  normalizeOptionalId,
  normalizePositiveInteger,
  resolveKuaishouWorkProductMatchContext,
  resolveMatchingProductRuleDecision,
} from './mappers.js'
import { reprocessKuaishouSendCodeWorkOrders } from './sync-work-orders-from-send-code.js'
import { getWorkerProductMatchConfig } from './worker-product-match-config-service.js'

export async function reprocessAdminKuaishouSendCodeWorkOrders(payload: JsonObject = {}) {
  const limit = Math.min(500, normalizePositiveInteger(payload.limit, 100))
  return reprocessKuaishouSendCodeWorkOrders(limit)
}

export async function listAdminWorkProductRuleMappings(query: JsonObject = {}) {
  const ruleId = normalizeOptionalId(query.ruleId ?? query.rule_id) || 0
  const hasPagination = query.page !== undefined || query.pageSize !== undefined || query.keyword
  if (hasPagination) {
    const page = normalizePage(query.page)
    const pageSize = normalizePageSize(query.pageSize)
    const keyword = String(query.keyword || '').trim()
    const result = await listWorkProductRuleMappingsPage({
      page,
      pageSize,
      keyword,
      ruleId,
    })
    return {
      items: result.items.map(mapWorkProductRuleMapping),
      pagination: { page, pageSize, total: result.total },
    }
  }
  const items = await listWorkProductRuleMappings({ ruleId })
  return { items: items.map(mapWorkProductRuleMapping) }
}

export async function saveAdminWorkProductRuleMapping(payload: JsonObject = {}) {
  const ruleId = normalizeOptionalId(payload.ruleId ?? payload.rule_id)
  const sellerIds = normalizeKuaishouMatchIds(
    payload.sellerIds ?? payload.seller_ids ?? payload.sellerId ?? payload.seller_id,
  )
  const relItemIds = normalizeKuaishouMatchIdList(payload.relItemId ?? payload.rel_item_id)
  const relItemId = relItemIds.join(',')
  const itemTitle = String(payload.itemTitle ?? payload.item_title ?? '').trim()
  const rawMappingType = String(payload.mappingType ?? payload.mapping_type ?? '').trim()
  const mappingType =
    rawMappingType === 'sku_series'
      ? 'sku_series'
      : rawMappingType === 'sku_exact' || rawMappingType === 'sku_override'
        ? 'sku_exact'
        : 'product_default'
  const relSkuId =
    mappingType === 'sku_exact'
      ? normalizeKuaishouMatchId(payload.relSkuId ?? payload.rel_sku_id)
      : ''
  const skuNick =
    mappingType === 'product_default'
      ? ''
      : String(payload.skuNick ?? payload.sku_nick ?? '').trim()
  const skuMatchMode =
    mappingType === 'sku_series' &&
    String(payload.skuMatchMode ?? payload.sku_match_mode ?? '').trim() === 'exact'
      ? 'exact'
      : 'fuzzy'
  const requiresProductScope = mappingType === 'product_default'
  if (
    !ruleId ||
    sellerIds.length === 0 ||
    (requiresProductScope && relItemIds.length === 0 && !itemTitle)
  ) {
    throw createHttpError(
      '请选择接单模板，并填写至少一个店铺；商品默认规则还需填写大标题或关联商品 ID',
      {
        statusCode: 400,
        errorCode: 'work_product_mapping_identity_required',
      },
    )
  }
  if (
    mappingType === 'product_default' &&
    sellerIds.length > 1 &&
    (!itemTitle || relItemIds.length > 0)
  ) {
    throw createHttpError('多店共享规则请填写快手大标题，并留空关联商品 ID', {
      statusCode: 400,
      errorCode: 'work_product_mapping_multi_shop_scope_invalid',
    })
  }
  if (mappingType === 'sku_exact' && !relSkuId && !skuNick) {
    throw createHttpError('SKU 精确规则需要关联 SKU ID 或具体 SKU 名称', {
      statusCode: 400,
      errorCode: 'work_product_mapping_sku_required',
    })
  }
  if (mappingType === 'sku_series' && !skuNick) {
    throw createHttpError('SKU 数量系列需要填写系列名称', {
      statusCode: 400,
      errorCode: 'work_product_mapping_series_required',
    })
  }
  const rule = (await listWorkProductRules()).find((item) => Number(item.id) === ruleId)
  if (!rule) {
    throw createHttpError('接单模板不存在', {
      statusCode: 404,
      errorCode: 'work_product_mapping_rule_not_found',
    })
  }
  const mappingId = normalizeOptionalId(payload.mappingId ?? payload.mapping_id)
  if (normalizeBoolean(payload.enabled, true)) {
    await assertNoConflictingWorkProductRuleMapping({
      mappingId,
      sellerIds,
      relItemId,
      itemTitle,
      relSkuId,
      skuNick,
      mappingType,
    })
  }
  const mapping = await upsertWorkProductRuleMapping({
    mappingId,
    ruleId,
    sellerIds,
    relItemId,
    itemTitle,
    relSkuId,
    skuNick,
    skuMatchMode,
    mappingType,
    enabled: normalizeBoolean(payload.enabled, true),
    now: nowIso(),
  })
  if (!mapping) {
    throw createHttpError('商品映射保存失败', {
      statusCode: 500,
      errorCode: 'work_product_mapping_save_failed',
    })
  }
  return {
    mapping: mapWorkProductRuleMapping({
      ...mapping,
      rule_key: rule.rule_key,
      product_name: rule.product_name,
    }),
  }
}

export async function deleteAdminWorkProductRuleMapping(mappingId: number | string) {
  const normalizedMappingId = normalizeOptionalId(mappingId)
  if (!normalizedMappingId) {
    throw createHttpError('商品映射 ID 不正确', {
      statusCode: 400,
      errorCode: 'work_product_mapping_id_invalid',
    })
  }
  return deleteWorkProductRuleMapping(normalizedMappingId)
}

export async function listAdminKuaishouMatchSources(payload: JsonObject = {}) {
  const limit = Math.min(500, normalizePositiveInteger(payload.limit, 100))
  const sources = await listKuaishouIndustryVoucherWorkOrderSyncSources(limit)
  const grouped = new Map<string, JsonObject>()
  for (const source of sources) {
    const rawPayload = safeParseJson(source.raw_payload_json)
    const body = resolveKuaishouMatchPayload(rawPayload)
    const ext = safeParseJson(body.ext)
    const sellerId = normalizeKuaishouMatchId(body.sellerId)
    const relItemId = normalizeKuaishouMatchId(ext.relItemId)
    const itemTitle = String(body.itemTitle || '').trim()
    const relSkuId = normalizeKuaishouMatchId(ext.relSkuId)
    const skuNick = String(ext.skuNick || '').trim()
    if (!sellerId || (!relItemId && !itemTitle)) continue
    const key = [
      sellerId,
      relItemId,
      normalizeMatchTextForAdmin(itemTitle),
      relSkuId,
      normalizeMatchTextForAdmin(skuNick),
    ].join('|')
    const current = grouped.get(key)
    grouped.set(key, {
      sellerId,
      relItemId,
      itemTitle,
      relSkuId,
      skuNick,
      sampleOid: String(body.oid || source.oid || '').trim(),
      lastSeenAt: source.updated_at,
      seenCount: Number(current?.seenCount || 0) + 1,
      rawPayload,
    })
  }
  return {
    items: Array.from(grouped.values()).sort((left, right) =>
      String(right.lastSeenAt).localeCompare(String(left.lastSeenAt)),
    ),
  }
}

export async function testAdminKuaishouProductMatch(payload: JsonObject = {}) {
  const body = resolveKuaishouMatchPayload(payload.rawPayload ?? payload.raw_payload ?? payload)
  const ext = safeParseJson(body.ext)
  const order = {
    provider: '91kaquan',
    platform: 'kuaishou',
    shop_id: String(body.sellerId || '').trim(),
  } as OrderRow
  const item = {
    id: 0,
    order_id: 0,
    sku_code: String(body.itemId || '').trim(),
    sku_name: String(ext.skuNick || '').trim(),
    quantity: Math.max(1, Number(body.num) || 1),
    spec_json: {},
    item_snapshot_json: {
      kuaishouSendCode: {
        sellerId: String(body.sellerId || '').trim(),
        itemId: String(body.itemId || '').trim(),
        itemTitle: String(body.itemTitle || '').trim(),
        skuId: String(body.skuId || '').trim(),
        relItemId: normalizeKuaishouMatchId(ext.relItemId),
        relSkuId: normalizeKuaishouMatchId(ext.relSkuId),
        skuNick: String(ext.skuNick || '').trim(),
      },
    },
  } as OrderItemRow
  const [rules, mappings] = await Promise.all([
    listWorkProductRules({ enabled: true }),
    listWorkProductRuleMappings({ enabled: true }),
  ])
  const decision = resolveMatchingProductRuleDecision(order, item, rules, mappings, {
    legacyFallbackEnabled: getWorkerProductMatchConfig().legacyFallbackEnabled,
  })
  const context = resolveKuaishouWorkProductMatchContext(item)
  return {
    context,
    status: decision.reason,
    mappingId: decision.mappingId,
    rule: decision.rule ? mapWorkProductRule(decision.rule) : null,
    candidates: decision.candidates,
  }
}

export async function listAdminWorkProductMatchLogs(payload: JsonObject = {}) {
  const page = normalizePage(payload.page)
  const pageSize = Math.min(200, normalizePageSize(payload.pageSize))
  const { items, total } = await listWorkProductMatchLogs({
    page,
    pageSize,
    matchStatus: String(payload.matchStatus || payload.status || '').trim(),
    keyword: String(payload.keyword || '').trim(),
    createdFrom: normalizeDateString(payload.createdFrom),
    createdTo: normalizeDateString(payload.createdTo),
  })
  return { items: items.map(mapWorkProductMatchLog), pagination: { page, pageSize, total } }
}

function mapWorkProductRuleMapping(mapping: WorkProductRuleMappingRow) {
  const sellerIds = parseKuaishouMappingSellerIds(mapping.seller_ids_json)
  return {
    mappingId: Number(mapping.id),
    ruleId: Number(mapping.rule_id),
    ruleKey: String(mapping.rule_key || ''),
    productName: String(mapping.product_name || ''),
    sellerIds,
    sellerId: sellerIds[0] || '',
    relItemId: String(mapping.rel_item_id || ''),
    itemTitle: String(mapping.item_title || ''),
    relSkuId: String(mapping.rel_sku_id || ''),
    skuNick: String(mapping.sku_nick || ''),
    skuMatchMode: mapping.sku_match_mode === 'exact' ? 'exact' : 'fuzzy',
    mappingType:
      mapping.mapping_type === 'sku_series'
        ? 'sku_series'
        : mapping.mapping_type === 'sku_exact' || mapping.mapping_type === 'sku_override'
          ? 'sku_exact'
          : 'product_default',
    enabled: mapping.enabled === true,
    createdAt: mapping.created_at,
    updatedAt: mapping.updated_at,
  }
}

function mapWorkProductMatchLog(log: WorkProductMatchLogRow) {
  return {
    logId: Number(log.id),
    orderId: log.order_id ? Number(log.order_id) : null,
    orderItemId: log.order_item_id ? Number(log.order_item_id) : null,
    source: String(log.source || ''),
    sellerId: String(log.seller_id || ''),
    relItemId: String(log.rel_item_id || ''),
    itemTitle: String(log.item_title || ''),
    relSkuId: String(log.rel_sku_id || ''),
    skuNick: String(log.sku_nick || ''),
    status: String(log.match_status || ''),
    ruleId: log.rule_id ? Number(log.rule_id) : null,
    mappingId: log.mapping_id ? Number(log.mapping_id) : null,
    ruleKey: String(log.rule_key || ''),
    productName: String(log.product_name || ''),
    candidates: parseJsonArray(log.candidates_json),
    rawPayload: safeParseJson(log.raw_payload_json),
    createdAt: log.created_at,
  }
}

function resolveKuaishouMatchPayload(value: unknown): JsonObject {
  const raw = safeParseJson(value)
  const body = safeParseJson(raw.body)
  return Object.keys(body).length > 0 ? body : raw
}

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

function normalizeKuaishouMatchIds(value: unknown) {
  const values = Array.isArray(value)
    ? value
    : String(value ?? '')
        .split(/[，,\n]/)
        .map((item) => item.trim())
  return Array.from(new Set(values.map(normalizeKuaishouMatchId).filter(Boolean)))
}

function normalizeKuaishouMatchIdList(value: unknown) {
  const values = Array.isArray(value) ? value : [value]
  return normalizeKuaishouMatchIds(
    values.flatMap((item) => String(item ?? '').split(/[，,;；\s\n\r]+/)),
  )
}

function parseKuaishouMappingSellerIds(value: unknown) {
  if (Array.isArray(value)) return normalizeKuaishouMatchIds(value)
  try {
    return normalizeKuaishouMatchIds(JSON.parse(String(value || '[]')))
  } catch {
    return normalizeKuaishouMatchIds(value)
  }
}

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

function assertNoConflictingWorkProductRuleMapping(input: {
  mappingId: number | null
  sellerIds: string[]
  relItemId: string
  itemTitle: string
  relSkuId: string
  skuNick: string
  mappingType: 'product_default' | 'sku_exact' | 'sku_series'
}) {
  return listWorkProductRuleMappings({ enabled: true }).then((mappings) => {
    const conflict = mappings.find((mapping) => {
      if (input.mappingId && Number(mapping.id) === input.mappingId) return false
      const existingType =
        mapping.mapping_type === 'sku_series'
          ? 'sku_series'
          : mapping.mapping_type === 'sku_exact' || mapping.mapping_type === 'sku_override'
            ? 'sku_exact'
            : 'product_default'
      if (existingType !== input.mappingType) return false
      const existingSellerIds = parseKuaishouMappingSellerIds(mapping.seller_ids_json)
      if (!existingSellerIds.some((sellerId) => input.sellerIds.includes(sellerId))) return false

      const inputItemIds = normalizeKuaishouMatchIdList(input.relItemId)
      const existingItemIds = normalizeKuaishouMatchIdList(mapping.rel_item_id)
      if (
        !mappingProductScopesOverlap(
          inputItemIds,
          input.itemTitle,
          existingItemIds,
          mapping.item_title,
        )
      ) {
        return false
      }

      if (input.mappingType === 'product_default') return true
      if (input.mappingType === 'sku_series') {
        // 精确系列是模糊系列的子集，两者并存仍可能产生同分冲突，因此统一拦截。
        return (
          normalizeMatchTextForAdmin(input.skuNick) === normalizeMatchTextForAdmin(mapping.sku_nick)
        )
      }

      const inputSkuId = normalizeKuaishouMatchId(input.relSkuId)
      const existingSkuId = normalizeKuaishouMatchId(mapping.rel_sku_id)
      const skuIdConflict = Boolean(inputSkuId && existingSkuId && inputSkuId === existingSkuId)
      const skuNameConflict =
        Boolean(input.skuNick && mapping.sku_nick) &&
        normalizeMatchTextForAdmin(input.skuNick) === normalizeMatchTextForAdmin(mapping.sku_nick)
      return skuIdConflict || skuNameConflict
    })

    if (conflict) {
      throw createHttpError('商品映射与已有启用映射冲突，请停用或调整其中一条', {
        statusCode: 409,
        errorCode: 'work_product_mapping_conflict',
        context: {
          mappingId: Number(conflict.id),
          ruleKey: String(conflict.rule_key || ''),
        },
      })
    }
  })
}

function mappingProductScopesOverlap(
  leftItemIds: string[],
  leftTitle: string,
  rightItemIds: string[],
  rightTitle: string,
) {
  if (leftItemIds.length > 0 && rightItemIds.length > 0) {
    return leftItemIds.some((itemId) => rightItemIds.includes(itemId))
  }
  // 只有一侧指定商品 ID 时，另一侧可能是全商品范围，按可能重叠处理并拦截。
  if (leftItemIds.length > 0 || rightItemIds.length > 0) return true
  const normalizedLeftTitle = normalizeMatchTextForAdmin(leftTitle)
  const normalizedRightTitle = normalizeMatchTextForAdmin(rightTitle)
  if (normalizedLeftTitle || normalizedRightTitle) {
    return Boolean(
      normalizedLeftTitle && normalizedRightTitle && normalizedLeftTitle === normalizedRightTitle,
    )
  }
  return true
}

function parseJsonArray(value: unknown): unknown[] {
  if (Array.isArray(value)) return value
  try {
    const parsed = JSON.parse(String(value || '[]'))
    return Array.isArray(parsed) ? parsed : []
  } catch {
    return []
  }
}
