import { listKuaishouCloudSourceLoadStats } from '../../../repositories/kuaishou-cloud-task-state-repo.js'
import {
  KUAISHOU_CLOUD_FIXED_VN_KEY,
  normalizeKuaishouCloudFlow,
  normalizeStringArray,
  type JsonObject,
} from './domain.js'
import { resolvePersistedCloudtentaclesContextWithFallback } from './cloudtentacles-context.js'
import { listCloudtentaclesVirtualNumbers } from '../../platforms/cloudtentacles/virtual-number-service.js'
import { createHttpError } from '../../../utils/http.js'

type CloudtentaclesContext = ReturnType<typeof resolvePersistedCloudtentaclesContextWithFallback>

type CloudtentaclesSourceSelectionDeps = {
  resolveContextBySourceKeys?: typeof resolvePersistedCloudtentaclesContextWithFallback
  listSourceLoadStats?: typeof listKuaishouCloudSourceLoadStats
  listVirtualNumbers?: typeof listCloudtentaclesVirtualNumbers
}

export type CloudtentaclesSourceSelection = {
  context: CloudtentaclesContext
  sourceKey: string
  candidateCount: number
  activeCount: number
  redeemingCount: number
  inProcessCount: number
  score: number
  occupiedCount: number
  capacity: number
  availableCount: number
  fixed: boolean
  release: () => void
}

const inProcessSelections = new Map<string, number>()
let selectionLockTail: Promise<void> = Promise.resolve()

export async function selectCloudtentaclesSourceForFulfillment(
  flowLike: unknown,
  deps: CloudtentaclesSourceSelectionDeps = {},
): Promise<CloudtentaclesSourceSelection> {
  const flow = normalizeKuaishouCloudFlow(flowLike)
  const resolveContext =
    deps.resolveContextBySourceKeys || resolvePersistedCloudtentaclesContextWithFallback
  const listSourceLoadStats = deps.listSourceLoadStats || listKuaishouCloudSourceLoadStats
  const listVirtualNumbers = deps.listVirtualNumbers || listCloudtentaclesVirtualNumbers
  const persistedSourceKey = String(flow.binding.resolvedSourceKey || '').trim()
  const fixedSourceKey = flow.binding.vnId > 0 ? persistedSourceKey : ''
  const excludedSourceKeys = new Set(normalizeStringArray((flow as JsonObject).excludedSourceKeys))
  const candidates = normalizeCloudtentaclesSourceCandidates(
    fixedSourceKey
      ? [fixedSourceKey, ...flow.binding.cloudSourceKeys]
      : [...flow.binding.cloudSourceKeys, persistedSourceKey],
  ).filter((key) => !excludedSourceKeys.has(key))

  if (fixedSourceKey) {
    const context = resolveContext([fixedSourceKey, ...flow.binding.cloudSourceKeys])
    return {
      context,
      sourceKey: context.resolvedSourceKey,
      candidateCount: candidates.length,
      activeCount: 0,
      redeemingCount: 0,
      inProcessCount: 0,
      score: 0,
      occupiedCount: 0,
      capacity: 20,
      availableCount: 20,
      fixed: true,
      release: () => {},
    }
  }

  return withCloudtentaclesSourceSelectionLock(async () => {
    const contexts = []
    let lastError: unknown = null

    for (const sourceKey of candidates) {
      try {
        contexts.push(resolveContext([sourceKey]))
      } catch (error) {
        lastError = error
      }
    }

    if (contexts.length === 0) {
      if (lastError) {
        throw lastError
      }
      resolveContext(candidates)
      throw new Error('所有 cloudtentacles 账号均不可用')
    }

    const stats = await listSourceLoadStats(contexts.map((context) => context.resolvedSourceKey))
    const statsBySourceKey = new Map(stats.map((stat) => [stat.sourceKey, stat]))
    const ranked = []
    let lastCapacityError: unknown = null
    for (const [index, context] of contexts.entries()) {
      let occupiedCount
      try {
        // CT's VN list is the authoritative count; DB counters can lag after crashes/restarts.
        const listed = await listVirtualNumbers({
          ...context,
          key: KUAISHOU_CLOUD_FIXED_VN_KEY,
          sourceKey: context.resolvedSourceKey,
          accountLabel: context.accountLabel,
        })
        occupiedCount = Number(listed.occupiedItemCount || 0) || 0
      } catch (error) {
        lastCapacityError = error
        continue
      }
      const capacity = 20
      const inProcessCount = normalizeCount(inProcessSelections.get(context.resolvedSourceKey))
      if (occupiedCount + inProcessCount >= capacity) continue
      const stat = statsBySourceKey.get(context.resolvedSourceKey) || {
        sourceKey: context.resolvedSourceKey,
        activeCount: 0,
        redeemingCount: 0,
      }
      const activeCount = normalizeCount(stat.activeCount)
      const redeemingCount = normalizeCount(stat.redeemingCount)
      const score = occupiedCount + inProcessCount

      ranked.push({
        context,
        sourceKey: context.resolvedSourceKey,
        index,
        activeCount,
        redeemingCount,
        inProcessCount,
        score,
        occupiedCount,
        capacity,
        availableCount: Math.max(0, capacity - occupiedCount - inProcessCount),
      })
    }
    if (ranked.length === 0 && lastCapacityError && contexts.length > 0) throw lastCapacityError
    ranked.sort(
      (left, right) =>
        left.score - right.score ||
        left.redeemingCount - right.redeemingCount ||
        left.activeCount - right.activeCount ||
        left.index - right.index ||
        left.sourceKey.localeCompare(right.sourceKey),
    )

    const selected = ranked[0]
    if (!selected) {
      throw createHttpError('所有 cloudtentacles 账号虚拟号配额均已满', {
        statusCode: 409,
        errorCode: 'cloudtentacles_all_sources_at_capacity',
      })
    }

    reserveCloudtentaclesSource(selected.sourceKey)

    return {
      context: selected.context,
      sourceKey: selected.sourceKey,
      candidateCount: contexts.length,
      activeCount: selected.activeCount,
      redeemingCount: selected.redeemingCount,
      inProcessCount: selected.inProcessCount,
      score: selected.score,
      occupiedCount: selected.occupiedCount,
      capacity: selected.capacity,
      availableCount: selected.availableCount,
      fixed: false,
      release: () => releaseCloudtentaclesSource(selected.sourceKey),
    }
  })
}

export function buildCloudtentaclesSourceSelectionEventDetail(
  selection: CloudtentaclesSourceSelection,
): JsonObject {
  return {
    sourceKey: selection.sourceKey,
    candidateCount: selection.candidateCount,
    activeCount: selection.activeCount,
    redeemingCount: selection.redeemingCount,
    inProcessCount: selection.inProcessCount,
    score: selection.score,
    occupiedCount: selection.occupiedCount,
    capacity: selection.capacity,
    availableCount: selection.availableCount,
    fixed: selection.fixed,
  }
}

function normalizeCloudtentaclesSourceCandidates(sourceKeys: unknown[] = []) {
  return Array.from(new Set(normalizeStringArray(sourceKeys)))
}

async function withCloudtentaclesSourceSelectionLock<T>(fn: () => Promise<T>): Promise<T> {
  let releaseLock: () => void = () => {}
  const waitForTurn = selectionLockTail
  selectionLockTail = new Promise<void>((resolve) => {
    releaseLock = resolve
  })

  await waitForTurn

  try {
    return await fn()
  } finally {
    releaseLock()
  }
}

function reserveCloudtentaclesSource(sourceKey: string) {
  inProcessSelections.set(sourceKey, normalizeCount(inProcessSelections.get(sourceKey)) + 1)
}

function releaseCloudtentaclesSource(sourceKey: string) {
  const nextCount = normalizeCount(inProcessSelections.get(sourceKey)) - 1
  if (nextCount > 0) {
    inProcessSelections.set(sourceKey, nextCount)
    return
  }

  inProcessSelections.delete(sourceKey)
}

function normalizeCount(value: unknown) {
  const count = Number(value || 0) || 0
  return Number.isFinite(count) && count > 0 ? count : 0
}
