import { apiRequest } from './api.js'

const APPROVAL_TASKS_PATH = '/approval-tasks'
const DEFAULT_PAGE_SIZE = 20
const MAX_PAGE_SIZE = 200
const DEFAULT_TIMEOUT_MS = 5000

function toObject(value) {
  return value && typeof value === 'object' && !Array.isArray(value) ? value : {}
}

function toArray(value) {
  return Array.isArray(value) ? value : []
}

function toText(value) {
  return String(value ?? '').trim()
}

function toNumber(value, fallback = 0) {
  const number = Number(value)
  return Number.isFinite(number) ? number : fallback
}

function toInteger(value, fallback = 0) {
  return Math.trunc(toNumber(value, fallback))
}

function toPositiveInteger(value, fallback = 1, maximum = Number.MAX_SAFE_INTEGER) {
  return Math.min(maximum, Math.max(1, toInteger(value, fallback)))
}

function toNullableObject(value) {
  const object = toObject(value)
  return Object.keys(object).length ? object : null
}

function uniqueTexts(value) {
  return [...new Set(toArray(value).map(toText).filter(Boolean))]
}

function normalizePriorityReason(item = {}) {
  return {
    code: toText(item.code),
    label: toText(item.label),
    weight: toNumber(item.weight),
    tone: toText(item.tone || 'normal') || 'normal'
  }
}

export function createApprovalTaskRequestId(action = 'action', taskId = 'task') {
  const normalizedAction = toText(action).replace(/[^a-z0-9_-]+/gi, '-') || 'action'
  const normalizedTaskId = toText(taskId).replace(/[^a-z0-9_-]+/gi, '-') || 'task'
  const suffix = typeof globalThis.crypto?.randomUUID === 'function'
    ? globalThis.crypto.randomUUID()
    : `${Date.now()}-${Math.random().toString(16).slice(2)}`
  return `approval:${normalizedAction}:${normalizedTaskId}:${suffix}`.slice(0, 120)
}

export function normalizeApprovalTask(item = {}) {
  const task = toObject(item?.task || item)
  if (!toText(task.id)) {
    return null
  }

  return {
    id: toText(task.id),
    tenantId: toText(task.tenant_id ?? task.tenantId),
    claimId: toText(task.claim_id ?? task.claimId),
    expenseCaseId: toText(task.expense_case_id ?? task.expenseCaseId),
    nodeInstanceId: toText(task.node_instance_id ?? task.nodeInstanceId),
    nodeEntryKey: toText(task.node_entry_key ?? task.nodeEntryKey),
    parentTaskId: toText(task.parent_task_id ?? task.parentTaskId),
    taskKind: toText(task.task_kind ?? task.taskKind ?? 'root') || 'root',
    nodeKey: toText(task.node_key ?? task.nodeKey),
    nodeLabel: toText(task.node_label ?? task.nodeLabel),
    nodeSequence: toInteger(task.node_sequence ?? task.nodeSequence),
    sequenceOrder: toInteger(task.sequence_order ?? task.sequenceOrder),
    coordinationMode: toText(task.coordination_mode ?? task.coordinationMode ?? 'single') || 'single',
    ownerKind: toText(task.owner_kind ?? task.ownerKind),
    ownerKey: toText(task.owner_key ?? task.ownerKey),
    ownerEmployeeId: toText(task.owner_employee_id ?? task.ownerEmployeeId),
    ownerName: toText(task.owner_name ?? task.ownerName),
    assigneeKind: toText(task.assignee_kind ?? task.assigneeKind),
    assigneeKey: toText(task.assignee_key ?? task.assigneeKey),
    assigneeEmployeeId: toText(task.assignee_employee_id ?? task.assigneeEmployeeId),
    assigneeName: toText(task.assignee_name ?? task.assigneeName),
    delegatedBy: toText(task.delegated_by ?? task.delegatedBy),
    delegationExpiresAt: toText(task.delegation_expires_at ?? task.delegationExpiresAt),
    status: toText(task.status),
    decision: toText(task.decision),
    opinion: toText(task.opinion),
    version: Math.max(0, toInteger(task.version)),
    claimStatusSnapshot: toText(task.claim_status_snapshot ?? task.claimStatusSnapshot),
    claimStageSnapshot: toText(task.claim_stage_snapshot ?? task.claimStageSnapshot),
    enteredAt: toText(task.entered_at ?? task.enteredAt),
    enteredAtSource: toText(task.entered_at_source ?? task.enteredAtSource),
    activatedAt: toText(task.activated_at ?? task.activatedAt),
    slaHoursSnapshot: Math.max(0, toInteger(task.sla_hours_snapshot ?? task.slaHoursSnapshot)),
    dueAt: toText(task.due_at ?? task.dueAt),
    completedAt: toText(task.completed_at ?? task.completedAt),
    cancelledAt: toText(task.cancelled_at ?? task.cancelledAt),
    escalationLevel: Math.max(0, toInteger(task.escalation_level ?? task.escalationLevel)),
    escalatedAt: toText(task.escalated_at ?? task.escalatedAt),
    nextEscalationAt: toText(task.next_escalation_at ?? task.nextEscalationAt),
    priorityScore: Math.max(0, Math.min(100, toNumber(task.priority_score ?? task.priorityScore))),
    priorityTier: toText(task.priority_tier ?? task.priorityTier ?? 'normal') || 'normal',
    priorityReasons: toArray(task.priority_reasons_json ?? task.priorityReasons)
      .map(normalizePriorityReason)
      .filter((reason) => reason.code || reason.label),
    riskLevel: toText(task.risk_level ?? task.riskLevel ?? 'low') || 'low',
    openRiskCount: Math.max(0, toInteger(task.open_risk_count ?? task.openRiskCount)),
    evidenceCompleteness: Math.max(
      0,
      Math.min(1, toNumber(task.evidence_completeness ?? task.evidenceCompleteness))
    ),
    batchEligible: Boolean(task.batch_eligible ?? task.batchEligible),
    batchBlockReasons: uniqueTexts(task.batch_block_reasons_json ?? task.batchBlockReasons),
    projectionUpdatedAt: toText(task.projection_updated_at ?? task.projectionUpdatedAt),
    createdAt: toText(task.created_at ?? task.createdAt),
    updatedAt: toText(task.updated_at ?? task.updatedAt),
    canAct: Boolean(task.can_act ?? task.canAct),
    availableActions: uniqueTexts(task.available_actions ?? task.availableActions),
    readOnlyReason: toText(task.read_only_reason ?? task.readOnlyReason)
  }
}

export function normalizeApprovalTaskQueueItem(item = {}) {
  const source = toObject(item)
  const task = normalizeApprovalTask(source.task || source)
  if (!task) {
    return null
  }
  return {
    task,
    claim: toNullableObject(source.claim),
    id: task.id,
    claimId: task.claimId
  }
}

export function normalizeApprovalTaskEvent(item = {}) {
  const event = toObject(item)
  if (!toText(event.id)) {
    return null
  }
  return {
    id: toText(event.id),
    tenantId: toText(event.tenant_id ?? event.tenantId),
    taskId: toText(event.task_id ?? event.taskId),
    nodeInstanceId: toText(event.node_instance_id ?? event.nodeInstanceId),
    eventType: toText(event.event_type ?? event.eventType),
    actorId: toText(event.actor_id ?? event.actorId),
    actorName: toText(event.actor_name ?? event.actorName),
    actorType: toText(event.actor_type ?? event.actorType),
    requestId: toText(event.request_id ?? event.requestId),
    expectedTaskVersion: Math.max(
      0,
      toInteger(event.expected_task_version ?? event.expectedTaskVersion)
    ),
    resultTaskVersion: Math.max(
      0,
      toInteger(event.result_task_version ?? event.resultTaskVersion)
    ),
    payload: toObject(event.payload_json ?? event.payload),
    before: toObject(event.before_json ?? event.before),
    after: toObject(event.after_json ?? event.after),
    approvalActionLedgerId: toText(
      event.approval_action_ledger_id ?? event.approvalActionLedgerId
    ),
    businessEventId: toText(event.business_event_id ?? event.businessEventId),
    correlationId: toText(event.correlation_id ?? event.correlationId),
    causationId: toText(event.causation_id ?? event.causationId),
    occurredAt: toText(event.occurred_at ?? event.occurredAt)
  }
}

export function normalizeApprovalTaskMutation(payload = {}) {
  const envelope = toObject(payload)
  const mutation = toObject(envelope.mutation || envelope)
  return {
    task: normalizeApprovalTask(mutation.task),
    event: normalizeApprovalTaskEvent(mutation.event),
    relatedTasks: toArray(mutation.related_tasks ?? mutation.relatedTasks)
      .map(normalizeApprovalTask)
      .filter(Boolean),
    replayed: Boolean(mutation.replayed),
    claim: toNullableObject(envelope.claim ?? mutation.claim)
  }
}

export function normalizeApprovalTaskList(payload = {}) {
  const source = toObject(payload)
  const items = toArray(source.items).map(normalizeApprovalTaskQueueItem).filter(Boolean)
  const total = Math.max(items.length, toInteger(source.total, items.length))
  const pageSize = toPositiveInteger(
    source.page_size ?? source.pageSize,
    DEFAULT_PAGE_SIZE,
    MAX_PAGE_SIZE
  )
  const page = toPositiveInteger(source.page, 1)
  const totalPages = Math.max(
    total ? 1 : 0,
    toInteger(source.total_pages ?? source.totalPages, Math.ceil(total / pageSize))
  )
  return {
    items,
    total,
    page,
    pageSize,
    totalPages,
    generatedAt: toText(source.generated_at ?? source.generatedAt)
  }
}

export function normalizeApprovalTaskCandidate(item = {}) {
  const source = toObject(item)
  const employeeId = toText(source.employee_id ?? source.employeeId)
  if (!employeeId) {
    return null
  }
  return {
    employeeId,
    employeeNo: toText(source.employee_no ?? source.employeeNo),
    name: toText(source.name),
    email: toText(source.email),
    qualified: Boolean(source.qualified),
    reason: toText(source.reason)
  }
}

export function normalizeApprovalTaskCandidateList(payload = {}) {
  const source = Array.isArray(payload) ? { items: payload } : toObject(payload)
  const items = toArray(source.items).map(normalizeApprovalTaskCandidate).filter(Boolean)
  return {
    items,
    total: Math.max(items.length, toInteger(source.total, items.length))
  }
}

export function normalizeApprovalTaskBatchResult(payload = {}) {
  const source = toObject(payload)
  const items = toArray(source.items).map((item) => ({
    taskId: toText(item?.task_id ?? item?.taskId),
    claimId: toText(item?.claim_id ?? item?.claimId),
    status: toText(item?.status || 'failed') || 'failed',
    code: toText(item?.code),
    message: toText(item?.message),
    claim: toNullableObject(item?.claim)
  })).filter((item) => item.taskId)
  const countStatus = (status) => items.filter((item) => item.status === status).length
  return {
    batchRequestId: toText(source.batch_request_id ?? source.batchRequestId),
    status: toText(source.status || 'failed') || 'failed',
    succeededCount: Math.max(0, toInteger(source.succeeded_count ?? source.succeededCount, countStatus('succeeded'))),
    replayedCount: Math.max(0, toInteger(source.replayed_count ?? source.replayedCount, countStatus('replayed'))),
    conflictCount: Math.max(0, toInteger(source.conflict_count ?? source.conflictCount, countStatus('conflict'))),
    blockedCount: Math.max(0, toInteger(source.blocked_count ?? source.blockedCount, countStatus('blocked'))),
    forbiddenCount: Math.max(0, toInteger(source.forbidden_count ?? source.forbiddenCount, countStatus('forbidden'))),
    failedCount: Math.max(0, toInteger(source.failed_count ?? source.failedCount, countStatus('failed'))),
    items
  }
}

function appendQueryValue(search, key, value) {
  if (Array.isArray(value)) {
    value.map(toText).filter(Boolean).forEach((item) => search.append(key, item))
    return
  }
  const normalized = toText(value)
  if (normalized) {
    search.set(key, normalized)
  }
}

function buildApprovalTaskListQuery(params = {}) {
  const search = new URLSearchParams()
  search.set('page', String(toPositiveInteger(params.page, 1)))
  search.set(
    'page_size',
    String(toPositiveInteger(params.pageSize ?? params.page_size, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE))
  )
  appendQueryValue(search, 'status', params.status)
  appendQueryValue(search, 'node_key', params.nodeKey ?? params.node_key)
  appendQueryValue(search, 'task_kind', params.taskKind ?? params.task_kind)
  appendQueryValue(search, 'risk_level', params.riskLevel ?? params.risk_level)
  appendQueryValue(search, 'sla_state', params.slaState ?? params.sla_state)
  appendQueryValue(search, 'keyword', params.keyword)
  appendQueryValue(search, 'sort', params.sort)
  appendQueryValue(search, 'assignee', params.assignee)
  if (typeof (params.batchEligible ?? params.batch_eligible) === 'boolean') {
    search.set(
      'batch_eligible',
      String(Boolean(params.batchEligible ?? params.batch_eligible))
    )
  }
  return search.toString()
}

function requestOptions(options, timeoutMessage) {
  return {
    ...options,
    timeoutMs: toNumber(options?.timeoutMs, DEFAULT_TIMEOUT_MS),
    timeoutMessage
  }
}

function requireTaskId(taskId) {
  const normalized = toText(taskId)
  if (!normalized) {
    throw new Error('审批任务缺少任务标识。')
  }
  return normalized
}

function buildBaseActionPayload(action, taskId, payload = {}) {
  const expectedTaskVersion = toInteger(
    payload.expectedTaskVersion ?? payload.expected_task_version
  )
  if (expectedTaskVersion < 1) {
    throw new Error('审批动作缺少有效的任务版本。')
  }
  const reason = toText(payload.reason)
  if (reason.length < 2) {
    throw new Error('审批动作需要填写至少 2 个字的原因。')
  }
  return {
    request_id: toText(payload.requestId ?? payload.request_id)
      || createApprovalTaskRequestId(action, taskId),
    expected_task_version: expectedTaskVersion,
    reason
  }
}

function buildAssignmentActionPayload(action, taskId, payload) {
  const targetEmployeeId = toText(payload.targetEmployeeId ?? payload.target_employee_id)
  if (!targetEmployeeId) {
    throw new Error('审批任务需要选择目标员工。')
  }
  const body = {
    ...buildBaseActionPayload(action, taskId, payload),
    target_employee_id: targetEmployeeId
  }
  const expiresAt = toText(payload.expiresAt ?? payload.expires_at)
  if (expiresAt) {
    body.expires_at = expiresAt
  }
  return body
}

function buildParticipantsActionPayload(action, taskId, payload) {
  const participantEmployeeIds = uniqueTexts(
    payload.participantEmployeeIds ?? payload.participant_employee_ids
  )
  if (!participantEmployeeIds.length) {
    throw new Error('审批任务需要至少选择一名参与人。')
  }
  return {
    ...buildBaseActionPayload(action, taskId, payload),
    participant_employee_ids: participantEmployeeIds
  }
}

async function mutateApprovalTask(taskId, path, payload, options = {}) {
  const normalizedTaskId = requireTaskId(taskId)
  const response = await apiRequest(
    `${APPROVAL_TASKS_PATH}/${encodeURIComponent(normalizedTaskId)}/${path}`,
    {
      ...requestOptions(options, '审批任务操作超时，请刷新任务状态后重试。'),
      method: 'POST',
      body: JSON.stringify(payload)
    }
  )
  return normalizeApprovalTaskMutation(response)
}

export async function fetchApprovalTasks(params = {}, options = {}) {
  const payload = await apiRequest(
    `${APPROVAL_TASKS_PATH}?${buildApprovalTaskListQuery(params)}`,
    requestOptions(options, '审批任务列表加载超时，请稍后重试。')
  )
  return normalizeApprovalTaskList(payload)
}

export async function fetchApprovalTaskDetail(taskId, options = {}) {
  const normalizedTaskId = requireTaskId(taskId)
  const payload = await apiRequest(
    `${APPROVAL_TASKS_PATH}/${encodeURIComponent(normalizedTaskId)}`,
    requestOptions(options, '审批任务详情加载超时，请稍后重试。')
  )
  return normalizeApprovalTaskQueueItem(payload)
}

export async function fetchApprovalTaskCandidates(taskId, params = {}, options = {}) {
  const normalizedTaskId = requireTaskId(taskId)
  const search = new URLSearchParams()
  appendQueryValue(search, 'action', params.action)
  appendQueryValue(search, 'keyword', params.keyword)
  search.set('limit', String(toPositiveInteger(params.limit, 20, 100)))
  const payload = await apiRequest(
    `${APPROVAL_TASKS_PATH}/${encodeURIComponent(normalizedTaskId)}/candidates?${search}`,
    requestOptions(options, '审批候选人加载超时，请稍后重试。')
  )
  return normalizeApprovalTaskCandidateList(payload)
}

export function delegateApprovalTask(taskId, payload = {}, options = {}) {
  const normalizedTaskId = requireTaskId(taskId)
  return mutateApprovalTask(
    normalizedTaskId,
    'delegate',
    buildAssignmentActionPayload('delegate', normalizedTaskId, payload),
    options
  )
}

export function revokeApprovalTaskDelegation(taskId, payload = {}, options = {}) {
  const normalizedTaskId = requireTaskId(taskId)
  return mutateApprovalTask(
    normalizedTaskId,
    'delegation/revoke',
    buildBaseActionPayload('delegation-revoke', normalizedTaskId, payload),
    options
  )
}

export function transferApprovalTask(taskId, payload = {}, options = {}) {
  const normalizedTaskId = requireTaskId(taskId)
  return mutateApprovalTask(
    normalizedTaskId,
    'transfer',
    buildAssignmentActionPayload('transfer', normalizedTaskId, payload),
    options
  )
}

export function addSignApprovalTask(taskId, payload = {}, options = {}) {
  const normalizedTaskId = requireTaskId(taskId)
  return mutateApprovalTask(
    normalizedTaskId,
    'add-sign',
    buildParticipantsActionPayload('add-sign', normalizedTaskId, payload),
    options
  )
}

export function countersignApprovalTask(taskId, payload = {}, options = {}) {
  const normalizedTaskId = requireTaskId(taskId)
  return mutateApprovalTask(
    normalizedTaskId,
    'countersign',
    buildParticipantsActionPayload('countersign', normalizedTaskId, payload),
    options
  )
}

export function escalateApprovalTask(taskId, payload = {}, options = {}) {
  const normalizedTaskId = requireTaskId(taskId)
  return mutateApprovalTask(
    normalizedTaskId,
    'escalate',
    buildBaseActionPayload('sla-escalate', normalizedTaskId, payload),
    options
  )
}

export function approveApprovalTask(taskId, payload = {}, options = {}) {
  const normalizedTaskId = requireTaskId(taskId)
  const body = buildBaseActionPayload('approve', normalizedTaskId, payload)
  const opinion = toText(payload.opinion)
  if (opinion) {
    body.opinion = opinion
  }
  return mutateApprovalTask(normalizedTaskId, 'approve', body, options)
}

export function returnApprovalTask(taskId, payload = {}, options = {}) {
  const normalizedTaskId = requireTaskId(taskId)
  const body = {
    ...buildBaseActionPayload('return', normalizedTaskId, payload),
    reason_codes: uniqueTexts(payload.reasonCodes ?? payload.reason_codes)
  }
  return mutateApprovalTask(normalizedTaskId, 'return', body, options)
}

function normalizeBatchApproveItem(item = {}) {
  const source = toObject(item?.task || item)
  const taskId = toText(item.task_id ?? item.taskId ?? source.id)
  const expectedTaskVersion = toInteger(
    item.expected_task_version ?? item.expectedTaskVersion ?? source.version
  )
  const expectedStatus = toText(
    item.expected_status ?? item.expectedStatus
      ?? source.claim_status_snapshot ?? source.claimStatusSnapshot
  )
  const expectedApprovalStage = toText(
    item.expected_approval_stage ?? item.expectedApprovalStage
      ?? source.claim_stage_snapshot ?? source.claimStageSnapshot
  )
  if (!taskId || expectedTaskVersion < 1 || !expectedStatus || !expectedApprovalStage) {
    throw new Error('批量审批项缺少任务、版本、单据状态或审批节点快照。')
  }
  const body = {
    task_id: taskId,
    expected_task_version: expectedTaskVersion,
    expected_status: expectedStatus,
    expected_approval_stage: expectedApprovalStage
  }
  const opinion = toText(item.opinion)
  if (opinion) {
    body.opinion = opinion
  }
  return body
}

export async function batchApproveApprovalTasks(payload = {}, options = {}) {
  const items = toArray(payload.items).map(normalizeBatchApproveItem)
  if (!items.length || items.length > 20) {
    throw new Error('批量审批每次需要选择 1 至 20 项任务。')
  }
  if (new Set(items.map((item) => item.task_id)).size !== items.length) {
    throw new Error('批量审批不能包含重复任务。')
  }
  const batchRequestId = toText(payload.batchRequestId ?? payload.batch_request_id)
    || createApprovalTaskRequestId('batch-approve', 'batch').slice(0, 80)
  const response = await apiRequest(`${APPROVAL_TASKS_PATH}/batch-approve`, {
    ...requestOptions(options, '批量审批超时，请刷新任务状态后查看实际结果。'),
    method: 'POST',
    body: JSON.stringify({
      batch_request_id: batchRequestId,
      items
    })
  })
  return normalizeApprovalTaskBatchResult(response)
}
