import type { PoolClient } from 'pg'

import { query, withTransaction } from '../../db/client.js'
import { resolveAfterSalesRecoveryAllocation } from './after-sales-recovery.js'
import { ensureWorkerWalletWithClient, getWorkerWalletWithClient } from './shared.js'
import type {
  WorkerAfterSalesCaseEventRow,
  WorkerAfterSalesCaseRow,
  WorkerAfterSalesDebtRow,
  WorkerDepositUnfreezeRow,
  WorkerWalletRow,
  WorkOrderRow,
  WorkOrderShareRow,
} from './types.js'

const AFTER_SALES_CASE_SELECT = `
  SELECT
    ac.*,
    COALESCE(NULLIF(BTRIM(wo.platform_order_id), ''), wo.work_order_no) AS work_order_no,
    wo.platform_order_id,
    wo.product_name,
    COALESCE(
      NULLIF(BTRIM(o.shop_name), ''),
      NULLIF(BTRIM(o.shop_id), ''),
      NULLIF(BTRIM(ft.shop_name), ''),
      NULLIF(BTRIM(ft.shop_id), ''),
      ''
    ) AS shop_name,
    wu.username AS worker_username,
    wu.display_name AS worker_display_name,
    wos.quantity AS share_quantity
  FROM worker_after_sales_cases ac
  INNER JOIN work_orders wo ON wo.id = ac.work_order_id
  LEFT JOIN orders o ON o.id = wo.order_id
  LEFT JOIN fulfillment_tasks ft ON ft.id = wo.task_id
  INNER JOIN worker_users wu ON wu.id = ac.worker_id
  LEFT JOIN work_order_shares wos ON wos.id = ac.work_order_share_id
`

type AfterSalesCaseFailureReason =
  | 'work_order_not_accepted'
  | 'work_order_share_not_accepted'
  | 'worker_missing'
  | 'active_case_exists'
  | null

export async function createAfterSalesCase(input: {
  caseNo: string
  workOrderId: number
  workOrderShareId?: number | null
  problemType: string
  complaintNote: string
  complaintFilesJson: string
  createdBy: string
  now: string
}): Promise<{ case: WorkerAfterSalesCaseRow | null; failureReason: AfterSalesCaseFailureReason }> {
  return withTransaction(async (client) => {
    const workOrderResult = await client.query<WorkOrderRow>(
      `SELECT * FROM work_orders WHERE id = $1 AND status = 'accepted' FOR UPDATE`,
      [input.workOrderId],
    )
    const workOrder = workOrderResult.rows[0] || null
    if (!workOrder) {
      return { case: null, failureReason: 'work_order_not_accepted' }
    }

    let workerId = Number(workOrder.assigned_worker_id || 0)
    let acceptanceSnapshot: Record<string, unknown> = {
      capturedAt: input.now,
      acceptedAt: workOrder.accepted_at,
      acceptance: workOrder.acceptance_json,
    }
    const shareId = Number(input.workOrderShareId || 0)
    if (shareId > 0) {
      const shareResult = await client.query<WorkOrderShareRow>(
        `SELECT * FROM work_order_shares WHERE id = $1 AND work_order_id = $2 FOR UPDATE`,
        [shareId, input.workOrderId],
      )
      const share = shareResult.rows[0] || null
      if (!share || share.status !== 'accepted') {
        return { case: null, failureReason: 'work_order_share_not_accepted' }
      }
      workerId = Number(share.worker_id || 0)
      acceptanceSnapshot = {
        capturedAt: input.now,
        acceptedAt: share.accepted_at,
        acceptance: share.acceptance_json,
        shareId,
        shareQuantity: Number(share.quantity || 0),
      }
    }
    if (workerId <= 0) {
      return { case: null, failureReason: 'worker_missing' }
    }

    const createdResult = await client.query<WorkerAfterSalesCaseRow>(
      `
        INSERT INTO worker_after_sales_cases (
          case_no, work_order_id, work_order_share_id, worker_id,
          problem_type, complaint_note, complaint_files_json, acceptance_snapshot_json,
          status, created_by, created_at, updated_at
        ) VALUES ($1, $2, NULLIF($3, 0), $4, $5, $6, $7::jsonb, $8::jsonb,
                  'awaiting_worker_response', $9, $10, $10)
        ON CONFLICT DO NOTHING
        RETURNING *
      `,
      [
        input.caseNo,
        input.workOrderId,
        shareId,
        workerId,
        input.problemType,
        input.complaintNote,
        input.complaintFilesJson,
        JSON.stringify(acceptanceSnapshot),
        input.createdBy,
        input.now,
      ],
    )
    const created = createdResult.rows[0] || null
    if (!created) {
      return { case: null, failureReason: 'active_case_exists' }
    }
    await createAfterSalesCaseEventWithClient(client, {
      caseId: Number(created.id),
      actorType: 'admin',
      actorId: input.createdBy,
      eventType: 'created',
      payloadJson: JSON.stringify({
        problemType: input.problemType,
        complaintNote: input.complaintNote,
        workOrderShareId: shareId || null,
      }),
      now: input.now,
    })
    return { case: created, failureReason: null }
  })
}

export async function getAfterSalesCaseById(
  caseId: number | string,
): Promise<WorkerAfterSalesCaseRow | null> {
  const result = await query<WorkerAfterSalesCaseRow>(
    `${AFTER_SALES_CASE_SELECT} WHERE ac.id = $1 LIMIT 1`,
    [Number(caseId)],
  )
  return result.rows[0] || null
}

export async function listAfterSalesCases(
  input: {
    page?: number
    pageSize?: number
    status?: string
    keyword?: string
    workOrderId?: number
  } = {},
): Promise<{ items: WorkerAfterSalesCaseRow[]; total: number }> {
  const params: unknown[] = []
  const conditions: string[] = []
  if (String(input.status || '').trim()) {
    params.push(String(input.status).trim())
    conditions.push(`ac.status = $${params.length}`)
  }
  if (Number(input.workOrderId || 0) > 0) {
    params.push(Number(input.workOrderId))
    conditions.push(`ac.work_order_id = $${params.length}`)
  }
  if (String(input.keyword || '').trim()) {
    params.push(`%${String(input.keyword).trim()}%`)
    conditions.push(
      `(ac.case_no ILIKE $${params.length} OR wo.work_order_no ILIKE $${params.length} OR wo.platform_order_id ILIKE $${params.length} OR wu.username ILIKE $${params.length} OR wu.display_name ILIKE $${params.length})`,
    )
  }
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
  const totalResult = await query<{ total: number }>(
    `SELECT COUNT(*)::int AS total FROM worker_after_sales_cases ac
     INNER JOIN work_orders wo ON wo.id = ac.work_order_id
     INNER JOIN worker_users wu ON wu.id = ac.worker_id
     ${whereClause}`,
    params,
  )
  const page = Math.max(1, Number(input.page || 1))
  const pageSize = Math.min(100, Math.max(1, Number(input.pageSize || 20)))
  const offset = (page - 1) * pageSize
  params.push(pageSize, offset)
  const itemsResult = await query<WorkerAfterSalesCaseRow>(
    `${AFTER_SALES_CASE_SELECT}
     ${whereClause}
     ORDER BY ac.created_at DESC, ac.id DESC
     LIMIT $${params.length - 1} OFFSET $${params.length}`,
    params,
  )
  return { items: itemsResult.rows, total: Number(totalResult.rows[0]?.total || 0) }
}

export async function listWorkerAfterSalesCases(
  workerId: number,
): Promise<WorkerAfterSalesCaseRow[]> {
  const result = await query<WorkerAfterSalesCaseRow>(
    `${AFTER_SALES_CASE_SELECT}
     WHERE ac.worker_id = $1
     ORDER BY CASE WHEN ac.status IN ('awaiting_worker_response', 'under_review', 'recovery_pending') THEN 0 ELSE 1 END,
              ac.created_at DESC, ac.id DESC`,
    [workerId],
  )
  return result.rows
}

export async function listAfterSalesCaseEvents(
  caseId: number | string,
): Promise<WorkerAfterSalesCaseEventRow[]> {
  const result = await query<WorkerAfterSalesCaseEventRow>(
    `SELECT * FROM worker_after_sales_case_events WHERE case_id = $1 ORDER BY created_at ASC, id ASC`,
    [Number(caseId)],
  )
  return result.rows
}

export async function submitWorkerAfterSalesResponse(input: {
  caseId: number
  workerId: number
  response: string
  filesJson: string
  now: string
}): Promise<{
  case: WorkerAfterSalesCaseRow | null
  failureReason: 'not_found' | 'not_pending' | null
}> {
  return withTransaction(async (client) => {
    const currentResult = await client.query<WorkerAfterSalesCaseRow>(
      `SELECT * FROM worker_after_sales_cases WHERE id = $1 AND worker_id = $2 FOR UPDATE`,
      [input.caseId, input.workerId],
    )
    const current = currentResult.rows[0] || null
    if (!current) return { case: null, failureReason: 'not_found' }
    if (current.status !== 'awaiting_worker_response') {
      return { case: current, failureReason: 'not_pending' }
    }
    const updatedResult = await client.query<WorkerAfterSalesCaseRow>(
      `
        UPDATE worker_after_sales_cases
        SET status = 'under_review', worker_response = $1, worker_response_files_json = $2::jsonb,
            responded_at = $3, updated_at = $3
        WHERE id = $4
        RETURNING *
      `,
      [input.response, input.filesJson, input.now, input.caseId],
    )
    const updated = updatedResult.rows[0] || null
    if (updated) {
      await createAfterSalesCaseEventWithClient(client, {
        caseId: input.caseId,
        actorType: 'worker',
        actorId: String(input.workerId),
        eventType: 'worker_responded',
        payloadJson: JSON.stringify({ response: input.response }),
        now: input.now,
      })
    }
    return { case: updated, failureReason: null }
  })
}

export async function resolveAfterSalesCase(input: {
  caseId: number
  action: 'not_upheld' | 'remedied' | 'upheld'
  resolutionNote: string
  recoveryAmount: number
  resolvedBy: string
  now: string
}): Promise<{
  case: WorkerAfterSalesCaseRow | null
  refundedRecoveries: Array<{ caseId: number; caseNo: string; amount: number }>
  refundedTotalAmount: number
  failureReason: 'not_found' | 'not_resolvable' | null
}> {
  return withTransaction(async (client) => {
    const currentResult = await client.query<WorkerAfterSalesCaseRow>(
      `SELECT * FROM worker_after_sales_cases WHERE id = $1 FOR UPDATE`,
      [input.caseId],
    )
    const current = currentResult.rows[0] || null
    if (!current)
      return {
        case: null,
        refundedRecoveries: [],
        refundedTotalAmount: 0,
        failureReason: 'not_found',
      }
    if (!['awaiting_worker_response', 'under_review'].includes(current.status)) {
      return {
        case: current,
        refundedRecoveries: [],
        refundedTotalAmount: 0,
        failureReason: 'not_resolvable',
      }
    }

    let pendingDepositDeductionAmount = 0
    let availableDeductionAmount = 0
    let debtAmount = 0
    let nextStatus = 'closed'
    if (input.action === 'upheld' && input.recoveryAmount > 0) {
      const recovery = await recoverAfterSalesAmountWithClient(client, {
        caseId: Number(current.id),
        workOrderId: Number(current.work_order_id),
        workerId: Number(current.worker_id),
        amount: input.recoveryAmount,
        note: input.resolutionNote,
        now: input.now,
      })
      pendingDepositDeductionAmount = recovery.pendingDepositDeductionAmount
      availableDeductionAmount = recovery.availableDeductionAmount
      debtAmount = recovery.debtAmount
      nextStatus = debtAmount > 0 ? 'recovery_pending' : 'closed'
    }

    const updatedResult = await client.query<WorkerAfterSalesCaseRow>(
      `
        UPDATE worker_after_sales_cases
        SET
          status = $1,
          resolution_action = $2,
          resolution_note = $3,
          recovery_amount = $4,
          pending_deposit_deduction_amount = $5,
          available_deduction_amount = $6,
          debt_amount = $7,
          resolved_by = $8,
          resolved_at = $9,
          updated_at = $9
        WHERE id = $10
        RETURNING *
      `,
      [
        nextStatus,
        input.action,
        input.resolutionNote,
        input.action === 'upheld' ? input.recoveryAmount : 0,
        pendingDepositDeductionAmount,
        availableDeductionAmount,
        debtAmount,
        input.resolvedBy,
        input.now,
        input.caseId,
      ],
    )
    const updated = updatedResult.rows[0] || null
    if (updated) {
      await createAfterSalesCaseEventWithClient(client, {
        caseId: Number(updated.id),
        actorType: 'admin',
        actorId: input.resolvedBy,
        eventType: `resolved_${input.action}`,
        payloadJson: JSON.stringify({
          recoveryAmount: input.action === 'upheld' ? input.recoveryAmount : 0,
          pendingDepositDeductionAmount,
          availableDeductionAmount,
          debtAmount,
          resolutionNote: input.resolutionNote,
        }),
        now: input.now,
      })
    }

    // 补救完成 / 问题不成立意味着责任解除：同一责任单元此前已追缴且未退还的金额应退还打手。
    const refundedRecoveries: Array<{ caseId: number; caseNo: string; amount: number }> = []
    if (updated && input.action !== 'upheld') {
      const priorCases = await client.query<WorkerAfterSalesCaseRow>(
        `
          SELECT * FROM worker_after_sales_cases
          WHERE work_order_id = $1
            AND worker_id = $2
            AND COALESCE(work_order_share_id, 0) = COALESCE($3, 0)
            AND id != $4
            AND resolution_action = 'upheld'
            AND recovery_amount > 0
            AND refunded_amount = 0
          ORDER BY id ASC
          FOR UPDATE
        `,
        [
          Number(updated.work_order_id),
          Number(updated.worker_id),
          updated.work_order_share_id ? Number(updated.work_order_share_id) : null,
          Number(updated.id),
        ],
      )
      for (const priorCase of priorCases.rows) {
        const refundAmount = Number(priorCase.recovery_amount || 0)
        if (refundAmount <= 0) continue
        await client.query(
          `
            UPDATE worker_after_sales_debts
            SET outstanding_amount = 0, status = 'settled', settled_at = $1, updated_at = $1
            WHERE case_id = $2 AND status = 'open'
          `,
          [input.now, Number(priorCase.id)],
        )
        await ensureWorkerWalletWithClient(client, Number(priorCase.worker_id), input.now)
        const wallet = await getWorkerWalletWithClient(client, Number(priorCase.worker_id))
        const nextAvailable = Number(wallet?.available_amount || 0) + refundAmount
        await client.query(
          `UPDATE worker_wallets SET available_amount = $1, updated_at = $2 WHERE worker_id = $3`,
          [nextAvailable, input.now, Number(priorCase.worker_id)],
        )
        await client.query(
          `
            INSERT INTO worker_wallet_ledgers (
              worker_id, ledger_type, amount, balance_after, frozen_after,
              related_work_order_id, note, payload_json, created_at
            ) VALUES ($1, 'after_sales_refund', $2, $3, $4, $5, '售后追缴退还', $6::jsonb, $7)
          `,
          [
            Number(priorCase.worker_id),
            refundAmount,
            nextAvailable,
            Number(wallet?.frozen_deposit_amount || 0),
            Number(priorCase.work_order_id),
            JSON.stringify({
              caseId: Number(priorCase.id),
              triggerCaseId: Number(updated.id),
              triggerAction: input.action,
            }),
            input.now,
          ],
        )
        await client.query(
          `
            UPDATE worker_after_sales_cases
            SET refunded_amount = $1,
                refunded_at = $2,
                refunded_by = $3,
                debt_amount = 0,
                status = CASE WHEN status = 'recovery_pending' THEN 'closed' ELSE status END,
                updated_at = $2
            WHERE id = $4
          `,
          [refundAmount, input.now, input.resolvedBy, Number(priorCase.id)],
        )
        await createAfterSalesCaseEventWithClient(client, {
          caseId: Number(priorCase.id),
          actorType: 'admin',
          actorId: input.resolvedBy,
          eventType: 'recovery_refunded',
          payloadJson: JSON.stringify({
            amount: refundAmount,
            triggerCaseId: Number(updated.id),
            triggerAction: input.action,
          }),
          now: input.now,
        })
        refundedRecoveries.push({
          caseId: Number(priorCase.id),
          caseNo: String(priorCase.case_no || ''),
          amount: refundAmount,
        })
      }
    }
    return {
      case: updated,
      refundedRecoveries,
      refundedTotalAmount: refundedRecoveries.reduce((total, item) => total + item.amount, 0),
      failureReason: null,
    }
  })
}

/** 在验收结算同一事务中抵扣未结清售后欠款，返回本次各售后单的抵扣明细。 */
export async function offsetWorkerAfterSalesDebtsWithClient(
  client: PoolClient,
  input: { workerId: number; amount: number; workOrderId: number; now: string },
): Promise<Array<{ caseId: number; amount: number }>> {
  let remaining = Math.max(0, input.amount)
  if (remaining <= 0) return []
  const debtsResult = await client.query<WorkerAfterSalesDebtRow>(
    `
      SELECT *
      FROM worker_after_sales_debts
      WHERE worker_id = $1 AND status = 'open' AND outstanding_amount > 0
      ORDER BY created_at ASC, id ASC
      FOR UPDATE
    `,
    [input.workerId],
  )
  const offsets: Array<{ caseId: number; amount: number }> = []
  for (const debt of debtsResult.rows) {
    if (remaining <= 0) break
    const outstanding = Number(debt.outstanding_amount || 0)
    const offsetAmount = Math.min(remaining, outstanding)
    const nextOutstanding = outstanding - offsetAmount
    const settled = nextOutstanding <= 0
    await client.query(
      `
        UPDATE worker_after_sales_debts
        SET outstanding_amount = $1, status = $2,
            settled_at = CASE WHEN $2 = 'settled' THEN $3 ELSE NULL END,
            updated_at = $3
        WHERE id = $4
      `,
      [nextOutstanding, settled ? 'settled' : 'open', input.now, debt.id],
    )
    await client.query(
      `
        UPDATE worker_after_sales_cases
        SET debt_amount = $1,
            status = CASE WHEN $1 = 0 AND status = 'recovery_pending' THEN 'closed' ELSE status END,
            updated_at = $2
        WHERE id = $3
      `,
      [nextOutstanding, input.now, debt.case_id],
    )
    await createAfterSalesCaseEventWithClient(client, {
      caseId: Number(debt.case_id),
      actorType: 'system',
      actorId: '',
      eventType: settled ? 'debt_settled' : 'debt_offset',
      payloadJson: JSON.stringify({ workOrderId: input.workOrderId, amount: offsetAmount }),
      now: input.now,
    })
    offsets.push({ caseId: Number(debt.case_id), amount: offsetAmount })
    remaining -= offsetAmount
  }
  return offsets
}

async function recoverAfterSalesAmountWithClient(
  client: PoolClient,
  input: {
    caseId: number
    workOrderId: number
    workerId: number
    amount: number
    note: string
    now: string
  },
): Promise<{
  pendingDepositDeductionAmount: number
  availableDeductionAmount: number
  debtAmount: number
}> {
  const pendingResult = await client.query<WorkerDepositUnfreezeRow>(
    `
      SELECT * FROM worker_deposit_unfreezes
      WHERE work_order_id = $1 AND worker_id = $2 AND status = 'pending'
      ORDER BY unfreeze_at ASC, id ASC
      FOR UPDATE
    `,
    [input.workOrderId, input.workerId],
  )
  await ensureWorkerWalletWithClient(client, input.workerId, input.now)
  let wallet: WorkerWalletRow | null = await getWorkerWalletWithClient(client, input.workerId)
  const allocation = resolveAfterSalesRecoveryAllocation({
    recoveryAmount: input.amount,
    pendingDepositAmount: pendingResult.rows.reduce(
      (total, row) => total + Math.max(0, Number(row.amount) || 0),
      0,
    ),
    availableAmount: Number(wallet?.available_amount || 0),
  })
  let pendingRemaining = allocation.pendingDepositDeductionAmount
  for (const row of pendingResult.rows) {
    if (pendingRemaining <= 0) break
    const rowAmount = Math.max(0, Number(row.amount) || 0)
    const deduction = Math.min(rowAmount, pendingRemaining)
    if (deduction <= 0) continue
    if (deduction >= rowAmount) {
      await client.query(
        `UPDATE worker_deposit_unfreezes SET status = 'deducted', updated_at = $1 WHERE id = $2`,
        [input.now, row.id],
      )
    } else {
      await client.query(
        `UPDATE worker_deposit_unfreezes SET amount = amount - $1, updated_at = $2 WHERE id = $3`,
        [deduction, input.now, row.id],
      )
    }
    await client.query(
      `UPDATE worker_wallets
       SET pending_unfreeze_amount = GREATEST(0, pending_unfreeze_amount - $1), updated_at = $2
       WHERE worker_id = $3`,
      [deduction, input.now, input.workerId],
    )
    await client.query(
      `
        INSERT INTO worker_wallet_ledgers (
          worker_id, ledger_type, amount, balance_after, frozen_after,
          related_work_order_id, note, payload_json, created_at
        ) VALUES ($1, 'after_sales_deposit_deduction', $2, $3, $4, $5, '售后问题单扣减待解冻押金', $6::jsonb, $7)
      `,
      [
        input.workerId,
        -deduction,
        Number(wallet?.available_amount || 0),
        Number(wallet?.frozen_deposit_amount || 0),
        input.workOrderId,
        JSON.stringify({ caseId: input.caseId, note: input.note, unfreezeId: row.id }),
        input.now,
      ],
    )
    pendingRemaining -= deduction
  }

  wallet = await getWorkerWalletWithClient(client, input.workerId)
  const availableDeductionAmount = allocation.availableDeductionAmount
  if (availableDeductionAmount > 0) {
    const nextAvailable = Number(wallet?.available_amount || 0) - availableDeductionAmount
    await client.query(
      `UPDATE worker_wallets SET available_amount = $1, updated_at = $2 WHERE worker_id = $3`,
      [nextAvailable, input.now, input.workerId],
    )
    await client.query(
      `
        INSERT INTO worker_wallet_ledgers (
          worker_id, ledger_type, amount, balance_after, frozen_after,
          related_work_order_id, note, payload_json, created_at
        ) VALUES ($1, 'after_sales_balance_deduction', $2, $3, $4, $5, '售后问题单扣减可用余额', $6::jsonb, $7)
      `,
      [
        input.workerId,
        -availableDeductionAmount,
        nextAvailable,
        Number(wallet?.frozen_deposit_amount || 0),
        input.workOrderId,
        JSON.stringify({ caseId: input.caseId, note: input.note }),
        input.now,
      ],
    )
  }
  if (allocation.debtAmount > 0) {
    await client.query(
      `
        INSERT INTO worker_after_sales_debts (
          case_id, worker_id, original_amount, outstanding_amount, status, created_at, updated_at
        ) VALUES ($1, $2, $3, $3, 'open', $4, $4)
      `,
      [input.caseId, input.workerId, allocation.debtAmount, input.now],
    )
  }
  return allocation
}

async function createAfterSalesCaseEventWithClient(
  client: PoolClient,
  input: {
    caseId: number
    actorType: string
    actorId: string
    eventType: string
    payloadJson: string
    now: string
  },
) {
  await client.query(
    `
      INSERT INTO worker_after_sales_case_events (
        case_id, actor_type, actor_id, event_type, payload_json, created_at
      ) VALUES ($1, $2, $3, $4, $5::jsonb, $6)
    `,
    [input.caseId, input.actorType, input.actorId, input.eventType, input.payloadJson, input.now],
  )
}
