import {
  markInventoryItemDelivered,
  releaseReservedInventoryItem,
} from '../../../repositories/inventory-repo.js'
import { updateClaimToken } from '../../../repositories/claim-token-repo.js'
import { listOrderItemsByOrderId } from '../../../repositories/order-item-repo.js'
import { getOrderById } from '../../../repositories/order-repo.js'
import { updateTask } from '../../../repositories/task-repo.js'
import {
  getTaskInventoryBindingById,
  listTaskInventoryBindingsByTaskId,
} from '../../../repositories/task-inventory-binding-repo.js'
import { createTaskEvent } from '../../../repositories/task-event-repo.js'
import { createTaskClaimToken } from '../../claim/claim-service.js'
import { confirmClaimRoleForAdminTask, redeemClaimTaskForAdminTask } from '../../claim/claim-session-service.js'
import { reserveInventoryForTask } from '../../order/inventory-service.js'
import { ensureAgisoXianyuAutoDeliveryForDeliveredTask } from '../../platforms/agiso/xianyu/auto-delivery-service.js'
import { closeTencentBrowserSession } from '../../session/session.js'
import { createHttpError } from '../../../utils/http.js'
import { nowIso } from '../../../utils/time.js'
import {
  createAdminViewerContext,
  getTaskPrimaryClaimTokenId,
  getTaskPrimaryInventoryItemId,
  isAssistedClaimTask,
  isManualDispatchTask,
  parseTaskContext,
} from '../admin-read-shared-helpers.js'
import {
  getRequiredTask,
  mapTaskActionPayload,
} from '../admin-task-read-helpers.js'
import {
  ensureViewerCanOperateAssistedTask,
  getTaskClaimExpiresAt,
  isRecoverableTaskSessionCloseError,
  maskCode,
  normalizeManualDispatchOutcome,
  resolveTaskInventoryGroupCodes,
} from './shared.js'

import type {
  AdminEntityIdInput,
  AdminViewerSessionInput,
} from '../../../types/admin-read-inputs.js'
import type { AdminTaskManualDispatchInput } from '../../../types/admin-write-inputs.js'
import type {
  AdminTaskActionResponse,
  AdminTaskBindingReleaseResponse,
  AdminTaskManualDispatchResponse,
} from '../../../types/admin-write-models.js'
import type { TaskRow } from '../../../types/repository-rows.js'
import type { TaskUpdatePatch } from '../../../types/repository-inputs.js'

type CloseAdminTaskDeps = {
  getRequiredTask?: (taskId: AdminEntityIdInput) => Promise<TaskRow>
  listTaskInventoryBindingsByTaskId?: (taskId: AdminEntityIdInput) => Promise<any[]>
  releaseReservedInventoryItem?: (inventoryItemId: AdminEntityIdInput, updatedAt: string) => Promise<unknown>
  updateClaimToken?: (tokenId: AdminEntityIdInput, patch: Record<string, unknown>) => Promise<unknown>
  updateTask?: (taskId: AdminEntityIdInput, patch: TaskUpdatePatch) => Promise<any>
  createTaskEvent?: (taskId: AdminEntityIdInput, eventType: string, payload: Record<string, unknown>, createdAt: string) => Promise<unknown>
  closeTencentBrowserSession?: (sessionId: string) => Promise<unknown>
  nowIso?: () => string
}

export async function releaseAdminTaskInventory(taskId: AdminEntityIdInput): Promise<AdminTaskActionResponse> {
  const task = await getRequiredTask(taskId)
  const primaryInventoryItemId = getTaskPrimaryInventoryItemId(task)

  if (!primaryInventoryItemId) {
    throw createHttpError('当前任务没有预占库存项', {
      statusCode: 409,
      errorCode: 'admin_task_no_reserved_inventory',
    })
  }

  if (task.task_status === 'redeemed') {
    throw createHttpError('已兑换任务不能释放库存项', {
      statusCode: 409,
      errorCode: 'admin_task_release_not_allowed',
    })
  }

  const now = nowIso()
  await releaseReservedInventoryItem(primaryInventoryItemId, now)
  const updatedTask = await updateTask(task.id, {
    task_status: 'waiting_inventory',
    inventory_status: 'pending',
    last_error: '已手动释放预占库存项',
    updated_at: now,
  })

  return {
    task: mapTaskActionPayload(updatedTask),
  }
}

export async function releaseAdminTaskInventoryBinding(
  taskId: AdminEntityIdInput,
  bindingId: AdminEntityIdInput,
): Promise<AdminTaskBindingReleaseResponse> {
  const task = await getRequiredTask(taskId)
  const binding = await getTaskInventoryBindingById(Number(bindingId))

  if (!binding || Number(binding.task_id) !== Number(task.id)) {
    throw createHttpError('任务库存绑定不存在', {
      statusCode: 404,
      errorCode: 'admin_task_inventory_binding_not_found',
    })
  }

  if (String(binding.binding_status || '').trim() !== 'reserved') {
    throw createHttpError('当前库存绑定不是预占状态，不能释放', {
      statusCode: 409,
      errorCode: 'admin_task_inventory_binding_release_not_allowed',
    })
  }

  if (task.task_status === 'redeemed') {
    throw createHttpError('已兑换任务不能释放库存绑定', {
      statusCode: 409,
      errorCode: 'admin_task_release_not_allowed',
    })
  }

  const now = nowIso()
  await releaseReservedInventoryItem(binding.inventory_item_id, now)

  const remainingBindings = await listTaskInventoryBindingsByTaskId(task.id)
  const activeBindings = remainingBindings.filter((item) => ['reserved', 'consumed'].includes(String(item.binding_status || '').trim()))
  const hasReservedBindings = activeBindings.some((item) => String(item.binding_status || '').trim() === 'reserved')
  const hasConsumedBindings = activeBindings.some((item) => String(item.binding_status || '').trim() === 'consumed')
  const nextInventoryStatus = hasReservedBindings ? 'reserved' : (hasConsumedBindings ? 'consumed' : 'pending')
  const nextTaskStatus = !activeBindings.length && !['closed', 'expired'].includes(String(task.task_status || '').trim())
    ? 'waiting_inventory'
    : task.task_status

  if (!hasReservedBindings) {
    const primaryClaimTokenId = getTaskPrimaryClaimTokenId(task)
    if (primaryClaimTokenId) {
      await updateClaimToken(primaryClaimTokenId, {
        status: 'revoked',
        updated_at: now,
      })
    }
  }

  const updatedTask = await updateTask(task.id, {
    task_status: nextTaskStatus,
    inventory_status: nextInventoryStatus,
    last_error: !activeBindings.length ? '已手动释放预占库存绑定' : (task.last_error || ''),
    updated_at: now,
  })

  await createTaskEvent(task.id, 'inventory_binding_released', {
    bindingId: Number(binding.id),
    inventoryItemId: Number(binding.inventory_item_id),
    roleKey: String(binding.role_key || '').trim(),
  }, now)

  return {
    task: mapTaskActionPayload(updatedTask),
    bindingId: Number(binding.id),
    inventoryItemId: Number(binding.inventory_item_id),
  }
}

export async function regenerateAdminTaskClaimLink(
  taskId: AdminEntityIdInput,
  session: AdminViewerSessionInput | null = null,
): Promise<AdminTaskActionResponse> {
  const task = await getRequiredTask(taskId)
  const primaryClaimTokenId = getTaskPrimaryClaimTokenId(task)
  const viewerContext = createAdminViewerContext(session)

  if (isManualDispatchTask(task)) {
    throw createHttpError('人工履约任务不需要领取链接，请直接回写人工履约结果', {
      statusCode: 409,
      errorCode: 'admin_task_manual_dispatch_claim_not_allowed',
    })
  }

  if (['redeemed', 'closed'].includes(task.task_status)) {
    throw createHttpError('当前任务状态不允许重新生成领取链接', {
      statusCode: 409,
      errorCode: 'admin_task_regenerate_not_allowed',
    })
  }

  if (!viewerContext.canManageTaskLifecycle && !isAssistedClaimTask(task)) {
    throw createHttpError('当前账号只能重发半自动客服任务的领取链接', {
      statusCode: 403,
      errorCode: 'admin_task_regenerate_permission_denied',
    })
  }

  const now = nowIso()
  if (primaryClaimTokenId) {
    await updateClaimToken(primaryClaimTokenId, {
      status: 'revoked',
      updated_at: now,
    })
  }

  const claimToken = await createTaskClaimToken(task.id)
  const updatedTask = await updateTask(task.id, {
    claim_token: claimToken.token,
    claim_expires_at: claimToken.expired_at,
    task_status: 'link_generated',
    last_error: '',
    updated_at: now,
  })

  return {
    task: mapTaskActionPayload(updatedTask),
    claimUrl: claimToken.claimUrl,
    token: claimToken.token,
  }
}

export async function confirmAdminTaskAssistedRole(
  taskId: AdminEntityIdInput,
  session: AdminViewerSessionInput | null = null,
): Promise<AdminTaskActionResponse> {
  const task = await getRequiredTask(taskId)
  const viewerContext = createAdminViewerContext(session)
  ensureViewerCanOperateAssistedTask(task, viewerContext, 'confirm')
  await confirmClaimRoleForAdminTask(task.id)
  const updatedTask = await getRequiredTask(task.id)

  return {
    task: mapTaskActionPayload(updatedTask),
  }
}

export async function redeemAdminTaskAssisted(
  taskId: AdminEntityIdInput,
  session: AdminViewerSessionInput | null = null,
): Promise<AdminTaskActionResponse> {
  const task = await getRequiredTask(taskId)
  const viewerContext = createAdminViewerContext(session)
  ensureViewerCanOperateAssistedTask(task, viewerContext, 'redeem')
  await redeemClaimTaskForAdminTask(task.id)
  const updatedTask = await getRequiredTask(task.id)

  return {
    task: mapTaskActionPayload(updatedTask),
  }
}

export async function closeAdminTask(taskId: AdminEntityIdInput): Promise<AdminTaskActionResponse> {
  return closeAdminTaskWithDeps(taskId)
}

export async function closeAdminTaskWithDeps(
  taskId: AdminEntityIdInput,
  {
    getRequiredTask: getTask = getRequiredTask,
    listTaskInventoryBindingsByTaskId: listBindings = listTaskInventoryBindingsByTaskId,
    releaseReservedInventoryItem: releaseReserved = releaseReservedInventoryItem,
    updateClaimToken: updateToken = updateClaimToken,
    updateTask: updateTaskRecord = updateTask,
    createTaskEvent: createEvent = createTaskEvent,
    closeTencentBrowserSession: closeSession = closeTencentBrowserSession,
    nowIso: getNowIso = nowIso,
  }: CloseAdminTaskDeps = {},
): Promise<AdminTaskActionResponse> {
  const task = await getTask(taskId)

  if (task.task_status === 'redeemed') {
    throw createHttpError('已兑换任务不能关闭', {
      statusCode: 409,
      errorCode: 'admin_task_close_not_allowed',
    })
  }

  const now = getNowIso()
  const bindings = await listBindings(task.id)
  const reservedBindings = bindings.filter((binding) => String(binding.binding_status || '').trim() === 'reserved')
  const releasedInventoryItemIds = Array.from(new Set(reservedBindings.map((binding) => Number(binding.inventory_item_id)).filter((id) => id > 0)))
  const hasConsumedBindings = bindings.some((binding) => String(binding.binding_status || '').trim() === 'consumed')
  const primaryClaimTokenId = getTaskPrimaryClaimTokenId(task)
  let browserSessionClosed = false

  if (primaryClaimTokenId) {
    await updateToken(primaryClaimTokenId, {
      status: 'revoked',
      expired_at: now,
      updated_at: now,
    })
  }

  if (task.browser_session_id) {
    try {
      await closeSession(task.browser_session_id)
      browserSessionClosed = true
    } catch (error) {
      if (!isRecoverableTaskSessionCloseError(error)) {
        throw error
      }
    }
  }

  for (const inventoryItemId of releasedInventoryItemIds) {
    await releaseReserved(inventoryItemId, now)
  }

  const closeReasonParts = ['已手动关闭任务']
  if (primaryClaimTokenId) {
    closeReasonParts.push('领取链接已失效')
  }
  if (releasedInventoryItemIds.length > 0) {
    closeReasonParts.push('预占库存已释放')
  }

  const updatedTask = await updateTaskRecord(task.id, {
    task_status: 'closed',
    inventory_status: hasConsumedBindings ? 'consumed' : 'pending',
    delivery_status: 'closed',
    user_action_status: 'closed',
    claim_expires_at: primaryClaimTokenId ? now : task.claim_expires_at,
    browser_session_id: '',
    last_error: task.last_error || closeReasonParts.join('，'),
    updated_at: now,
  })

  await createEvent(task.id, 'task_closed', {
    claimTokenRevoked: Boolean(primaryClaimTokenId),
    releasedInventoryItemIds,
    releasedInventoryCount: releasedInventoryItemIds.length,
    browserSessionClosed,
  }, now)

  return {
    task: mapTaskActionPayload(updatedTask),
  }
}

export async function markAdminTaskManualReview(taskId: AdminEntityIdInput): Promise<AdminTaskActionResponse> {
  const task = await getRequiredTask(taskId)
  const updatedTask = await updateTask(task.id, {
    task_status: 'manual_review',
    delivery_status: task.delivery_status || 'pending',
    last_error: task.last_error || '已转人工处理',
    updated_at: nowIso(),
  })

  return {
    task: mapTaskActionPayload(updatedTask),
  }
}

export async function retryAdminTask(taskId: AdminEntityIdInput): Promise<AdminTaskActionResponse> {
  const task = await getRequiredTask(taskId)
  const now = nowIso()

  if (isManualDispatchTask(task)) {
    throw createHttpError('人工履约任务不能走自动重试，请在详情页直接回写人工履约结果', {
      statusCode: 409,
      errorCode: 'admin_task_manual_dispatch_retry_not_allowed',
    })
  }

  if (!['retry_pending', 'manual_review', 'waiting_inventory'].includes(task.task_status)) {
    throw createHttpError('当前任务状态不允许重试', {
      statusCode: 409,
      errorCode: 'admin_task_retry_not_allowed',
    })
  }

  const taskContext = parseTaskContext(task)
  const primaryRequirement = taskContext.primaryRequirement || null
  const orderItems = await listOrderItemsByOrderId(task.order_id)
  const orderItem = orderItems.find((item) => item.id === task.order_item_id) || null
  let reservedInventoryItemId = getTaskPrimaryInventoryItemId(task)
  let claimTokenId = getTaskPrimaryClaimTokenId(task)
  let nextStatus = 'link_generated'
  let lastError = ''
  let claimExpiresAt = getTaskClaimExpiresAt(task)
  let claimUrl = ''
  let token = ''

  if (!reservedInventoryItemId) {
    const reserved = await reserveInventoryForTask({
      skuCode: orderItem?.sku_code || '',
      taskId: task.id,
      credentialType: primaryRequirement?.credentialType || 'tencent_code',
      roleKey: primaryRequirement?.roleKey || 'primary_code',
      inventoryGroupCodes: resolveTaskInventoryGroupCodes(task),
    })

    if (!reserved) {
      nextStatus = 'waiting_inventory'
      lastError = '库存不足，等待可用库存凭据'
    } else {
      reservedInventoryItemId = reserved.id
    }
  }

  if (nextStatus === 'link_generated' && !claimTokenId) {
    const claimToken = await createTaskClaimToken(task.id)
    claimTokenId = claimToken.id
    claimExpiresAt = claimToken.expired_at
    claimUrl = claimToken.claimUrl
    token = claimToken.token
  }

  const updatedTask = await updateTask(task.id, {
    task_status: nextStatus,
    inventory_status: reservedInventoryItemId ? 'reserved' : 'pending',
    claim_token: token || task.claim_token || '',
    claim_expires_at: claimExpiresAt,
    last_error: lastError,
    updated_at: now,
  })

  const response: AdminTaskActionResponse = {
    task: mapTaskActionPayload(updatedTask),
  }

  if (claimUrl) {
    response.claimUrl = claimUrl
    response.token = token
  }

  return response
}

export async function completeAdminTaskManualDispatch(
  taskId: AdminEntityIdInput,
  payload: AdminTaskManualDispatchInput = {},
  session: AdminViewerSessionInput | null = null,
): Promise<AdminTaskManualDispatchResponse> {
  const task = await getRequiredTask(taskId)

  if (!isManualDispatchTask(task)) {
    throw createHttpError('当前任务不是人工履约任务', {
      statusCode: 409,
      errorCode: 'admin_task_not_manual_dispatch',
    })
  }

  if (['redeemed', 'closed'].includes(task.task_status)) {
    throw createHttpError('当前任务已经完结，不能重复回写人工履约结果', {
      statusCode: 409,
      errorCode: 'admin_task_manual_dispatch_already_completed',
    })
  }

  const now = nowIso()
  const outcome = normalizeManualDispatchOutcome(payload.outcome)
  const resultMessage = String(payload.resultMessage || '').trim()
  const deliveryReference = String(payload.deliveryReference || '').trim()
  const deliveredCredential = String(payload.deliveredCredential || '').trim()
  const context = parseTaskContext(task)
  const inventoryItemId = getTaskPrimaryInventoryItemId(task)
  const resultCode = outcome === 'failed' ? 'manual_dispatch_failed' : 'manual_dispatch_delivered'
  const fallbackMessage = outcome === 'failed' ? '人工履约失败' : '人工履约已完成'
  const nextTaskStatus = outcome === 'failed' ? 'closed' : 'redeemed'
  const nextDeliveryStatus = outcome === 'failed' ? 'failed' : 'delivered'
  const nextInventoryStatus = outcome === 'delivered' && inventoryItemId
    ? 'consumed'
    : task.inventory_status || 'not_required'
  const manualDispatch = {
    outcome,
    deliveryReference,
    deliveredCredential,
    resultMessage: resultMessage || fallbackMessage,
    completedAt: now,
    completedBy: session
      ? {
        userId: Number(session.userId || 0),
        username: String(session.username || ''),
        role: String(session.role || ''),
      }
      : null,
  }

  if (outcome === 'delivered' && inventoryItemId) {
    await markInventoryItemDelivered(inventoryItemId, now)
  }

  const updatedTask = await updateTask(task.id, {
    task_status: nextTaskStatus,
    inventory_status: nextInventoryStatus,
    delivery_status: nextDeliveryStatus,
    result_code: resultCode,
    result_message: resultMessage || fallbackMessage,
    user_action_status: 'not_required',
    last_error: outcome === 'failed' ? (resultMessage || fallbackMessage) : '',
    redeemed_at: outcome === 'delivered' ? now : task.redeemed_at || null,
    context_json: JSON.stringify({
      ...context,
      manualDispatch,
    }),
    updated_at: now,
  })

  await createTaskEvent(task.id, 'manual_dispatch_completed', {
    outcome,
    resultCode,
    resultMessage: resultMessage || fallbackMessage,
    deliveryReference,
    deliveredCredentialMasked: maskCode(deliveredCredential),
    completedBy: manualDispatch.completedBy,
  }, now)

  const taskAfterAutoDelivery = outcome === 'delivered'
    ? (await ensureAgisoXianyuAutoDeliveryForDeliveredTask({
      order: await getOrderById(task.order_id),
      task: updatedTask,
      trigger: 'manual_dispatch_completed',
    })).task || updatedTask
    : updatedTask

  return {
    outcome,
    task: mapTaskActionPayload(taskAfterAutoDelivery),
  }
}
