import { resolveFulfillmentBinding } from '../../repositories/fulfillment-profile-repo.js'
import { resolveProductMatchRule } from '../../repositories/product-match-rule-repo.js'

export type FulfillmentItem = {
  itemId?: string
  externalItemId?: string
  skuCode?: string
  skuName?: string
  externalSkuCode?: string
  externalSkuName?: string
  quantity?: number
  spec?: Record<string, unknown>
  snapshot?: Record<string, unknown>
  [key: string]: unknown
}

type ResolveOrderItemForFulfillmentInput = {
  provider?: string
  platform?: string
  shopId?: string
  shopIdAliases?: string[]
  item?: FulfillmentItem
}

type HasConfiguredOrderItemsInput = Omit<ResolveOrderItemForFulfillmentInput, 'item'> & {
  items?: FulfillmentItem[]
}

type FulfillmentItemCandidate = {
  externalItemId: string
  externalSkuCode: string
  externalSkuName: string
  externalSkuNameNormalized: string
  matchedRule: Awaited<ReturnType<typeof resolveProductMatchRule>>
  resolvedSkuCode: string
  binding: Awaited<ReturnType<typeof resolveFulfillmentBinding>>
  isConfigured: boolean
}

export type ResolvedFulfillmentItem = FulfillmentItem & {
  itemId: string
  externalItemId: string
  externalSkuCode: string
  externalSkuName: string
  skuCode: string
  skuName: string
  snapshot: Record<string, unknown>
  isConfigured: boolean
}

type ShopIdCandidatesInput = {
  shopId?: string
  shopIdAliases?: string[]
}

export async function resolveOrderItemForFulfillment({
  provider = '',
  platform = '',
  shopId = '',
  shopIdAliases = [],
  item = {},
}: ResolveOrderItemForFulfillmentInput): Promise<ResolvedFulfillmentItem> {
  const candidate = await resolveConfiguredItemCandidate({
    provider,
    platform,
    shopId,
    shopIdAliases,
    item,
  })
  const {
    externalItemId,
    externalSkuCode,
    externalSkuName,
    externalSkuNameNormalized,
    matchedRule,
    binding,
  } = candidate

  const resolvedSkuCode = pickFirstNonEmpty([
    matchedRule?.resolved_sku_code,
    externalSkuCode,
    externalItemId,
  ])
  const resolvedSkuName = pickFirstNonEmpty([
    readConfigValue(matchedRule?.config_json, 'resolvedSkuName'),
    readConfigValue(matchedRule?.config_json, 'internalProductName'),
    item.skuName,
    externalSkuName,
    resolvedSkuCode,
  ])
  const snapshot = {
    ...(isPlainObject(item.snapshot) ? item.snapshot : {}),
    externalItemId,
    externalSkuCode,
    externalSkuName,
    externalSkuNameNormalized,
    resolvedSkuCode,
    matchedProductRuleId: matchedRule ? Number(matchedRule.id) : null,
    matchedProductRuleBy: String(matchedRule?.matched_by || '').trim(),
    matchedFulfillmentBindingId: binding ? Number(binding.id) : null,
    matchedFulfillmentProfileKey: String(binding?.profile_key || '').trim(),
    isConfigured: candidate.isConfigured,
  }

  return {
    ...item,
    itemId: externalItemId,
    externalItemId,
    externalSkuCode,
    externalSkuName,
    skuCode: resolvedSkuCode,
    skuName: resolvedSkuName,
    snapshot,
    isConfigured: candidate.isConfigured,
  }
}

export async function hasConfiguredOrderItems({
  provider = '',
  platform = '',
  shopId = '',
  shopIdAliases = [],
  items = [],
}: HasConfiguredOrderItemsInput): Promise<boolean> {
  const candidates = await Promise.all(
    (Array.isArray(items) ? items : []).map((item) => resolveConfiguredItemCandidate({
      provider,
      platform,
      shopId,
      shopIdAliases,
      item,
    })),
  )

  return candidates.some((item) => item.isConfigured)
}

export function normalizeProductName(value: unknown): string {
  return String(value || '')
    .toLowerCase()
    .replace(/[【】\[\]（）()]/g, ' ')
    .replace(/(自动发货|秒发|极速发货|官方直充|官方充值)/gi, ' ')
    .replace(/[^\p{L}\p{N}]+/gu, ' ')
    .replace(/\s+/g, ' ')
    .trim()
}

function pickFirstNonEmpty(values: unknown[]): string {
  for (const value of values) {
    const normalized = String(value || '').trim()
    if (normalized) {
      return normalized
    }
  }

  return ''
}

async function resolveConfiguredItemCandidate({
  provider = '',
  platform = '',
  shopId = '',
  shopIdAliases = [],
  item = {},
}: ResolveOrderItemForFulfillmentInput): Promise<FulfillmentItemCandidate> {
  const externalItemId = pickFirstNonEmpty([
    item.externalItemId,
    item.itemId,
  ])
  const externalSkuCode = pickFirstNonEmpty([
    item.externalSkuCode,
    item.skuCode,
    externalItemId,
  ])
  const externalSkuName = pickFirstNonEmpty([
    item.externalSkuName,
    item.skuName,
    externalSkuCode,
  ])
  const externalSkuNameNormalized = normalizeProductName(externalSkuName)
  const shopIdCandidates = resolveShopIdCandidates({ shopId, shopIdAliases })
  let matchedRule = null
  let binding = null

  for (const candidateShopId of shopIdCandidates) {
    const candidateRule = await resolveProductMatchRule({
      provider,
      platform,
      shopId: candidateShopId,
      externalItemId,
      externalSkuCode,
      externalSkuNameNormalized,
    })
    if (candidateRule) {
      matchedRule = candidateRule
    }

    const resolvedSkuCode = pickFirstNonEmpty([
      candidateRule?.resolved_sku_code,
      externalSkuCode,
      externalItemId,
    ])
    binding = resolvedSkuCode
      ? await resolveFulfillmentBinding({
        skuCode: resolvedSkuCode,
        provider,
        platform,
        shopId: candidateShopId,
      })
      : null

    if (binding) {
      break
    }
  }

  const resolvedSkuCode = pickFirstNonEmpty([
    matchedRule?.resolved_sku_code,
    externalSkuCode,
    externalItemId,
  ])

  return {
    externalItemId,
    externalSkuCode,
    externalSkuName,
    externalSkuNameNormalized,
    matchedRule,
    resolvedSkuCode,
    binding,
    isConfigured: Boolean(binding),
  }
}

export function resolveShopIdCandidates({
  shopId = '',
  shopIdAliases = [],
}: ShopIdCandidatesInput = {}): string[] {
  const candidates = [...new Set([
    String(shopId || '').trim(),
    ...(Array.isArray(shopIdAliases) ? shopIdAliases : []).map((item) => String(item || '').trim()),
  ].filter(Boolean))]

  return candidates.length > 0 ? candidates : ['']
}

function readConfigValue(rawValue: unknown, key: string): string {
  if (!rawValue) {
    return ''
  }

  const parsed = typeof rawValue === 'string' ? safeParseJson(rawValue) : rawValue
  if (!isPlainObject(parsed)) {
    return ''
  }

  return String(parsed[key] || '').trim()
}

function safeParseJson(rawValue: unknown): unknown {
  try {
    return JSON.parse(String(rawValue || '{}'))
  } catch {
    return null
  }
}

function isPlainObject(value: unknown): value is Record<string, unknown> {
  return Object.prototype.toString.call(value) === '[object Object]'
}
