import { createTaskEvent } from '../../../repositories/task-event-repo.js'
import { createHttpError } from '../../../utils/http.js'
import { nowIso } from '../../../utils/time.js'
import { notifyKuaishouCloudAssetNotEnough } from '../../notification/domain-notifications.js'
import {
  buyCloudtentaclesSku,
  getCloudtentaclesAsset,
  listCloudtentaclesSku,
  useCloudtentaclesSku,
} from '../../platforms/cloudtentacles/catalog-service.js'
import { getCloudtentaclesKnapsack } from '../../platforms/cloudtentacles/knapsack-service.js'
import type { TaskRow } from '../../../types/repository/rows.js'
import { maskPhone, type JsonObject } from './domain.js'
import {
  enrichCloudtentaclesDispatchError,
  resolveErrorCode,
  resolveErrorMessage,
} from './dispatch-failure.js'
import type {
  DispatchDeliveryItem,
  DispatchResultItem,
  DispatchStockItem,
  DispatchStockResult,
} from './dispatch-types.js'

const cloudSkuDispatchLocks = new Map<string, Promise<void>>()

/**
 * 计算库存缺口 → 必要时采购 → 按数量逐次 use SKU 下发。
 * 调用方应在 withCloudSkuDispatchLocks 内执行，避免同账号同 SKU 并发抢库存。
 */
export async function prepareStockAndDispatch({
  task,
  flow,
  cloudContext,
  deliveryItems,
}: {
  task: TaskRow
  flow: JsonObject
  cloudContext: JsonObject
  deliveryItems: DispatchDeliveryItem[]
}): Promise<{ dispatchResults: DispatchResultItem[]; stockResult: DispatchStockResult }> {
  const [knapsack, skuList] = await Promise.all([
    getCloudtentaclesKnapsack(cloudContext),
    listCloudtentaclesSku(cloudContext),
  ])
  const skuItems = Array.isArray(skuList.items) ? (skuList.items as JsonObject[]) : []
  const knapsackItems = Array.isArray(knapsack.items) ? (knapsack.items as JsonObject[]) : []
  const stockItems = buildDispatchStockItems(deliveryItems, {
    skuItems,
    knapsackItems,
  })
  const missingItems = stockItems.filter((item) => item.purchasedCount > 0)

  let assetBefore = 0
  let assetAfter = 0
  let purchaseTriggered = false

  const purchase = isPlainObject(flow.purchase) ? flow.purchase : {}
  const binding = isPlainObject(flow.binding) ? flow.binding : {}

  if (missingItems.length > 0) {
    if (purchase.autoBuyEnabled === false) {
      throw createHttpError('背包中没有现成库存，且当前配置未开启自动购买', {
        statusCode: 409,
        errorCode: 'kuaishou_cloud_auto_buy_disabled',
      })
    }

    const missingSku = missingItems.find((item) => !findCloudSkuItem(skuItems, item.cloudSkuId))
    if (missingSku) {
      throw createHttpError(`cloudtentacles 未找到 SKU ${missingSku.cloudSkuId}`, {
        statusCode: 404,
        errorCode: 'kuaishou_cloud_sku_not_found',
      })
    }

    const asset = await getCloudtentaclesAsset(cloudContext)
    assetBefore = Number(asset.asset || 0) || 0
    const targetPrice = missingItems.reduce((sum, item) => {
      const skuItem = findCloudSkuItem(skuItems, item.cloudSkuId)
      return sum + item.purchasedCount * Number(skuItem?.price || 0)
    }, 0)
    const requiredAsset = targetPrice + Number(purchase.minAssetReserve || 0)

    if (assetBefore < requiredAsset) {
      await notifyKuaishouCloudAssetNotEnough({
        task,
        flow,
        assetBefore,
        requiredAsset,
        skuName: missingItems
          .map((item) => item.cloudSkuName || `SKU ${item.cloudSkuId}`)
          .join('、'),
      })
      throw createHttpError(`余额不足，当前 ${assetBefore}，至少需要 ${requiredAsset}`, {
        statusCode: 409,
        errorCode: 'kuaishou_cloud_asset_not_enough',
      })
    }

    for (const item of missingItems) {
      await createTaskEvent(
        task.id,
        'cloudtentacles_sku_buy_started',
        {
          source: 'kuaishou_cloud_dispatch',
          cloudSkuId: item.cloudSkuId,
          cloudSkuName: item.cloudSkuName,
          count: item.purchasedCount,
          assetBefore,
        },
        nowIso(),
      )

      try {
        const buyResult = await buyCloudtentaclesSku({
          ...cloudContext,
          id: item.cloudSkuId,
          count: item.purchasedCount,
        })
        await createTaskEvent(
          task.id,
          'cloudtentacles_sku_buy_succeeded',
          {
            source: 'kuaishou_cloud_dispatch',
            cloudSkuId: item.cloudSkuId,
            cloudSkuName: item.cloudSkuName,
            count: item.purchasedCount,
            responseMessage: buyResult.responseMessage,
          },
          nowIso(),
        )
      } catch (error) {
        await createTaskEvent(
          task.id,
          'cloudtentacles_sku_buy_failed',
          {
            source: 'kuaishou_cloud_dispatch',
            cloudSkuId: item.cloudSkuId,
            cloudSkuName: item.cloudSkuName,
            count: item.purchasedCount,
            errorCode: resolveErrorCode(error),
            errorMessage: resolveErrorMessage(error),
          },
          nowIso(),
        )
        throw enrichCloudtentaclesDispatchError(error, {
          stage: 'purchase',
          stockResult: buildCurrentStockResult({
            stockItems,
            purchaseTriggered,
            assetBefore,
            assetAfter,
          }),
          item,
        })
      }
    }

    purchaseTriggered = true
    const assetResult = await getCloudtentaclesAsset(cloudContext)
    assetAfter = Number(assetResult.asset || 0) || 0
  }

  const dispatchResults: DispatchResultItem[] = []
  const vnId = Number(binding.vnId || 0) || 0
  const vnPhone = String(binding.vnPhone || '').trim()

  for (const item of deliveryItems) {
    for (let index = 0; index < item.quantity; index += 1) {
      await createTaskEvent(
        task.id,
        'cloudtentacles_sku_use_started',
        {
          source: 'kuaishou_cloud_dispatch',
          cloudSkuId: item.cloudSkuId,
          cloudSkuName: item.cloudSkuName,
          unitIndex: index + 1,
          quantity: item.quantity,
          vnId,
          vnPhoneMasked: maskPhone(vnPhone),
        },
        nowIso(),
      )

      let dispatchResult: Awaited<ReturnType<typeof useCloudtentaclesSku>>
      try {
        dispatchResult = await useCloudtentaclesSku({
          ...cloudContext,
          id: item.cloudSkuId,
          virtualNumberId: vnId,
          phone: vnPhone,
        })
      } catch (error) {
        await createTaskEvent(
          task.id,
          'cloudtentacles_sku_use_failed',
          {
            source: 'kuaishou_cloud_dispatch',
            cloudSkuId: item.cloudSkuId,
            cloudSkuName: item.cloudSkuName,
            unitIndex: index + 1,
            quantity: item.quantity,
            vnId,
            vnPhoneMasked: maskPhone(vnPhone),
            errorCode: resolveErrorCode(error),
            errorMessage: resolveErrorMessage(error),
          },
          nowIso(),
        )
        throw enrichCloudtentaclesDispatchError(error, {
          stage: 'dispatch',
          stockResult: buildCurrentStockResult({
            stockItems,
            purchaseTriggered,
            assetBefore,
            assetAfter,
          }),
          item,
          unitIndex: index + 1,
          vnId,
          vnPhoneMasked: maskPhone(vnPhone),
        })
      }

      await createTaskEvent(
        task.id,
        'cloudtentacles_sku_use_succeeded',
        {
          source: 'kuaishou_cloud_dispatch',
          cloudSkuId: item.cloudSkuId,
          cloudSkuName: item.cloudSkuName,
          unitIndex: index + 1,
          quantity: item.quantity,
          vnId,
          vnPhoneMasked: maskPhone(vnPhone),
          sendType: Number(dispatchResult.sendType || 0) || 0,
          note: String(dispatchResult.note || '').trim(),
          responseMessage: String(dispatchResult.responseMessage || '').trim(),
        },
        nowIso(),
      )
      dispatchResults.push({
        cloudSkuId: item.cloudSkuId,
        cloudSkuName: item.cloudSkuName,
        unitIndex: index + 1,
        quantity: item.quantity,
        sendType: Number(dispatchResult.sendType || 0) || 0,
        note: String(dispatchResult.note || '').trim(),
        responseMessage: String(dispatchResult.responseMessage || '').trim(),
      })
    }
  }

  return {
    dispatchResults,
    stockResult: {
      usedKnapsack: stockItems.every((item) => item.purchasedCount <= 0),
      purchaseTriggered,
      assetBefore,
      assetAfter,
      items: stockItems,
    },
  }
}

export function buildDispatchStockItems(
  deliveryItems: DispatchDeliveryItem[],
  {
    skuItems = [],
    knapsackItems = [],
  }: { skuItems?: JsonObject[]; knapsackItems?: JsonObject[] } = {},
): DispatchStockItem[] {
  return deliveryItems.map((item) => {
    const knapsackItem = findCloudSkuItem(knapsackItems, item.cloudSkuId)
    const skuItem = findCloudSkuItem(skuItems, item.cloudSkuId)
    const knapsackCount = Math.max(0, Number(knapsackItem?.count || 0) || 0)

    return {
      ...item,
      cloudSkuName: item.cloudSkuName || String(skuItem?.name || knapsackItem?.name || '').trim(),
      requiredCount: item.quantity,
      knapsackCount,
      purchasedCount: Math.max(0, item.quantity - knapsackCount),
    }
  })
}

export function resolveCloudSkuDispatchLockKeys(
  sourceKey: unknown,
  deliveryItems: DispatchDeliveryItem[],
) {
  const normalizedSourceKey = String(sourceKey || 'default').trim() || 'default'
  return deliveryItems.map((item) => `${normalizedSourceKey}:${item.cloudSkuId}`)
}

export async function withCloudSkuDispatchLocks<T>(
  keys: string[],
  callback: () => Promise<T>,
): Promise<T> {
  const normalizedKeys = [
    ...new Set(keys.map((key) => String(key || '').trim()).filter(Boolean)),
  ].sort()

  async function run(index: number): Promise<T> {
    const key = normalizedKeys[index]
    if (!key) {
      return callback()
    }

    return withCloudSkuDispatchLock(key, () => run(index + 1))
  }

  return run(0)
}

function buildCurrentStockResult({
  stockItems,
  purchaseTriggered,
  assetBefore,
  assetAfter,
}: {
  stockItems: DispatchStockItem[]
  purchaseTriggered: boolean
  assetBefore: number
  assetAfter: number
}): DispatchStockResult {
  return {
    usedKnapsack: stockItems.every((item) => item.purchasedCount <= 0),
    purchaseTriggered,
    assetBefore,
    assetAfter,
    items: stockItems,
  }
}

function findCloudSkuItem(items: JsonObject[], cloudSkuId: number) {
  return items.find((item) => Number(item.id || 0) === cloudSkuId) || null
}

async function withCloudSkuDispatchLock<T>(key: string, callback: () => Promise<T>): Promise<T> {
  const previous = cloudSkuDispatchLocks.get(key) || Promise.resolve()
  let release: () => void = () => {}
  const current = new Promise<void>((resolve) => {
    release = resolve
  })
  const queued = previous.catch(() => {}).then(() => current)

  cloudSkuDispatchLocks.set(key, queued)

  await previous.catch(() => {})

  try {
    return await callback()
  } finally {
    release()
    if (cloudSkuDispatchLocks.get(key) === queued) {
      cloudSkuDispatchLocks.delete(key)
    }
  }
}

function isPlainObject(value: unknown): value is JsonObject {
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
}
