import { reloadTencentBrowserSession, redeemTencentBrowserSession } from '../../session/session.js'
import { classifyTencentRedeemResult } from '../../session/session-redeem.js'
import { createTaskEvent } from '../../../repositories/task-event-repo.js'
import {
  invalidateReservedInventoryItem,
  markInventoryItemConsumed,
} from '../../../repositories/inventory-repo.js'
import { reserveInventoryForTask } from '../../order/inventory-service.js'
import { createHttpError } from '../../../utils/http.js'
import { nowIso } from '../../../utils/time.js'
import { maskCode, REDEEM_REPLACEMENT_LIMIT } from './shared.js'
import type { InventoryItemRow, OrderItemRow, TaskRow } from '../../../types/repository-rows.js'

type ClaimRedeemContext = {
  task: Pick<TaskRow, 'id' | 'browser_session_id'>
  orderItem: Pick<OrderItemRow, 'sku_code'>
}

type TencentRedeemPayload = {
  [key: string]: unknown
  iRet?: unknown
  ret?: unknown
}

type TencentRedeemSession = {
  [key: string]: unknown
  sessionId?: string
  artifacts?: {
    [key: string]: unknown
    hasScreenshot?: boolean
  } | null
  redeem?: {
    final?: {
      redeem?: TencentRedeemPayload | null
    } | null
  } | null
}

type TencentRedeemClassification = {
  success: boolean
  outcome: string
  message: string
  retCode?: unknown
  [key: string]: unknown
}

type RedeemAttemptSummary = {
  attempt: number
  inventoryItemId: number
  codeMasked: string
  credentialType: string
  outcome: string
  resultCode: string
  resultMessage: string
}

type RedeemTaskState = {
  taskStatus: string
  inventoryStatus: string
  deliveryStatus: string
  lastError: string
  attempts: RedeemAttemptSummary[]
  classification: TencentRedeemClassification | null
}

type RedeemTaskStateError = Error & {
  statusCode?: number
  errorCode?: string
  redeemTaskState: RedeemTaskState
}

type RedeemTaskStateErrorPayload = {
  statusCode?: number
  errorCode?: string
  taskStatus?: string
  inventoryStatus?: string
  deliveryStatus?: string
  attempts?: RedeemAttemptSummary[]
  classification?: TencentRedeemClassification | null
}

type RedeemClaimTaskDeps = {
  reloadTencentBrowserSession?: (sessionId: string) => Promise<unknown>
  redeemTencentBrowserSession?: (
    sessionId: string,
    payload: { code: string },
  ) => Promise<TencentRedeemSession>
  classifyTencentRedeemResult?: (finalRedeem: TencentRedeemPayload | null) => TencentRedeemClassification
  markInventoryItemConsumed?: (
    inventoryItemId: number | string,
    reason: string,
    consumedAt: string,
  ) => Promise<InventoryItemRow | null>
  createTaskEvent?: (
    taskId: number | string,
    eventType: string,
    payload?: unknown,
    createdAt?: string,
  ) => Promise<unknown>
  invalidateReservedInventoryItem?: (
    inventoryItemId: number | string,
    reason: string,
    updatedAt: string,
  ) => Promise<InventoryItemRow | null>
  reserveInventoryForTask?: (payload: {
    skuCode: string
    taskId: number | string
    credentialType?: string
    roleKey?: string
    inventoryGroupCodes?: string[] | null
  }) => Promise<InventoryItemRow | null>
  nowIso?: () => string
  redeemReplacementLimit?: number
}

type RedeemClaimTaskResult = {
  session: TencentRedeemSession
  inventoryItem: InventoryItemRow
  classification: TencentRedeemClassification
  attempts: RedeemAttemptSummary[]
}

export async function redeemClaimTaskWithInventoryFallback(
  context: ClaimRedeemContext,
  initialInventoryItem: InventoryItemRow,
): Promise<RedeemClaimTaskResult> {
  return redeemClaimTaskWithInventoryFallbackWithDeps(context, initialInventoryItem)
}

export async function redeemClaimTaskWithInventoryFallbackWithDeps(
  context: ClaimRedeemContext,
  initialInventoryItem: InventoryItemRow,
  {
    reloadTencentBrowserSession: reloadRedeemSession = reloadTencentBrowserSession,
    redeemTencentBrowserSession: redeemSession = redeemTencentBrowserSession,
    classifyTencentRedeemResult: classifyRedeemResult = classifyTencentRedeemResult,
    markInventoryItemConsumed: markConsumedInventoryItem = markInventoryItemConsumed,
    createTaskEvent: createRedeemTaskEvent = createTaskEvent,
    invalidateReservedInventoryItem: invalidateReservedItem = invalidateReservedInventoryItem,
    reserveInventoryForTask: reserveReplacementInventory = reserveInventoryForTask,
    nowIso: getNowIso = nowIso,
    redeemReplacementLimit = REDEEM_REPLACEMENT_LIMIT,
  }: RedeemClaimTaskDeps = {},
): Promise<RedeemClaimTaskResult> {
  const attempts: RedeemAttemptSummary[] = []
  let currentInventoryItem = initialInventoryItem
  let shouldReloadBeforeNextAttempt = false

  for (let attemptIndex = 1; attemptIndex <= redeemReplacementLimit; attemptIndex += 1) {
    if (shouldReloadBeforeNextAttempt) {
      await reloadRedeemSession(context.task.browser_session_id)
      shouldReloadBeforeNextAttempt = false
    }

    const session = await redeemSession(context.task.browser_session_id, {
      code: currentInventoryItem.display_value,
    })
    const finalRedeem = session.redeem?.final?.redeem || null
    const classification = classifyRedeemResult(finalRedeem)
    const attemptSummary = {
      attempt: attemptIndex,
      inventoryItemId: currentInventoryItem.id,
      codeMasked: maskCode(currentInventoryItem.display_value),
      credentialType: String(currentInventoryItem.credential_type || ''),
      outcome: classification.outcome,
      resultCode: resolveTencentRedeemResultCode(finalRedeem, classification),
      resultMessage: classification.message,
    }

    attempts.push(attemptSummary)

    if (classification.success) {
      return {
        session,
        inventoryItem: currentInventoryItem,
        classification,
        attempts,
      }
    }

    if (classification.outcome === 'code_used') {
      const updatedAt = getNowIso()
      await markConsumedInventoryItem(currentInventoryItem.id, classification.message, updatedAt)
      await createRedeemTaskEvent(context.task.id, 'claim_redeem_code_used', {
        inventoryItemId: currentInventoryItem.id,
        codeMasked: attemptSummary.codeMasked,
        resultCode: attemptSummary.resultCode,
        resultMessage: classification.message,
      }, updatedAt)
    } else if (classification.outcome === 'code_invalid') {
      const updatedAt = getNowIso()
      await invalidateReservedItem(currentInventoryItem.id, classification.message, updatedAt)
      await createRedeemTaskEvent(context.task.id, 'claim_redeem_code_invalid', {
        inventoryItemId: currentInventoryItem.id,
        codeMasked: attemptSummary.codeMasked,
        resultCode: attemptSummary.resultCode,
        resultMessage: classification.message,
      }, updatedAt)
    } else {
      throw createRedeemTaskStateError(classification.message, {
        errorCode: 'claim_redeem_failed',
        taskStatus: 'retry_pending',
        inventoryStatus: 'reserved',
        deliveryStatus: 'pending',
        attempts,
        classification,
      })
    }

    if (attemptIndex >= redeemReplacementLimit) {
      throw createRedeemTaskStateError('连续更换兑换码后仍未成功，请联系人工处理', {
        errorCode: 'claim_redeem_replacement_limit_reached',
        taskStatus: 'retry_pending',
        inventoryStatus: 'pending',
        deliveryStatus: 'pending',
        attempts,
        classification,
      })
    }

    const replacement = await reserveReplacementInventory({
      skuCode: context.orderItem.sku_code,
      taskId: context.task.id,
      credentialType: currentInventoryItem.credential_type || 'tencent_code',
      roleKey: 'primary_code',
      inventoryGroupCodes: currentInventoryItem.inventory_group_code
        ? [String(currentInventoryItem.inventory_group_code).trim()]
        : null,
    })

    if (!replacement || !String(replacement.display_value || '').trim()) {
      const exhaustedMessage = classification.outcome === 'code_used'
        ? '兑换码已使用，且没有更多同类型可用 CDK 可继续重试'
        : '兑换码错误，请确认库存数据；当前没有更多同类型可用 CDK 可继续重试'

      throw createRedeemTaskStateError(exhaustedMessage, {
        errorCode: 'claim_inventory_replacement_exhausted',
        taskStatus: 'waiting_inventory',
        inventoryStatus: 'pending',
        deliveryStatus: 'pending',
        attempts,
        classification,
      })
    }

    const replacedAt = getNowIso()
    await createRedeemTaskEvent(context.task.id, 'claim_redeem_inventory_replaced', {
      previousInventoryItemId: currentInventoryItem.id,
      previousCodeMasked: attemptSummary.codeMasked,
      previousOutcome: classification.outcome,
      nextInventoryItemId: replacement.id,
      nextCodeMasked: maskCode(replacement.display_value),
      credentialType: String(replacement.credential_type || currentInventoryItem.credential_type || ''),
      inventoryGroupCode: String(replacement.inventory_group_code || currentInventoryItem.inventory_group_code || '').trim(),
    }, replacedAt)
    currentInventoryItem = replacement
    shouldReloadBeforeNextAttempt = true
  }

  throw createRedeemTaskStateError('兑换失败，请稍后重试', {
    errorCode: 'claim_redeem_failed',
    taskStatus: 'retry_pending',
    inventoryStatus: 'pending',
    deliveryStatus: 'pending',
    attempts,
    classification: null,
  })
}

function createRedeemTaskStateError(
  message: string,
  payload: RedeemTaskStateErrorPayload = {},
): RedeemTaskStateError {
  const error = createHttpError(message, {
    statusCode: payload.statusCode || 409,
    errorCode: payload.errorCode || 'claim_redeem_failed',
  }) as unknown as RedeemTaskStateError

  error.redeemTaskState = {
    taskStatus: payload.taskStatus || 'retry_pending',
    inventoryStatus: payload.inventoryStatus || 'pending',
    deliveryStatus: payload.deliveryStatus || 'pending',
    lastError: String(message || ''),
    attempts: payload.attempts || [],
    classification: payload.classification || null,
  }

  return error
}

export function resolveTencentRedeemResultCode(
  finalRedeem: TencentRedeemPayload | null,
  classification: Pick<TencentRedeemClassification, 'retCode'> | null,
): string {
  if (classification?.retCode != null) {
    return String(classification.retCode)
  }

  const value = finalRedeem?.iRet ?? finalRedeem?.ret
  return value == null ? '' : String(value)
}
