import { query } from '../../db/client.js'
import type { JsonObject } from '../../types/json.js'
import { nowIso } from '../../utils/time.js'

const CHINA_TIME_ZONE = 'Asia/Shanghai'
const DAY_MS = 24 * 60 * 60 * 1000

type FinanceRange = {
  from: string
  to: string
  fromDate: string
  toDate: string
  bucketFrom: string
  bucketTo: string
}

const FINANCE_EVENTS_CTE = `
  WITH finance_events AS (
    SELECT
      'order_paid'::text AS category,
      o.id::bigint AS source_id,
      COALESCE(o.paid_at, o.created_at) AS occurred_at,
      o.total_amount::bigint AS amount,
      '订单收款'::text AS label,
      o.platform_order_id::text AS reference_no,
      COALESCE(NULLIF(CONCAT_WS('/', NULLIF(o.provider, ''), NULLIF(o.platform, '')), ''), '其他')::text AS channel
    FROM orders o
    WHERE o.pay_status IN ('paid', 'refunded')
      AND o.total_amount > 0

    UNION ALL

    SELECT
      'order_refund'::text,
      o.id::bigint,
      COALESCE(o.updated_at, o.created_at),
      o.total_amount::bigint,
      '订单退款'::text,
      o.platform_order_id::text,
      COALESCE(NULLIF(CONCAT_WS('/', NULLIF(o.provider, ''), NULLIF(o.platform, '')), ''), '其他')::text
    FROM orders o
    WHERE o.pay_status = 'refunded'
      AND o.total_amount > 0

    UNION ALL

    SELECT
      CASE WHEN wl.ledger_type IN ('reward_settlement', 'sharing_reward')
        THEN 'worker_reward' ELSE 'worker_recovery' END::text,
      wl.id::bigint,
      wl.created_at,
      ABS(wl.amount)::bigint,
      CASE WHEN wl.ledger_type IN ('reward_settlement', 'sharing_reward')
        THEN '打手报酬' ELSE '打手资金回收' END::text,
      COALESCE(wl.related_work_order_id::text, ''),
      'worker-platform'::text
    FROM worker_wallet_ledgers wl
    WHERE wl.ledger_type IN (
      'reward_settlement', 'sharing_reward', 'after_sales_deposit_deduction',
      'after_sales_balance_deduction', 'after_sales_debt_offset'
    )
      AND wl.amount <> 0

    UNION ALL

    SELECT
      'withdraw_paid'::text,
      wfr.id::bigint,
      wfr.reviewed_at,
      wfr.amount::bigint,
      '打手提现'::text,
      wfr.account_channel::text,
      COALESCE(NULLIF(wfr.account_channel, ''), '其他')::text
    FROM worker_finance_requests wfr
    WHERE wfr.request_type = 'withdraw'
      AND wfr.status = 'approved'
      AND wfr.reviewed_at IS NOT NULL
      AND wfr.amount > 0

    UNION ALL

    SELECT
      'recharge'::text,
      wfr.id::bigint,
      wfr.reviewed_at,
      wfr.amount::bigint,
      '打手充值'::text,
      wfr.account_channel::text,
      COALESCE(NULLIF(wfr.account_channel, ''), '其他')::text
    FROM worker_finance_requests wfr
    WHERE wfr.request_type = 'recharge'
      AND wfr.status = 'approved'
      AND wfr.reviewed_at IS NOT NULL
      AND wfr.amount > 0

    UNION ALL

    SELECT
      'after_sales_refund'::text,
      wasc.id::bigint,
      wasc.refunded_at,
      wasc.refunded_amount::bigint,
      '售后追缴退还'::text,
      wasc.case_no::text,
      'worker-platform'::text
    FROM worker_after_sales_cases wasc
    WHERE wasc.refunded_at IS NOT NULL
      AND wasc.refunded_amount > 0
  )
`

export async function getAdminFinanceSummary(payload: JsonObject = {}) {
  const range = resolveFinanceRange(payload, nowIso())
  const [dailyResult, snapshotResult] = await Promise.all([
    query<FinanceDailyRow>(
      `${FINANCE_EVENTS_CTE}
       SELECT
         to_char(day AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM-DD') AS day,
         COALESCE(SUM(amount) FILTER (WHERE category = 'order_paid'), 0)::bigint AS paid_amount,
         COUNT(*) FILTER (WHERE category = 'order_paid')::int AS paid_order_count,
         COALESCE(SUM(amount) FILTER (WHERE category = 'order_refund'), 0)::bigint AS refund_amount,
         COALESCE(SUM(amount) FILTER (WHERE category = 'worker_reward'), 0)::bigint AS worker_reward_amount,
         COALESCE(SUM(amount) FILTER (WHERE category = 'withdraw_paid'), 0)::bigint AS withdraw_amount,
         COALESCE(SUM(amount) FILTER (WHERE category = 'recharge'), 0)::bigint AS recharge_amount,
         COALESCE(SUM(amount) FILTER (WHERE category = 'after_sales_refund'), 0)::bigint AS after_sales_refund_amount,
         COALESCE(SUM(amount) FILTER (WHERE category = 'worker_recovery'), 0)::bigint AS worker_recovery_amount
       FROM generate_series($3::timestamptz, $4::timestamptz - interval '1 day', interval '1 day') day
       LEFT JOIN finance_events event
         ON event.occurred_at >= $1::timestamptz
        AND event.occurred_at < $2::timestamptz
        AND event.occurred_at >= day
        AND event.occurred_at < day + interval '1 day'
       GROUP BY day
       ORDER BY day DESC`,
      [range.from, range.to, range.bucketFrom, range.bucketTo],
    ),
    query<FinanceSnapshotRow>(
      `
        SELECT
          (SELECT COALESCE(SUM(available_amount), 0)::bigint FROM worker_wallets) AS available_amount,
          (SELECT COALESCE(SUM(frozen_deposit_amount), 0)::bigint FROM worker_wallets) AS frozen_deposit_amount,
          (SELECT COALESCE(SUM(pending_unfreeze_amount), 0)::bigint FROM worker_wallets) AS pending_unfreeze_amount,
          (SELECT COALESCE(SUM(frozen_withdraw_amount), 0)::bigint FROM worker_wallets) AS frozen_withdraw_amount,
          (SELECT COALESCE(SUM(amount), 0)::bigint FROM worker_finance_requests WHERE status = 'pending' AND request_type = 'withdraw') AS pending_withdraw_amount,
          (SELECT COUNT(*)::int FROM worker_finance_requests WHERE status = 'pending' AND request_type = 'withdraw') AS pending_withdraw_count
      `,
    ),
  ])

  const daily = dailyResult.rows.map(mapFinanceDailyRow)
  const channelDailyResult = await query<FinanceChannelDailyRow>(
    `${FINANCE_EVENTS_CTE}
     SELECT
       to_char(event.occurred_at AT TIME ZONE 'Asia/Shanghai', 'YYYY-MM-DD') AS day,
       event.channel,
       event.category,
       COUNT(*)::int AS total,
       COALESCE(SUM(event.amount), 0)::bigint AS amount
     FROM finance_events event
     WHERE event.occurred_at >= $1::timestamptz
       AND event.occurred_at < $2::timestamptz
     GROUP BY 1, 2, 3
     ORDER BY day DESC, event.channel ASC, event.category ASC`,
    [range.from, range.to],
  )
  const totals = daily.reduce(
    (result, row) => ({
      paidOrderCount: result.paidOrderCount + row.paidOrderCount,
      paidAmount: result.paidAmount + row.paidAmount,
      refundAmount: result.refundAmount + row.refundAmount,
      workerRewardAmount: result.workerRewardAmount + row.workerRewardAmount,
      withdrawAmount: result.withdrawAmount + row.withdrawAmount,
      rechargeAmount: result.rechargeAmount + row.rechargeAmount,
      afterSalesRefundAmount: result.afterSalesRefundAmount + row.afterSalesRefundAmount,
      workerRecoveryAmount: result.workerRecoveryAmount + row.workerRecoveryAmount,
      netCashFlow: result.netCashFlow + row.netCashFlow,
    }),
    emptyFinanceTotals(),
  )
  const snapshot = snapshotResult.rows[0] || emptyFinanceSnapshot()

  return {
    range: { from: range.fromDate, to: range.toDate },
    overview: {
      ...totals,
      availableAmount: Number(snapshot.available_amount || 0),
      frozenDepositAmount: Number(snapshot.frozen_deposit_amount || 0),
      pendingUnfreezeAmount: Number(snapshot.pending_unfreeze_amount || 0),
      frozenWithdrawAmount: Number(snapshot.frozen_withdraw_amount || 0),
      pendingWithdrawAmount: Number(snapshot.pending_withdraw_amount || 0),
      pendingWithdrawCount: Number(snapshot.pending_withdraw_count || 0),
    },
    daily,
    channelDaily: channelDailyResult.rows.map((row) => ({
      date: String(row.day).slice(0, 10),
      channel: row.channel || '其他',
      category: row.category,
      count: Number(row.total || 0),
      amount: Number(row.amount || 0),
    })),
  }
}

export async function listAdminFinanceTransactions(payload: JsonObject = {}) {
  const range = resolveFinanceRange(payload, nowIso())
  const page = normalizePositiveInt(payload.page, 1)
  const pageSize = Math.min(normalizePositiveInt(payload.pageSize, 20), 100)
  const category = String(payload.category || '').trim()
  const channel = String(payload.channel || '').trim()
  const allowedCategories = new Set([
    'order_paid',
    'order_refund',
    'worker_reward',
    'worker_recovery',
    'withdraw_paid',
    'recharge',
    'after_sales_refund',
  ])
  const params: unknown[] = [range.from, range.to]
  const categoryClause = allowedCategories.has(category)
    ? ` AND event.category = $${params.push(category)}`
    : ''
  const channelClause = channel ? ` AND event.channel = $${params.push(channel)}` : ''
  const offset = (page - 1) * pageSize
  const countResult = await query<{ total: number }>(
    `${FINANCE_EVENTS_CTE}
     SELECT COUNT(*)::int AS total
     FROM finance_events event
       WHERE event.occurred_at >= $1
       AND event.occurred_at < $2${categoryClause}${channelClause}`,
    params,
  )
  const itemsParams = [...params, pageSize, offset]
  const itemsResult = await query<FinanceTransactionRow>(
    `${FINANCE_EVENTS_CTE}
     SELECT category, source_id, occurred_at, amount, label, reference_no, channel
     FROM finance_events event
     WHERE event.occurred_at >= $1
       AND event.occurred_at < $2${categoryClause}${channelClause}
     ORDER BY event.occurred_at DESC, event.source_id DESC
     LIMIT $${itemsParams.length - 1} OFFSET $${itemsParams.length}`,
    itemsParams,
  )
  return {
    items: itemsResult.rows.map((row) => ({
      category: row.category,
      sourceId: Number(row.source_id),
      occurredAt: row.occurred_at,
      amount: Number(row.amount || 0),
      direction: ['order_paid', 'recharge', 'worker_recovery'].includes(row.category)
        ? 'in'
        : 'out',
      label: row.label,
      referenceNo: row.reference_no || '',
      channel: row.channel || '其他',
    })),
    pagination: { page, pageSize, total: Number(countResult.rows[0]?.total || 0) },
    range: { from: range.fromDate, to: range.toDate },
  }
}

export function resolveFinanceRange(payload: JsonObject = {}, now = nowIso()): FinanceRange {
  const today = chinaDateParts(now)
  const fromInput = String(payload.dateFrom || payload.from || '')
  const toInput = String(payload.dateTo || payload.to || '')
  const fromDate = normalizeDateTimeDate(fromInput) || today
  const toDate = normalizeDateTimeDate(toInput) || fromDate
  const [safeFrom, safeTo] = fromDate <= toDate ? [fromDate, toDate] : [toDate, fromDate]
  const from = parseFinanceBoundary(fromInput, safeFrom, false)
  const to = parseFinanceBoundary(toInput, safeTo, true)
  const normalizedFrom = Date.parse(from) <= Date.parse(to) ? from : chinaDateToUtcIso(safeFrom)
  const normalizedTo =
    Date.parse(from) <= Date.parse(to)
      ? to
      : new Date(Date.parse(chinaDateToUtcIso(safeTo)) + DAY_MS).toISOString()
  return {
    from: normalizedFrom,
    to: normalizedTo,
    fromDate: safeFrom,
    toDate: safeTo,
    bucketFrom: chinaDateToUtcIso(safeFrom),
    bucketTo: new Date(Date.parse(chinaDateToUtcIso(safeTo)) + DAY_MS).toISOString(),
  }
}

export function calculateFinanceNetCashFlow(row: {
  paidAmount: number
  refundAmount: number
  rechargeAmount: number
  workerRecoveryAmount: number
  withdrawAmount: number
  afterSalesRefundAmount: number
}) {
  return (
    row.paidAmount +
    row.rechargeAmount -
    row.refundAmount -
    row.withdrawAmount -
    row.afterSalesRefundAmount +
    row.workerRecoveryAmount
  )
}

type FinanceDailyRow = {
  day: string
  paid_amount: number
  paid_order_count: number
  refund_amount: number
  worker_reward_amount: number
  withdraw_amount: number
  recharge_amount: number
  after_sales_refund_amount: number
  worker_recovery_amount: number
}

type FinanceSnapshotRow = {
  available_amount: number
  frozen_deposit_amount: number
  pending_unfreeze_amount: number
  frozen_withdraw_amount: number
  pending_withdraw_amount: number
  pending_withdraw_count: number
}

type FinanceTransactionRow = {
  category: string
  source_id: number
  occurred_at: string
  amount: number
  label: string
  reference_no: string
  channel: string
}

type FinanceChannelDailyRow = {
  day: string
  channel: string
  category: string
  total: number
  amount: number
}

function mapFinanceDailyRow(row: FinanceDailyRow) {
  const mapped = {
    date: String(row.day).slice(0, 10),
    paidOrderCount: Number(row.paid_order_count || 0),
    paidAmount: Number(row.paid_amount || 0),
    refundAmount: Number(row.refund_amount || 0),
    workerRewardAmount: Number(row.worker_reward_amount || 0),
    withdrawAmount: Number(row.withdraw_amount || 0),
    rechargeAmount: Number(row.recharge_amount || 0),
    afterSalesRefundAmount: Number(row.after_sales_refund_amount || 0),
    workerRecoveryAmount: Number(row.worker_recovery_amount || 0),
  }
  return { ...mapped, netCashFlow: calculateFinanceNetCashFlow(mapped) }
}

function emptyFinanceTotals() {
  return {
    paidOrderCount: 0,
    paidAmount: 0,
    refundAmount: 0,
    workerRewardAmount: 0,
    withdrawAmount: 0,
    rechargeAmount: 0,
    afterSalesRefundAmount: 0,
    workerRecoveryAmount: 0,
    netCashFlow: 0,
  }
}

function emptyFinanceSnapshot(): FinanceSnapshotRow {
  return {
    available_amount: 0,
    frozen_deposit_amount: 0,
    pending_unfreeze_amount: 0,
    frozen_withdraw_amount: 0,
    pending_withdraw_amount: 0,
    pending_withdraw_count: 0,
  }
}

function normalizePositiveInt(value: unknown, fallback: number) {
  const parsed = Number(value)
  return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback
}

function normalizeDateTimeDate(value: string) {
  const match = value.trim().match(/^(\d{4}-\d{2}-\d{2})/)
  if (!match || Number.isNaN(Date.parse(`${match[1]}T00:00:00Z`))) return ''
  return match[1]
}

function parseFinanceBoundary(value: string, fallbackDate: string, end: boolean) {
  const normalized = value.trim().replace('T', ' ')
  if (!normalized || /^\d{4}-\d{2}-\d{2}$/.test(normalized)) {
    return new Date(Date.parse(chinaDateToUtcIso(fallbackDate)) + (end ? DAY_MS : 0)).toISOString()
  }
  const minuteMatch = normalized.match(/^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2})$/)
  const valueWithSeconds = minuteMatch
    ? `${minuteMatch[1]} ${minuteMatch[2]}:${end ? '59' : '00'}`
    : normalized
  const parsed = new Date(`${valueWithSeconds}+08:00`)
  return Number.isNaN(parsed.getTime())
    ? new Date(Date.parse(chinaDateToUtcIso(fallbackDate)) + (end ? DAY_MS : 0)).toISOString()
    : parsed.toISOString()
}

function chinaDateParts(value: string) {
  const parts = new Intl.DateTimeFormat('en-CA', {
    timeZone: CHINA_TIME_ZONE,
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
  }).formatToParts(new Date(value))
  const result = Object.fromEntries(parts.map((part) => [part.type, part.value]))
  return `${result.year}-${result.month}-${result.day}`
}

function chinaDateToUtcIso(value: string) {
  return new Date(`${value}T00:00:00+08:00`).toISOString()
}
