import type { PoolClient } from 'pg'

import { query, withTransaction } from '../../db/client.js'
import { isWorkOrderGiftDepositForfeitable } from '../../domain/work-order-acceptance-mode.js'
import {
  ensureWorkerWalletWithClient,
  getWorkerWalletWithClient,
  resolveWorkOrderShareCancellationStatus,
} from './shared.js'
import { createWorkOrderEventWithClient } from './work-order-event-repo.js'
import {
  getOutstandingDepositAmountWithClient,
  getWorkOrderByIdWithClient,
} from './work-order-repo.js'
import type { WorkerCancelRequestRow, WorkOrderRow, WorkOrderShareRow } from './types.js'

const WORKER_CANCEL_REQUEST_SELECT = `
  SELECT
    cr.*,
    COALESCE(NULLIF(BTRIM(wo.platform_order_id), ''), o.platform_order_id, ft.platform_order_id, '') AS platform_order_id,
    COALESCE(
      NULLIF(BTRIM(o.shop_name), ''),
      NULLIF(BTRIM(o.shop_id), ''),
      NULLIF(BTRIM(ft.shop_name), ''),
      NULLIF(BTRIM(ft.shop_id), ''),
      ''
    ) AS shop_name,
    wo.product_name,
    wu.username AS worker_username,
    wu.display_name AS worker_display_name,
    wos.quantity AS share_quantity
  FROM worker_order_cancel_requests cr
  INNER JOIN work_orders wo ON wo.id = cr.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 = cr.worker_id
  LEFT JOIN work_order_shares wos ON wos.id = cr.work_order_share_id
`

async function getWorkerCancelRequestByIdWithClient(
  client: PoolClient,
  requestId: number | string,
): Promise<WorkerCancelRequestRow | null> {
  const result = await client.query<WorkerCancelRequestRow>(
    `${WORKER_CANCEL_REQUEST_SELECT} WHERE cr.id = $1 LIMIT 1`,
    [Number(requestId)],
  )
  return result.rows[0] || null
}

async function countWorkerAcceptedOrdersWithClient(
  client: PoolClient,
  workerId: number,
): Promise<number> {
  const result = await client.query<{ total: number }>(
    `SELECT COUNT(DISTINCT order_key)::int AS total
     FROM (
       SELECT COALESCE(
         'order:' || NULLIF(wo.order_id::text, ''),
         'platform_order:' || NULLIF(BTRIM(wo.platform_order_id), ''),
         'work_order:' || wo.id::text
       ) AS order_key
       FROM work_orders wo
       WHERE wo.assigned_worker_id = $1 AND wo.status = 'accepted'

       UNION

       SELECT COALESCE(
         'order:' || NULLIF(wo.order_id::text, ''),
         'platform_order:' || NULLIF(BTRIM(wo.platform_order_id), ''),
         'work_order:' || wo.id::text
       ) AS order_key
       FROM work_order_shares wos
       INNER JOIN work_orders wo ON wo.id = wos.work_order_id
       WHERE wos.worker_id = $1 AND wos.status = 'accepted'
     ) accepted_orders`,
    [workerId],
  )
  return Number(result.rows[0]?.total || 0)
}

async function applyWorkerCancelRequestPenaltyWithClient(
  client: PoolClient,
  input: { workerId: number; requestId: number; now: string },
): Promise<{ levelBeforeId: number | null; levelChanged: boolean; accountDisabled: boolean }> {
  const workerResult = await client.query<{
    level_id: number | null
    level_key: string
    level_name: string
    sort_order: number
    level_penalty_accepted_count: number
  }>(
    `SELECT wu.level_id, wl.level_key, wl.name AS level_name, wl.sort_order,
            wu.level_penalty_accepted_count
     FROM worker_users wu
     LEFT JOIN worker_levels wl ON wl.id = wu.level_id
     WHERE wu.id = $1
     FOR UPDATE OF wu`,
    [input.workerId],
  )
  const worker = workerResult.rows[0]
  const levelBeforeId = worker?.level_id ? Number(worker.level_id) : null
  if (!worker || !levelBeforeId) {
    return { levelBeforeId, levelChanged: false, accountDisabled: false }
  }

  const approvedResult = await client.query<{ total: number }>(
    `SELECT COUNT(*)::int AS total
     FROM worker_order_cancel_requests
     WHERE worker_id = $1 AND status = 'approved'`,
    [input.workerId],
  )
  const approvedTotal = Number(approvedResult.rows[0]?.total || 0) + 1
  const isVip1 = String(worker.level_key || '').toLowerCase() === 'vip1'

  if (isVip1) {
    const vip1ApprovedResult = await client.query<{ total: number }>(
      `SELECT COUNT(*)::int AS total
       FROM worker_order_cancel_requests
       WHERE worker_id = $1 AND status = 'approved' AND level_before_id = $2`,
      [input.workerId, levelBeforeId],
    )
    const vip1ApprovedTotal = Number(vip1ApprovedResult.rows[0]?.total || 0) + 1
    if (vip1ApprovedTotal >= 5) {
      await client.query(
        `UPDATE worker_users
         SET status = 'disabled', session_version = session_version + 1, updated_at = $1
         WHERE id = $2`,
        [input.now, input.workerId],
      )
      await client.query(
        `UPDATE worker_sessions
         SET status = 'revoked', revoked_at = $1, updated_at = $1
         WHERE worker_id = $2 AND status = 'active'`,
        [input.now, input.workerId],
      )
      await client.query(
        `INSERT INTO worker_level_logs (
           worker_id, from_level_id, to_level_id, reason, payload_json, created_at
         ) VALUES ($1, $2, $2, 'cancel_request_ban', $3::jsonb, $4)`,
        [
          input.workerId,
          levelBeforeId,
          JSON.stringify({ requestId: input.requestId, vip1ApprovedTotal, approvedTotal }),
          input.now,
        ],
      )
      return { levelBeforeId, levelChanged: false, accountDisabled: true }
    }
    return { levelBeforeId, levelChanged: false, accountDisabled: false }
  }

  if (approvedTotal % 10 !== 0) {
    return { levelBeforeId, levelChanged: false, accountDisabled: false }
  }

  const lowerLevelResult = await client.query<{
    id: number
    level_key: string
    threshold: number
  }>(
    `SELECT id, level_key, COALESCE((permission_json->>'upgradeThreshold')::int, 0) AS threshold
     FROM worker_levels
     WHERE status = 'active' AND sort_order < $1
     ORDER BY sort_order DESC, id DESC
     LIMIT 1`,
    [Number(worker.sort_order || 0)],
  )
  const lowerLevel = lowerLevelResult.rows[0]
  if (!lowerLevel) {
    return { levelBeforeId, levelChanged: false, accountDisabled: false }
  }
  const actualAcceptedCount = await countWorkerAcceptedOrdersWithClient(client, input.workerId)
  const nextPenaltyAcceptedCount = Math.max(
    Number(worker.level_penalty_accepted_count || 0),
    actualAcceptedCount - Math.max(0, Number(lowerLevel.threshold || 0)),
  )
  await client.query(
    `UPDATE worker_users
     SET level_id = $1, level_penalty_accepted_count = $2, updated_at = $3
     WHERE id = $4`,
    [lowerLevel.id, nextPenaltyAcceptedCount, input.now, input.workerId],
  )
  await client.query(
    `INSERT INTO worker_level_logs (
       worker_id, from_level_id, to_level_id, reason, payload_json, created_at
     ) VALUES ($1, $2, $3, 'cancel_request_penalty', $4::jsonb, $5)`,
    [
      input.workerId,
      levelBeforeId,
      lowerLevel.id,
      JSON.stringify({
        requestId: input.requestId,
        approvedTotal,
        actualAcceptedCount,
        penaltyAcceptedCount: nextPenaltyAcceptedCount,
      }),
      input.now,
    ],
  )
  return { levelBeforeId, levelChanged: true, accountDisabled: false }
}

/** 打手提交退单申请；待审核期间订单与押金均保持原状。 */
/** 批量查询打手在各工单上待审核的退单申请，用于订单角标与问题单归类。 */
export async function getPendingCancelRequestOrderIds(
  workerId: number,
  workOrderIds: number[],
): Promise<Set<number>> {
  if (workOrderIds.length === 0) return new Set()
  const result = await query<{ work_order_id: number }>(
    `
      SELECT work_order_id
      FROM worker_order_cancel_requests
      WHERE worker_id = $1 AND work_order_id = ANY($2::bigint[]) AND status = 'pending'
    `,
    [Number(workerId), workOrderIds],
  )
  return new Set(result.rows.map((row) => Number(row.work_order_id)))
}

export async function createWorkerCancelRequest(input: {
  workOrderId: number
  workerId: number
  reason: string
  now: string
}): Promise<{
  request: WorkerCancelRequestRow | null
  dailyUsed: number
  failureReason:
    | 'work_order_not_cancellable'
    | 'work_order_owner_required'
    | 'share_not_cancellable'
    | 'request_pending'
    | 'daily_limit_reached'
    | null
}> {
  return withTransaction(async (client) => {
    const orderResult = await client.query<WorkOrderRow>(
      'SELECT * FROM work_orders WHERE id = $1 FOR UPDATE',
      [input.workOrderId],
    )
    const workOrder = orderResult.rows[0] || null
    if (!workOrder) {
      return { request: null, dailyUsed: 0, failureReason: 'work_order_not_cancellable' }
    }

    let shareId: number | null = null
    if (workOrder.sharing_enabled === true) {
      const shareResult = await client.query<WorkOrderShareRow>(
        `SELECT * FROM work_order_shares
         WHERE work_order_id = $1 AND worker_id = $2
         FOR UPDATE`,
        [input.workOrderId, input.workerId],
      )
      const share = shareResult.rows[0] || null
      if (!share || !resolveWorkOrderShareCancellationStatus(workOrder.status, share.status)) {
        return { request: null, dailyUsed: 0, failureReason: 'share_not_cancellable' }
      }
      shareId = Number(share.id)
    } else if (
      workOrder.status !== 'in_progress' ||
      Number(workOrder.assigned_worker_id || 0) !== input.workerId
    ) {
      return {
        request: null,
        dailyUsed: 0,
        failureReason: workOrder.assigned_worker_id
          ? 'work_order_not_cancellable'
          : 'work_order_owner_required',
      }
    }

    const dailyResult = await client.query<{ total: number }>(
      `SELECT COUNT(*)::int AS total
       FROM worker_order_cancel_requests
       WHERE worker_id = $1
         AND status IN ('pending', 'approved')
         AND created_at >= date_trunc('day', $2::timestamptz AT TIME ZONE 'Asia/Shanghai') AT TIME ZONE 'Asia/Shanghai'`,
      [input.workerId, input.now],
    )
    const dailyUsed = Number(dailyResult.rows[0]?.total || 0)
    if (dailyUsed >= 5) {
      return { request: null, dailyUsed, failureReason: 'daily_limit_reached' }
    }

    const pendingResult = await client.query<{ id: number }>(
      `SELECT id FROM worker_order_cancel_requests
       WHERE work_order_id = $1
         AND worker_id = $2
         AND COALESCE(work_order_share_id, 0) = $3
         AND status = 'pending'
       LIMIT 1`,
      [input.workOrderId, input.workerId, shareId || 0],
    )
    if (pendingResult.rows[0]) {
      return { request: null, dailyUsed, failureReason: 'request_pending' }
    }

    const created = await client.query<{ id: number }>(
      `INSERT INTO worker_order_cancel_requests (
         work_order_id, work_order_share_id, worker_id, reason, status, created_at, updated_at
       ) VALUES ($1, $2, $3, $4, 'pending', $5, $5)
       RETURNING id`,
      [input.workOrderId, shareId, input.workerId, input.reason, input.now],
    )
    const requestId = Number(created.rows[0]?.id || 0)
    await createWorkOrderEventWithClient(client, {
      workOrderId: input.workOrderId,
      actorType: 'worker',
      actorId: String(input.workerId),
      eventType: 'cancel_request_submitted',
      fromStatus: workOrder.status,
      toStatus: workOrder.status,
      payloadJson: JSON.stringify({ requestId, shareId, reason: input.reason }),
      now: input.now,
    })
    return {
      request: await getWorkerCancelRequestByIdWithClient(client, requestId),
      dailyUsed: dailyUsed + 1,
      failureReason: null,
    }
  })
}

export async function listWorkerCancelRequests(
  workerId: number | string,
): Promise<WorkerCancelRequestRow[]> {
  const result = await query<WorkerCancelRequestRow>(
    `${WORKER_CANCEL_REQUEST_SELECT}
     WHERE cr.worker_id = $1
     ORDER BY cr.id DESC
     LIMIT 100`,
    [Number(workerId)],
  )
  return result.rows
}

export async function listAdminWorkerCancelRequests(
  status = 'pending',
): Promise<WorkerCancelRequestRow[]> {
  const result = await query<WorkerCancelRequestRow>(
    `${WORKER_CANCEL_REQUEST_SELECT}
     WHERE ($1 = '' OR cr.status = $1)
     ORDER BY cr.id DESC
     LIMIT 200`,
    [status],
  )
  return result.rows
}

export async function getWorkerCancelRequestRisk(workerId: number | string, now: string) {
  const result = await query<{ daily_used: number; approved_total: number }>(
    `SELECT
       COUNT(*) FILTER (
         WHERE status IN ('pending', 'approved')
           AND created_at >= date_trunc('day', $2::timestamptz AT TIME ZONE 'Asia/Shanghai') AT TIME ZONE 'Asia/Shanghai'
       )::int AS daily_used,
       COUNT(*) FILTER (WHERE status = 'approved')::int AS approved_total
     FROM worker_order_cancel_requests
     WHERE worker_id = $1`,
    [Number(workerId), now],
  )
  const row = result.rows[0]
  const approvedTotal = Number(row?.approved_total || 0)
  const nextPenaltyAt = Math.ceil((approvedTotal + 1) / 10) * 10
  return {
    dailyUsed: Number(row?.daily_used || 0),
    dailyLimit: 5,
    approvedTotal,
    nextPenaltyAt,
    remainingToPenalty: Math.max(0, nextPenaltyAt - approvedTotal),
  }
}

/** 客服审核通过时在同一事务内撤单、退押金并执行等级处罚。 */
export async function reviewWorkerCancelRequest(input: {
  requestId: number
  approved: boolean
  reviewNote: string
  actorName: string
  now: string
}): Promise<{
  request: WorkerCancelRequestRow | null
  order: WorkOrderRow | null
  releasedDepositAmount: number
  levelChanged: boolean
  accountDisabled: boolean
  failureReason: 'request_not_found' | 'request_not_pending' | 'target_state_changed' | null
}> {
  return withTransaction(async (client) => {
    const requestResult = await client.query<WorkerCancelRequestRow>(
      'SELECT * FROM worker_order_cancel_requests WHERE id = $1 FOR UPDATE',
      [input.requestId],
    )
    const request = requestResult.rows[0] || null
    if (!request) {
      return {
        request: null,
        order: null,
        releasedDepositAmount: 0,
        levelChanged: false,
        accountDisabled: false,
        failureReason: 'request_not_found',
      }
    }
    if (request.status !== 'pending') {
      return {
        request: await getWorkerCancelRequestByIdWithClient(client, request.id),
        order: null,
        releasedDepositAmount: 0,
        levelChanged: false,
        accountDisabled: false,
        failureReason: 'request_not_pending',
      }
    }

    const workOrderResult = await client.query<WorkOrderRow>(
      'SELECT * FROM work_orders WHERE id = $1 FOR UPDATE',
      [request.work_order_id],
    )
    const workOrder = workOrderResult.rows[0] || null
    if (!workOrder) {
      return {
        request: null,
        order: null,
        releasedDepositAmount: 0,
        levelChanged: false,
        accountDisabled: false,
        failureReason: 'target_state_changed',
      }
    }

    if (!input.approved) {
      await client.query(
        `UPDATE worker_order_cancel_requests
         SET status = 'rejected', review_note = $1, reviewed_by = $2, reviewed_at = $3, updated_at = $3
         WHERE id = $4`,
        [input.reviewNote, input.actorName, input.now, request.id],
      )
      await createWorkOrderEventWithClient(client, {
        workOrderId: Number(workOrder.id),
        actorType: 'admin',
        actorId: input.actorName,
        eventType: 'cancel_request_rejected',
        fromStatus: workOrder.status,
        toStatus: workOrder.status,
        payloadJson: JSON.stringify({ requestId: request.id, note: input.reviewNote }),
        now: input.now,
      })
      return {
        request: await getWorkerCancelRequestByIdWithClient(client, request.id),
        order: await getWorkOrderByIdWithClient(client, workOrder.id),
        releasedDepositAmount: 0,
        levelChanged: false,
        accountDisabled: false,
        failureReason: null,
      }
    }

    let releasedDepositAmount = 0
    // 赠送单与拼单互斥，拼单份额分支不会命中赠送单；整单分支按确认加好友判定扣押金。
    const forfeitGiftDeposit = isWorkOrderGiftDepositForfeitable(workOrder)
    if (request.work_order_share_id) {
      const shareResult = await client.query<WorkOrderShareRow>(
        `SELECT * FROM work_order_shares WHERE id = $1 AND work_order_id = $2 FOR UPDATE`,
        [request.work_order_share_id, request.work_order_id],
      )
      const share = shareResult.rows[0] || null
      if (
        !share ||
        Number(share.worker_id) !== Number(request.worker_id) ||
        !resolveWorkOrderShareCancellationStatus(workOrder.status, share.status)
      ) {
        return {
          request: null,
          order: null,
          releasedDepositAmount: 0,
          levelChanged: false,
          accountDisabled: false,
          failureReason: 'target_state_changed',
        }
      }
      await ensureWorkerWalletWithClient(client, Number(request.worker_id), input.now)
      const wallet = await getWorkerWalletWithClient(client, Number(request.worker_id))
      releasedDepositAmount = Math.min(
        Number(share.share_deposit || 0),
        Number(wallet?.frozen_deposit_amount || 0),
      )
      if (releasedDepositAmount > 0) {
        const nextAvailable = Number(wallet?.available_amount || 0) + releasedDepositAmount
        const nextFrozen = Math.max(
          0,
          Number(wallet?.frozen_deposit_amount || 0) - releasedDepositAmount,
        )
        await client.query(
          `UPDATE worker_wallets
           SET available_amount = $1, frozen_deposit_amount = $2, updated_at = $3
           WHERE worker_id = $4`,
          [nextAvailable, nextFrozen, input.now, request.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, 'deposit_release', $2, $3, $4, $5, '打手退单申请审核通过退还拼单押金', $6::jsonb, $7)`,
          [
            request.worker_id,
            releasedDepositAmount,
            nextAvailable,
            nextFrozen,
            request.work_order_id,
            JSON.stringify({ requestId: request.id, shareId: share.id, reason: request.reason }),
            input.now,
          ],
        )
      }
      await client.query(
        `UPDATE work_order_shares SET status = 'cancelled', updated_at = $1 WHERE id = $2`,
        [input.now, share.id],
      )
      await client.query(
        `UPDATE work_orders
         SET status = 'open', submitted_at = NULL, published_at = COALESCE(published_at, $1),
             hall_queued_at = COALESCE(hall_queued_at, $1), updated_at = $1
         WHERE id = $2`,
        [input.now, workOrder.id],
      )
    } else {
      if (
        workOrder.status !== 'in_progress' ||
        Number(workOrder.assigned_worker_id || 0) !== Number(request.worker_id)
      ) {
        return {
          request: null,
          order: null,
          releasedDepositAmount: 0,
          levelChanged: false,
          accountDisabled: false,
          failureReason: 'target_state_changed',
        }
      }
      await ensureWorkerWalletWithClient(client, Number(request.worker_id), input.now)
      const wallet = await getWorkerWalletWithClient(client, Number(request.worker_id))
      releasedDepositAmount = Math.min(
        await getOutstandingDepositAmountWithClient(
          client,
          Number(request.worker_id),
          workOrder.id,
        ),
        Number(wallet?.frozen_deposit_amount || 0),
      )
      await client.query(
        `UPDATE work_orders
         SET status = 'open', assigned_worker_id = NULL, assigned_at = NULL, deadline_at = NULL,
             published_at = $1, hall_queued_at = $1, updated_at = $1
         WHERE id = $2`,
        [input.now, workOrder.id],
      )
      if (releasedDepositAmount > 0) {
        const nextAvailable =
          Number(wallet?.available_amount || 0) + (forfeitGiftDeposit ? 0 : releasedDepositAmount)
        const nextFrozen = Math.max(
          0,
          Number(wallet?.frozen_deposit_amount || 0) - releasedDepositAmount,
        )
        await client.query(
          `UPDATE worker_wallets
           SET available_amount = $1, frozen_deposit_amount = $2, updated_at = $3
           WHERE worker_id = $4`,
          [nextAvailable, nextFrozen, input.now, request.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, $2, $3, $4, $5, $6, $7, $8::jsonb, $9)`,
          [
            request.worker_id,
            forfeitGiftDeposit ? 'deposit_deduction' : 'deposit_release',
            forfeitGiftDeposit ? -releasedDepositAmount : releasedDepositAmount,
            nextAvailable,
            nextFrozen,
            request.work_order_id,
            forfeitGiftDeposit
              ? '打手退单申请审核通过扣除押金（已确认加好友）'
              : '打手退单申请审核通过退还押金',
            JSON.stringify({
              requestId: request.id,
              reason: request.reason,
              giftDepositForfeited: forfeitGiftDeposit || undefined,
            }),
            input.now,
          ],
        )
      }
    }

    const penalty = await applyWorkerCancelRequestPenaltyWithClient(client, {
      workerId: Number(request.worker_id),
      requestId: Number(request.id),
      now: input.now,
    })
    await client.query(
      `UPDATE worker_order_cancel_requests
       SET status = 'approved', review_note = $1, reviewed_by = $2, reviewed_at = $3,
           level_before_id = $4, updated_at = $3
       WHERE id = $5`,
      [input.reviewNote, input.actorName, input.now, penalty.levelBeforeId, request.id],
    )
    const updatedOrder = await getWorkOrderByIdWithClient(client, workOrder.id)
    await createWorkOrderEventWithClient(client, {
      workOrderId: Number(workOrder.id),
      actorType: 'admin',
      actorId: input.actorName,
      eventType: 'cancel_request_approved',
      fromStatus: workOrder.status,
      toStatus: updatedOrder?.status || workOrder.status,
      payloadJson: JSON.stringify({
        requestId: request.id,
        shareId: request.work_order_share_id,
        releasedDepositAmount: forfeitGiftDeposit ? 0 : releasedDepositAmount,
        forfeitedDepositAmount: forfeitGiftDeposit ? releasedDepositAmount : 0,
        levelChanged: penalty.levelChanged,
        accountDisabled: penalty.accountDisabled,
        note: input.reviewNote,
      }),
      now: input.now,
    })
    return {
      request: await getWorkerCancelRequestByIdWithClient(client, request.id),
      order: updatedOrder,
      releasedDepositAmount: forfeitGiftDeposit ? 0 : releasedDepositAmount,
      levelChanged: penalty.levelChanged,
      accountDisabled: penalty.accountDisabled,
      failureReason: null,
    }
  })
}
