import { createTaskEvent } from '../../repositories/task-event-repo.js'
import { getTaskById, updateTask } from '../../repositories/task-repo.js'
import { createHttpError } from '../../utils/http.js'
import { parseTaskContext as parseTaskContextValue } from '../../utils/task-json.js'
import { nowIso } from '../../utils/time.js'
import type { JsonObject } from '../../types/json.js'
import { TASK_STATUS } from '../../domain/task-status.js'
import {
  confirmFulfillmentRole,
  prepareFulfillmentBinding,
  rebindFulfillmentRole,
  redeemFulfillmentTask,
} from '../fulfillment/executors/registry.js'
import {
  isKuaishouCloudMockTask,
  normalizeKuaishouCloudFlow,
} from '../fulfillment/kuaishou-cloud/index.js'
import { syncKuaishouFeifeiTaskStatus } from '../fulfillment/kuaishou-feifei/index.js'
import {
  normalizeAffiliateDashFlow,
  syncAffiliateDashTaskStatus,
} from '../fulfillment/affiliate-dash/index.js'
import {
  bindAffiliateDashDelivery,
  getAffiliateDashBindResult,
  submitAffiliateDashDelivery,
} from '../platforms/affiliate-dash/order-service.js'
import {
  assertValidClaimUid,
  canUpdateClaimUid,
  getClaimIdentityFromTask,
} from './claim-identity.js'
import { buildClaimDetailPayload, getClaimContext } from './kuaishou-cloud-claim-context.js'
import { syncKuaishouCloudRoleInfo } from './kuaishou-cloud-sync-service.js'
import type { TaskRow } from '../../types/repository/rows.js'

type ClaimDetailPayload = ReturnType<typeof buildClaimDetailPayload>

function assertLewanClaimTask(task: TaskRow) {
  if (String(task.executor_key || '').trim() !== 'kuaishou_ct_assisted') {
    throw createHttpError('当前领取链接不是 kuaishou-lewan 客户领取流程', {
      statusCode: 409,
      errorCode: 'claim_not_kuaishou_cloud',
    })
  }
}

async function requireExecutorAction<T>(
  result: Promise<T | null> | T | null,
  errorMessage: string,
  errorCode: string,
): Promise<NonNullable<T>> {
  const value = await result
  if (!value) {
    throw createHttpError(errorMessage, {
      statusCode: 409,
      errorCode,
    })
  }
  return value
}

function isKuaishouCloudBindingReady(flow: ReturnType<typeof normalizeKuaishouCloudFlow>) {
  return (
    flow.binding.prepareStatus === 'ready' && Boolean(String(flow.binding.bindUrl || '').trim())
  )
}

/**
 * 确保 Cloud 绑定资源已准备（取号 + 绑链）。
 * 提前核销后仍可能未 prepare，不能因 consume.success 跳过。
 */
async function ensureKuaishouCloudBindingPrepared(
  task: TaskRow,
  options: { source: string; actorSource?: string } = { source: 'system_auto_prepare_binding' },
): Promise<TaskRow> {
  const flow = normalizeKuaishouCloudFlow(parseTaskContext(task).kuaishouCloudFulfillment)
  if (isKuaishouCloudBindingReady(flow)) {
    return task
  }

  try {
    const prepared = await prepareFulfillmentBinding(task, {
      source: options.source,
      actor: { source: options.actorSource || options.source },
    })
    return prepared?.task || task
  } catch {
    return task
  }
}

async function verifyIndustryVoucherTicket(
  context: Awaited<ReturnType<typeof getClaimContext>>,
  now: string,
): Promise<ClaimDetailPayload> {
  const taskContext = parseTaskContext(context.task)
  const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment)

  // 提前核销后 consume 已是 success，但仍可能未准备绑定资源；禁止整段短路。
  // 注意：不要再调 getKuaishouCloudClaimDetail，避免 prepare 失败时递归。
  if (flow.consume.status === 'success') {
    const task = await ensureKuaishouCloudBindingPrepared(context.task, {
      source: 'claim_after_early_consume_prepare',
      actorSource: 'system',
    })
    return buildClaimDetailPayload({
      claimToken: context.claimToken,
      task,
      order: context.order,
      orderItem: context.orderItem,
    })
  }

  const industryContext = typeof taskContext === 'object' ? taskContext : {}
  const voucherContext = normalizeIndustryVoucherContext(industryContext.kuaishouIndustryVoucher)
  if (voucherContext.status === 'DESTROYED') {
    throw createHttpError('当前电子凭证已销毁，无法继续领取', {
      statusCode: 409,
      errorCode: 'claim_kuaishou_industry_voucher_destroyed',
    })
  }

  const token = String(voucherContext.token || industryContext.token || '').trim()
  const voucherCode = String(voucherContext.voucherCode || voucherContext.eticketId || '').trim()
  const certExpireType = Number(industryContext.certExpireType || 0)
  const certActualStartTime = Number(
    voucherContext.validStartTime || industryContext.certActualStartTime || 0,
  )
  const certActualEndTime = Number(
    voucherContext.validEndTime || industryContext.certActualEndTime || 0,
  )

  const nextContext = {
    ...taskContext,
    kuaishouIndustryVoucher: {
      ...voucherContext,
      oid: voucherContext.oid || context.order.platform_order_id,
      token,
      eticketId: voucherCode,
      voucherCode,
      status: voucherContext.status || 'UNUSED',
      verifiedAt: voucherContext.verifiedAt || now,
    },
    kuaishouCloudFulfillment: {
      ...flow,
      ticket: {
        ...flow.ticket,
        code: voucherCode,
        status: 'verified',
        capturedAt: flow.ticket.capturedAt || now,
        capturedBy: flow.ticket.capturedBy || { source: 'send_code_callback' },
        verifiedAt: now,
        oid: voucherContext.oid || context.order.platform_order_id,
        formToken: token,
        leftCount: voucherContext.status === 'CONSUMED' ? 0 : 1,
        goodsTitle: context.orderItem.sku_name || context.orderItem.sku_code || '',
      },
      consume: {
        ...flow.consume,
        status: voucherContext.status === 'CONSUMED' ? 'success' : 'pending',
        shopId: context.order.shop_id,
        shopName: context.order.shop_name,
        autoConsumeEnabled: true,
        consumedAt:
          voucherContext.status === 'CONSUMED'
            ? voucherContext.consumedAt || flow.consume.consumedAt || now
            : flow.consume.consumedAt,
      },
      certInfo: {
        certExpireType,
        certActualStartTime,
        certActualEndTime,
      },
    },
  }

  const taskWithTicket = (await updateTask(context.task.id, {
    claim_token: context.claimToken.token,
    claim_expires_at: context.claimToken.expired_at,
    context_json: JSON.stringify(nextContext),
    claimed_at: context.task.claimed_at || now,
    task_status: TASK_STATUS.WAITING_BINDING,
    user_action_status: 'pending_claim',
    last_error: '',
    updated_at: now,
  })) || {
    ...context.task,
    context_json: JSON.stringify(nextContext),
    claimed_at: context.task.claimed_at || now,
    task_status: TASK_STATUS.WAITING_BINDING,
    user_action_status: 'pending_claim',
    last_error: '',
    updated_at: now,
  }

  const preparedFlow = normalizeKuaishouCloudFlow(nextContext.kuaishouCloudFulfillment)
  let taskAfterPrepare = taskWithTicket
  if (!isKuaishouCloudBindingReady(preparedFlow)) {
    taskAfterPrepare = await ensureKuaishouCloudBindingPrepared(taskWithTicket, {
      source: 'send_code_callback',
      actorSource: 'send_code_callback',
    })
  }

  await createTaskEvent(
    context.task.id,
    'kuaishou_cloud_ticket_verified',
    {
      ticketCodeMasked: '',
      source: 'send_code_callback',
      industryVoucher: true,
      verifiedAt: now,
    },
    now,
  )

  return buildClaimDetailPayload({
    claimToken: context.claimToken,
    task: taskAfterPrepare,
    order: context.order,
    orderItem: context.orderItem,
  })
}

export async function getKuaishouCloudClaimDetail(token: unknown): Promise<ClaimDetailPayload> {
  const context = await getClaimContext(token)
  let task = context.task

  const executorKey = String(task.executor_key || '').trim()

  if (executorKey === 'kuaishou_feifei') {
    task = (await syncKuaishouFeifeiTaskStatus(task)) || task
  }

  if (executorKey === 'affiliate_dash') {
    task = (await syncAffiliateDashTaskStatus(task)) || task
    if (shouldRefreshAffiliateDashBindState(task)) {
      task = (await refreshAffiliateDashBindState(task)) || task
    }
  }

  if (executorKey === 'kuaishou-industry') {
    const flow = normalizeKuaishouCloudFlow(parseTaskContext(task).kuaishouCloudFulfillment)
    // 已核销也要走 verify：内部会补 prepare 绑定资源
    if (flow.consume.status !== 'success' || !isKuaishouCloudBindingReady(flow)) {
      const now = nowIso()
      const prepared: ClaimDetailPayload | null = await verifyIndustryVoucherTicket(
        context,
        now,
      ).catch(() => null)
      if (prepared) {
        return prepared
      }
    }
  }

  if (executorKey === 'kuaishou_ct_assisted') {
    const taskContext = parseTaskContext(task)
    const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment)
    const needsIndustryVoucherPrepare =
      hasUsableIndustryVoucher(taskContext) &&
      (flow.ticket.status !== 'verified' || !isKuaishouCloudBindingReady(flow))

    if (needsIndustryVoucherPrepare) {
      const now = nowIso()
      const prepared: ClaimDetailPayload | null = await verifyIndustryVoucherTicket(
        context,
        now,
      ).catch(() => null)
      if (prepared) {
        return prepared
      }
    }

    // 兜底：不依赖 UID；凭证已确认但绑定未就绪时自动补 prepare（覆盖提前核销场景）
    if (!isKuaishouCloudMockTask(task)) {
      const latestFlow = normalizeKuaishouCloudFlow(parseTaskContext(task).kuaishouCloudFulfillment)
      if (
        (latestFlow.ticket.status === 'verified' ||
          hasUsableIndustryVoucher(parseTaskContext(task))) &&
        !isKuaishouCloudBindingReady(latestFlow)
      ) {
        task = await ensureKuaishouCloudBindingPrepared(task, {
          source: 'claim_page_auto_binding_prepare',
          actorSource: 'system',
        })
      }
    }
  }

  // 已提交 UID 的 lewan 任务：详情查询时顺带刷新角色，便于前端轮询匹配状态
  if (
    executorKey === 'kuaishou_ct_assisted' &&
    !isKuaishouCloudMockTask(task) &&
    getClaimIdentityFromTask(task).expectedUid
  ) {
    task = (await syncKuaishouCloudRoleInfo(task)) || task
  }

  return buildClaimDetailPayload({
    claimToken: context.claimToken,
    task,
    order: context.order,
    orderItem: context.orderItem,
  })
}

/**
 * 统一领取 Step1：提交游戏 UID。
 */
export async function submitClaimUid(
  token: unknown,
  payload: { uid?: unknown } = {},
): Promise<ClaimDetailPayload> {
  const context = await getClaimContext(token)
  const expectedUid = assertValidClaimUid(payload.uid)
  const now = nowIso()

  if (!canUpdateClaimUid(context.task)) {
    throw createHttpError('当前任务状态不可修改 UID', {
      statusCode: 409,
      errorCode: 'claim_uid_locked',
    })
  }

  const taskContext = parseTaskContext(context.task)
  const previous = getClaimIdentityFromTask(context.task)
  const executorKey = String(context.task.executor_key || '').trim()
  if (executorKey === 'affiliate_dash') {
    return submitAffiliateDashClaimUid(context, expectedUid, previous, taskContext, now, token)
  }

  const nextContext = {
    ...taskContext,
    claimIdentity: buildClaimIdentityContext(previous, expectedUid, now),
  }

  let updatedTask = (await updateTask(context.task.id, {
    context_json: JSON.stringify(nextContext),
    claimed_at: context.task.claimed_at || now,
    last_error: '',
    updated_at: now,
  })) || {
    ...context.task,
    context_json: JSON.stringify(nextContext),
    claimed_at: context.task.claimed_at || now,
    updated_at: now,
  }

  await createClaimUidSubmittedEventIfChanged(
    context.task.id,
    previous.expectedUid,
    expectedUid,
    now,
  )

  if (executorKey === 'kuaishou_ct_assisted' && !isKuaishouCloudMockTask(updatedTask)) {
    try {
      const prepared = await prepareFulfillmentBinding(updatedTask, {
        source: 'claim_page_submit_uid',
        actor: { source: 'claim_page' },
      })
      if (prepared?.task) {
        updatedTask = prepared.task
      }
    } catch {
      // 绑定资源准备失败时仍保留 UID，详情页可继续重试/轮询
    }
  }

  return getKuaishouCloudClaimDetail(token)
}

function buildClaimIdentityContext(
  previous: ReturnType<typeof getClaimIdentityFromTask>,
  expectedUid: string,
  now: string,
) {
  return {
    expectedUid,
    submittedAt:
      previous.expectedUid === expectedUid && previous.submittedAt ? previous.submittedAt : now,
    source: 'claim_page',
  }
}

async function createClaimUidSubmittedEventIfChanged(
  taskId: number,
  previousUid: string,
  expectedUid: string,
  now: string,
) {
  if (previousUid === expectedUid) {
    return
  }

  await createTaskEvent(
    taskId,
    'claim_uid_submitted',
    {
      expectedUid,
      previousUid: previousUid || '',
      source: 'claim_page',
    },
    now,
  )
}

async function submitAffiliateDashClaimUid(
  context: Awaited<ReturnType<typeof getClaimContext>>,
  expectedUid: string,
  previous: ReturnType<typeof getClaimIdentityFromTask>,
  taskContext: JsonObject,
  now: string,
  token: unknown,
): Promise<ClaimDetailPayload> {
  const flow = normalizeAffiliateDashFlow(taskContext.affiliateDash)
  const claimIdentity = buildClaimIdentityContext(previous, expectedUid, now)
  const baseTaskPatch = {
    claimed_at: context.task.claimed_at || now,
    last_error: '',
    updated_at: now,
  }

  if (!flow.orderNo) {
    await updateTask(context.task.id, {
      ...baseTaskPatch,
      context_json: JSON.stringify({
        ...taskContext,
        claimIdentity,
        affiliateDash: clearAffiliateDashBindSnapshot(flow, expectedUid),
      }),
    })
    await createClaimUidSubmittedEventIfChanged(
      context.task.id,
      previous.expectedUid,
      expectedUid,
      now,
    )
    return getKuaishouCloudClaimDetail(token)
  }

  if (canReuseAffiliateDashBindSnapshot(flow, expectedUid)) {
    const nextFlow = {
      ...flow,
      gameAccount: expectedUid,
      expectedGameAccount: expectedUid,
      bindMismatch: false,
    }
    await updateTask(context.task.id, {
      ...baseTaskPatch,
      task_status: TASK_STATUS.WAITING_BINDING,
      context_json: JSON.stringify({
        ...taskContext,
        claimIdentity,
        affiliateDash: nextFlow,
      }),
    })
    await createClaimUidSubmittedEventIfChanged(
      context.task.id,
      previous.expectedUid,
      expectedUid,
      now,
    )
    await createTaskEvent(
      context.task.id,
      'affiliate_dash_bind_reused',
      {
        orderNo: flow.orderNo,
        bindUuid: flow.bindUuid,
        gameAccount: expectedUid,
      },
      now,
    )
    return getKuaishouCloudClaimDetail(token)
  }

  const bindResult = await bindAffiliateDashDelivery({
    orderNo: flow.orderNo,
    gameAccount: expectedUid,
  })
  const nextFlow = {
    ...flow,
    bindUuid: bindResult.bindUuid,
    bindUrl: bindResult.bindUrl,
    qrUrl: bindResult.qrUrl,
    gameAccount: expectedUid,
    expectedGameAccount: expectedUid,
    // 重新绑定：清空旧绑定状态，等待用户用新账号扫码完成新绑定。
    bound: false,
    boundAccount: '',
    gameChannel: '',
    bindMismatch: false,
  }

  await updateTask(context.task.id, {
    ...baseTaskPatch,
    task_status: TASK_STATUS.WAITING_BINDING,
    context_json: JSON.stringify({
      ...taskContext,
      claimIdentity,
      affiliateDash: nextFlow,
    }),
  })
  await createClaimUidSubmittedEventIfChanged(
    context.task.id,
    previous.expectedUid,
    expectedUid,
    now,
  )
  await createTaskEvent(
    context.task.id,
    'affiliate_dash_bind_created',
    {
      orderNo: flow.orderNo,
      bindUuid: bindResult.bindUuid,
      gameAccount: expectedUid,
    },
    now,
  )

  return getKuaishouCloudClaimDetail(token)
}

/**
 * affiliate_dash 领取 Step2：提交发货（绑定完成后）。
 */
export async function submitAffiliateDashClaim(
  token: unknown,
  payload: { gameAccount?: unknown; bindUuid?: unknown } = {},
): Promise<ClaimDetailPayload> {
  const context = await getClaimContext(token)
  const task = context.task
  const executorKey = String(task.executor_key || '').trim()
  if (executorKey !== 'affiliate_dash') {
    throw createHttpError('当前领取链接不是 affiliate-dash 领取流程', {
      statusCode: 409,
      errorCode: 'claim_not_affiliate_dash',
    })
  }

  const taskContext = parseTaskContextValue(task)
  const flow = normalizeAffiliateDashFlow(taskContext.affiliateDash)
  if (!flow.orderNo) {
    throw createHttpError('affiliate-dash 订单尚未创建', {
      statusCode: 409,
      errorCode: 'affiliate_dash_order_missing',
    })
  }

  const expectedGameAccount = String(
    getClaimIdentityFromTask(task).expectedUid ||
      flow.expectedGameAccount ||
      flow.gameAccount ||
      '',
  ).trim()
  const requestedGameAccount = String(payload.gameAccount || '').trim()
  const requestedBindUuid = String(payload.bindUuid || '').trim()
  const bindUuid = String(flow.bindUuid || '').trim()
  if (!expectedGameAccount || !bindUuid) {
    throw createHttpError('缺少绑定账号或 bindUuid', {
      statusCode: 400,
      errorCode: 'affiliate_dash_submit_missing',
    })
  }
  if (
    requestedGameAccount &&
    normalizeUid(requestedGameAccount) !== normalizeUid(expectedGameAccount)
  ) {
    throw createHttpError('提交 UID 与第一步填写 UID 不一致，请先更新 UID 后重新绑定', {
      statusCode: 409,
      errorCode: 'affiliate_dash_submit_uid_changed',
    })
  }
  if (requestedBindUuid && requestedBindUuid !== bindUuid) {
    throw createHttpError('绑定二维码已更新，请刷新页面后重试', {
      statusCode: 409,
      errorCode: 'affiliate_dash_bind_uuid_stale',
    })
  }

  const now = nowIso()
  const isMock = Boolean(flow.mock?.enabled)
  const gameAccount = expectedGameAccount
  const latestFlow = flow

  if (!isMock && flow.bindUuid) {
    // 提交前不再额外请求 bind-result：平台侧曾出现 GET 后 submit 的 bind_verify 异常。
    // 这里仅使用详情轮询写入的本地快照做强拦截，真正提交仍交给平台二次校验。
    if (isAffiliateDashBindSnapshotStale(flow, expectedGameAccount)) {
      throw createHttpError('绑定二维码与当前 UID 不一致，请更新 UID 后重新绑定', {
        statusCode: 409,
        errorCode: 'affiliate_dash_bind_uid_stale',
      })
    }

    const latestBoundAccount = latestFlow.boundAccount
    const latestMismatch =
      Boolean(latestFlow.bindMismatch) ||
      Boolean(
        latestFlow.bound &&
        latestBoundAccount &&
        normalizeUid(latestBoundAccount) !== normalizeUid(expectedGameAccount),
      )
    if (!latestFlow.bound) {
      throw createHttpError('绑定尚未完成，请先扫码绑定账号', {
        statusCode: 409,
        errorCode: 'affiliate_dash_bind_pending',
      })
    }
    if (!latestBoundAccount) {
      throw createHttpError('暂时无法确认绑定 UID，请稍后自动刷新后再提交', {
        statusCode: 409,
        errorCode: 'affiliate_dash_bound_account_missing',
      })
    }
    if (latestMismatch) {
      throw createHttpError('绑定账号与填写 UID 不一致，请确认后重新绑定', {
        statusCode: 409,
        errorCode: 'affiliate_dash_bind_mismatch',
      })
    }
  }

  const result = isMock
    ? {
        status: 'delivered',
        message: '开发 mock 已模拟提交发货',
        providerOrderNo: `MOCKAD-${flow.orderNo}`,
      }
    : await submitAffiliateDashDelivery({
        orderNo: flow.orderNo,
        gameAccount,
        bindUuid,
      })

  const nextFlow = {
    ...latestFlow,
    gameAccount,
    bindUuid,
    submitStatus: result.status,
    orderStatus: result.status || flow.orderStatus,
  }

  await updateTask(task.id, {
    task_status: TASK_STATUS.REDEEMING,
    context_json: JSON.stringify({
      ...taskContext,
      affiliateDash: nextFlow,
    }),
    result_code: 'affiliate_dash_submitted',
    result_message: result.message || 'affiliate-dash 已提交发货',
    updated_at: now,
  })
  await createTaskEvent(
    task.id,
    'affiliate_dash_submitted',
    {
      orderNo: flow.orderNo,
      submitStatus: result.status,
      providerOrderNo: result.providerOrderNo,
    },
    now,
  )

  // 平台已返回 delivered：立即用本地快照收敛任务为成功，不等平台回调（避免结果页等数秒）。
  // delivered 回调到达时 sync 幂等短路（已 REDEEMED 且 delivered 仍会刷新，无副作用）。
  if (result.status === 'delivered') {
    const refreshedTask = await getTaskById(task.id)
    if (refreshedTask) {
      await syncAffiliateDashTaskStatus(refreshedTask, { snapshotOnly: true })
    }
  }

  return getKuaishouCloudClaimDetail(token)
}

type AffiliateDashFlowSnapshot = ReturnType<typeof normalizeAffiliateDashFlow>

function resolveAffiliateDashExpectedGameAccount(
  task: Partial<TaskRow> | null | undefined,
  flow: AffiliateDashFlowSnapshot,
) {
  return String(
    getClaimIdentityFromTask(task).expectedUid ||
      flow.expectedGameAccount ||
      flow.gameAccount ||
      '',
  ).trim()
}

function resolveAffiliateDashRecordedBindUid(flow: AffiliateDashFlowSnapshot) {
  return normalizeUid(flow.expectedGameAccount || flow.gameAccount)
}

export function isAffiliateDashBindSnapshotStale(flowLike: unknown, expectedUid: unknown): boolean {
  const flow = normalizeAffiliateDashFlow(flowLike)
  const expected = normalizeUid(expectedUid)
  if (!flow.bindUuid || !expected) {
    return false
  }

  const recordedBindUid = resolveAffiliateDashRecordedBindUid(flow)
  return !recordedBindUid || recordedBindUid !== expected
}

export function canReuseAffiliateDashBindSnapshot(
  flowLike: unknown,
  expectedUid: unknown,
): boolean {
  const flow = normalizeAffiliateDashFlow(flowLike)
  const expected = normalizeUid(expectedUid)
  if (
    !expected ||
    !flow.bindUuid ||
    !(flow.bindUrl || flow.qrUrl) ||
    flow.bindMismatch ||
    isAffiliateDashBindSnapshotStale(flow, expected)
  ) {
    return false
  }

  const boundAccount = normalizeUid(flow.boundAccount)
  if (boundAccount && boundAccount !== expected) {
    return false
  }

  return !flow.bound || Boolean(boundAccount)
}

export function shouldRefreshAffiliateDashBindStateForSnapshot(input: {
  taskStatus?: unknown
  orderStatus?: unknown
  submitStatus?: unknown
}): boolean {
  const taskStatus = String(input.taskStatus || '').trim()
  if (
    taskStatus === TASK_STATUS.REDEEMING ||
    taskStatus === TASK_STATUS.DISPATCHED_PENDING_RETURN ||
    taskStatus === TASK_STATUS.REDEEMED ||
    taskStatus === TASK_STATUS.COMPLETED
  ) {
    return false
  }

  const submitStatus = String(input.submitStatus || '').trim()
  if (submitStatus) {
    return false
  }

  const orderStatus = String(input.orderStatus || '').trim()
  return !['delivering', 'delivered', 'ship_failed', 'cancelled'].includes(orderStatus)
}

function shouldRefreshAffiliateDashBindState(task: TaskRow): boolean {
  const flow = normalizeAffiliateDashFlow(parseTaskContextValue(task).affiliateDash)
  return shouldRefreshAffiliateDashBindStateForSnapshot({
    taskStatus: task.task_status,
    orderStatus: flow.orderStatus,
    submitStatus: flow.submitStatus,
  })
}

function clearAffiliateDashBindSnapshot(
  flow: AffiliateDashFlowSnapshot,
  expectedUid: unknown,
): AffiliateDashFlowSnapshot {
  const gameAccount = String(expectedUid || '').trim()
  return {
    ...flow,
    bindUuid: '',
    bindUrl: '',
    qrUrl: '',
    gameAccount: gameAccount || flow.gameAccount,
    expectedGameAccount: gameAccount || flow.expectedGameAccount,
    bound: false,
    boundAccount: '',
    gameChannel: '',
    bindMismatch: false,
  }
}

/**
 * 轮询绑定状态：详情查询时若有 bind_uuid，拉取 affiliate-dash bind-result 实时刷新
 * bound / 绑定账号 / mismatch 到上下文（可重入，拉取失败不阻塞详情）。
 */
async function refreshAffiliateDashBindState(task: TaskRow): Promise<TaskRow | null> {
  const taskContext = parseTaskContextValue(task)
  const flow = normalizeAffiliateDashFlow(taskContext.affiliateDash)
  if (flow.mock?.enabled) {
    // dev-mock：不请求 affiliate-dash 平台，绑定状态以 context 为准
    return task
  }
  if (!flow.orderNo || !flow.bindUuid) {
    return task
  }

  const expectedGameAccount = resolveAffiliateDashExpectedGameAccount(task, flow)
  if (isAffiliateDashBindSnapshotStale(flow, expectedGameAccount)) {
    const now = nowIso()
    return (
      (await updateTask(task.id, {
        context_json: JSON.stringify({
          ...taskContext,
          affiliateDash: clearAffiliateDashBindSnapshot(flow, expectedGameAccount),
        }),
        last_error: '绑定二维码与当前 UID 不一致，请点击更新 UID 重新生成绑定二维码',
        updated_at: now,
      })) || task
    )
  }

  try {
    const result = await getAffiliateDashBindResult({
      orderNo: flow.orderNo,
      bindUuid: flow.bindUuid,
    })
    const boundAccount = result.bound ? result.gameAccount || flow.boundAccount : ''
    // 本地强制校验：绑定返回的游戏账号与第一步填写的 UID 不一致时置 mismatch
    // （不依赖平台 mismatch 字段，防止平台未标记但实际账号不一致的情况）。
    const localMismatch = Boolean(
      boundAccount &&
      expectedGameAccount &&
      normalizeUid(boundAccount) !== normalizeUid(expectedGameAccount),
    )
    const nextFlow = {
      ...flow,
      bindUuid: result.bindUuid || flow.bindUuid,
      bindUrl: result.bindUrl || flow.bindUrl,
      qrUrl: result.qrUrl || flow.qrUrl,
      gameAccount: expectedGameAccount || flow.gameAccount,
      expectedGameAccount: expectedGameAccount || flow.expectedGameAccount,
      bound: result.bound,
      boundAccount,
      gameChannel: result.bound ? result.gameChannel || flow.gameChannel : '',
      bindMismatch: Boolean(result.mismatch) || localMismatch,
    }
    const now = nowIso()

    return (
      (await updateTask(task.id, {
        context_json: JSON.stringify({
          ...taskContext,
          affiliateDash: nextFlow,
        }),
        updated_at: now,
      })) || task
    )
  } catch {
    // 绑定状态拉取失败：保持现状，前端可继续轮询
    return task
  }
}

function normalizeUid(value: unknown) {
  return String(value || '')
    .trim()
    .replace(/\s+/g, '')
}

export async function rebindKuaishouCloudClaimRole(token: unknown) {
  const context = await getClaimContext(token)
  assertLewanClaimTask(context.task)

  await requireExecutorAction(
    rebindFulfillmentRole(context.task, {
      source: 'claim_page_rebind_role',
      actor: { source: 'claim_page' },
    }),
    '当前领取链接不支持换绑角色',
    'claim_rebind_not_supported',
  )

  return getKuaishouCloudClaimDetail(token)
}

export async function confirmKuaishouCloudClaimRole(token: unknown) {
  const context = await getClaimContext(token)
  assertLewanClaimTask(context.task)

  await requireExecutorAction(
    confirmFulfillmentRole(context.task, {
      source: 'claim_page_role_confirm',
      actor: { source: 'claim_page' },
      errorCodePrefix: 'claim_kuaishou_cloud',
      forceProbe: true,
    }),
    '当前领取链接不支持确认角色',
    'claim_confirm_not_supported',
  )

  return getKuaishouCloudClaimDetail(token)
}

/**
 * 领取页一键兑换：鉴权后交给 lewan 履约引擎 redeem 用例。
 */
export async function redeemKuaishouCloudClaim(token: unknown) {
  const context = await getClaimContext(token)
  assertLewanClaimTask(context.task)

  await requireExecutorAction(
    redeemFulfillmentTask(context.task, {
      source: 'claim_page_redeem',
      actor: { source: 'claim_page' },
      autoFinalize: true,
      errorCodePrefix: 'claim_kuaishou_cloud_redeem',
    }),
    '当前领取链接不支持一键兑换',
    'claim_redeem_not_supported',
  )

  return getKuaishouCloudClaimDetail(token)
}

function parseTaskContext(task: Partial<TaskRow> | null | undefined): JsonObject {
  return parseTaskContextValue(task)
}

function hasUsableIndustryVoucher(context: JsonObject = {}) {
  const voucher = normalizeIndustryVoucherContext(context.kuaishouIndustryVoucher)
  if (!voucher.voucherCode && !voucher.eticketId) {
    return false
  }

  return voucher.status !== 'DESTROYED'
}

function normalizeIndustryVoucherContext(value: unknown): JsonObject {
  const source =
    value && typeof value === 'object' && !Array.isArray(value) ? (value as JsonObject) : {}
  const status = String(source.status || 'UNUSED')
    .trim()
    .toUpperCase()

  return {
    ...source,
    oid: String(source.oid || '').trim(),
    token: String(source.token || '').trim(),
    eticketId: String(source.eticketId || source.voucherCode || '').trim(),
    voucherCode: String(source.voucherCode || source.eticketId || '').trim(),
    status: status === 'CONSUMED' || status === 'DESTROYED' ? status : 'UNUSED',
    validStartTime: Number(source.validStartTime || 0) || 0,
    validEndTime: Number(source.validEndTime || 0) || 0,
    verifiedAt: source.verifiedAt || null,
    consumedAt: source.consumedAt || null,
  }
}
