import type { JsonObject } from '../../types/json.js'
import { listCloudtentaclesSources } from '../platforms/cloudtentacles/source-config-service.js'
import { getCloudtentaclesSessionStateByKey } from '../platforms/cloudtentacles/session-state-service.js'
import {
  normalizeCloudtentaclesDeviceId,
  normalizeCloudtentaclesDeviceType,
} from '../platforms/cloudtentacles/defaults.js'
import { listCloudtentaclesSku } from '../platforms/cloudtentacles/catalog-service.js'
import {
  resolveCloudtentaclesOverrideRule,
  type CloudtentaclesOverrideRule,
} from './cloudtentacles-override-rule-service.js'
import { normalizeCloudtentaclesMatchName } from './cloudtentacles-match-utils.js'

type CloudtentaclesSource = {
  key?: string
  label?: string
  enabled?: boolean
  baseUrl?: string
  deviceId?: string
  deviceType?: number
}

type CloudtentaclesSession = {
  token?: string
  baseUrl?: string
  deviceId?: string
  deviceType?: number
}

type CloudtentaclesSku = {
  id?: number
  name?: string
  inventory?: number
  price?: number
  [key: string]: unknown
}

type CloudtentaclesNameMatchDeps = {
  listCloudtentaclesSources?: typeof listCloudtentaclesSources
  getCloudtentaclesSessionStateByKey?: typeof getCloudtentaclesSessionStateByKey
  listCloudtentaclesSku?: typeof listCloudtentaclesSku
  resolveCloudtentaclesOverrideRule?: typeof resolveCloudtentaclesOverrideRule
}

export type CloudtentaclesResolvedDeliveryItem = {
  cloudSkuId: number
  cloudSkuName: string
  quantity: number
}

export type CloudtentaclesNameMatchResult = {
  matchMode: 'cloudtentacles_name' | 'cloudtentacles_override'
  productName: string
  normalizedProductName: string
  cloudSkuId: number
  cloudSkuName: string
  cloudSkuPrice: number
  cloudSkuInventory: number
  cloudSourceKeys: string[]
  resolvedSourceKey: string
  deliveryItems: CloudtentaclesResolvedDeliveryItem[]
  skuSnapshot: JsonObject
}

export async function resolveCloudtentaclesSkuByProductName(
  productName: unknown,
  deps: CloudtentaclesNameMatchDeps = {},
): Promise<CloudtentaclesNameMatchResult | null> {
  const normalizedProductName = normalizeCloudtentaclesMatchName(productName)
  if (!normalizedProductName) {
    return null
  }

  const listSources = deps.listCloudtentaclesSources || listCloudtentaclesSources
  const getSessionByKey =
    deps.getCloudtentaclesSessionStateByKey || getCloudtentaclesSessionStateByKey
  const listSku = deps.listCloudtentaclesSku || listCloudtentaclesSku
  const resolveOverrideRule =
    deps.resolveCloudtentaclesOverrideRule || resolveCloudtentaclesOverrideRule
  const sourcesConfig = listSources()

  if (sourcesConfig.enabled === false) {
    return null
  }

  const sourceContexts = (Array.isArray(sourcesConfig.sources) ? sourcesConfig.sources : [])
    .map((source: CloudtentaclesSource) =>
      buildCloudtentaclesSourceContext(source, getSessionByKey),
    )
    .filter(
      (context): context is NonNullable<ReturnType<typeof buildCloudtentaclesSourceContext>> =>
        Boolean(context),
    )

  if (sourceContexts.length === 0) {
    return null
  }

  const overrideRule = resolveOverrideRule(productName)
  const overrideMatch = overrideRule
    ? await resolveOverrideMatch(
        productName,
        normalizedProductName,
        overrideRule,
        sourceContexts,
        listSku,
      )
    : null
  if (overrideMatch) {
    return overrideMatch
  }

  const cloudSourceKeys = sourceContexts.map((context) => context.sourceKey)

  for (const context of sourceContexts) {
    try {
      const skuList = await listSku(context)
      const matchedSku = findCloudtentaclesSkuByName(
        Array.isArray(skuList.items) ? skuList.items : [],
        productName,
      )

      if (!matchedSku) {
        continue
      }

      const cloudSkuId = Number(matchedSku.id || 0)
      const cloudSkuName = String(matchedSku.name || '').trim()
      if (!Number.isInteger(cloudSkuId) || cloudSkuId <= 0 || !cloudSkuName) {
        continue
      }

      return {
        matchMode: 'cloudtentacles_name',
        productName: String(productName || '').trim(),
        normalizedProductName,
        cloudSkuId,
        cloudSkuName,
        cloudSkuPrice: Number(matchedSku.price || 0) || 0,
        cloudSkuInventory: Number(matchedSku.inventory || 0) || 0,
        cloudSourceKeys,
        // Matching only discovers compatible accounts. The fulfillment selector
        // chooses the least-loaded account immediately before taking a number.
        resolvedSourceKey: '',
        deliveryItems: [
          {
            cloudSkuId,
            cloudSkuName,
            quantity: 1,
          },
        ],
        skuSnapshot: { ...matchedSku },
      }
    } catch {
      // 当前账号不可用时继续尝试下一个已登录账号。
    }
  }

  return null
}

function buildCloudtentaclesSourceContext(
  source: CloudtentaclesSource,
  getSessionByKey: typeof getCloudtentaclesSessionStateByKey,
) {
  const sourceKey = String(source.key || '').trim()
  if (!sourceKey || source.enabled === false) {
    return null
  }

  const session = getSessionByKey(sourceKey) as CloudtentaclesSession | null
  const token = String(session?.token || '').trim()
  if (!token) {
    return null
  }

  return {
    sourceKey,
    accountLabel: String(source.label || sourceKey).trim() || sourceKey,
    baseUrl: String(session?.baseUrl || source.baseUrl || '').trim(),
    token,
    deviceId: normalizeCloudtentaclesDeviceId(session?.deviceId || source.deviceId),
    deviceType: normalizeCloudtentaclesDeviceType(session?.deviceType ?? source.deviceType),
  }
}

function findCloudtentaclesSkuByName(items: CloudtentaclesSku[], productName: unknown) {
  const rawName = String(productName || '').trim()
  const normalizedName = normalizeCloudtentaclesMatchName(rawName)

  return (
    items.find((item) => String(item.name || '').trim() === rawName) ||
    items.find((item) => normalizeCloudtentaclesMatchName(item.name) === normalizedName) ||
    null
  )
}

async function resolveOverrideMatch(
  productName: unknown,
  normalizedProductName: string,
  rule: CloudtentaclesOverrideRule,
  sourceContexts: Array<NonNullable<ReturnType<typeof buildCloudtentaclesSourceContext>>>,
  listSku: typeof listCloudtentaclesSku,
): Promise<CloudtentaclesNameMatchResult | null> {
  const candidates = rule.sourceKey
    ? sourceContexts.filter((context) => context.sourceKey === rule.sourceKey)
    : sourceContexts

  if (candidates.length === 0) {
    return null
  }

  for (const context of candidates) {
    try {
      const skuList = await listSku(context)
      const items = Array.isArray(skuList.items) ? skuList.items : []
      const deliveryItems = resolveOverrideDeliveryItems(rule, items)

      if (deliveryItems.length !== rule.deliveryItems.length || !deliveryItems[0]) {
        continue
      }

      const firstDeliveryItem = deliveryItems[0]
      const firstSku =
        items.find((item) => Number(item.id || 0) === firstDeliveryItem.cloudSkuId) || {}
      const cloudSourceKeys = rule.sourceKey
        ? [context.sourceKey]
        : sourceContexts.map((sourceContext) => sourceContext.sourceKey)

      return {
        matchMode: 'cloudtentacles_override',
        productName: String(productName || '').trim(),
        normalizedProductName,
        cloudSkuId: firstDeliveryItem.cloudSkuId,
        cloudSkuName: firstDeliveryItem.cloudSkuName,
        cloudSkuPrice: Number(firstSku.price || 0) || 0,
        cloudSkuInventory: Number(firstSku.inventory || 0) || 0,
        cloudSourceKeys,
        resolvedSourceKey: rule.sourceKey ? context.sourceKey : '',
        deliveryItems,
        skuSnapshot: {
          overrideRuleId: rule.id,
          overrideRuleProductName: rule.productName,
          deliveryItems,
          firstSku,
        },
      }
    } catch {
      // 当前账号不可用时继续尝试下一个已登录账号。
    }
  }

  return null
}

function resolveOverrideDeliveryItems(
  rule: CloudtentaclesOverrideRule,
  skuItems: CloudtentaclesSku[],
) {
  const resolvedItems: CloudtentaclesResolvedDeliveryItem[] = []

  for (const ruleItem of rule.deliveryItems) {
    const matchedSku =
      ruleItem.cloudSkuId > 0
        ? skuItems.find((item) => Number(item.id || 0) === ruleItem.cloudSkuId)
        : findCloudtentaclesSkuByName(skuItems, ruleItem.cloudSkuName)
    const cloudSkuId = Number(matchedSku?.id || 0)
    const cloudSkuName = String(matchedSku?.name || '').trim()

    if (!Number.isInteger(cloudSkuId) || cloudSkuId <= 0 || !cloudSkuName) {
      return []
    }

    resolvedItems.push({
      cloudSkuId,
      cloudSkuName,
      quantity: Math.max(1, Number(ruleItem.quantity || 1)),
    })
  }

  return mergeResolvedDeliveryItems(resolvedItems)
}

function mergeResolvedDeliveryItems(items: CloudtentaclesResolvedDeliveryItem[]) {
  const merged = new Map<number, CloudtentaclesResolvedDeliveryItem>()

  for (const item of items) {
    const existing = merged.get(item.cloudSkuId)
    if (existing) {
      existing.quantity += item.quantity
      existing.cloudSkuName = existing.cloudSkuName || item.cloudSkuName
      continue
    }

    merged.set(item.cloudSkuId, { ...item })
  }

  return Array.from(merged.values())
}

export { normalizeCloudtentaclesMatchName }
