import type { PageParams, Paginated } from '@/lib/api/types';

/**
 * Payouts domain — the **nurse read** side of the b13 weekly-payout engine ("I did the work, where is
 * my money?"). Shapes are derived from the payouts contract
 * (`dev/contracts/domains/payouts.md` + `dev/contracts/openapi/swagger.v1.json`).
 *
 * **The contract only serves a nurse ONE endpoint** (`GET api/v1/nurse_payouts/history` →
 * `NursePayoutHistoryDto`). The **four-bucket earnings summary**, the **per-booking earnings list with a
 * money-state**, and a **nurse-readable payout detail** (batch context + booking links) are contract gaps
 * filed as **REQ-025** and mocked behind the `PayoutsApi` seam this phase — so the domain is mock-primary
 * (see `constants.ts`). When REQ-025 lands, only `apis/clientApi.ts` flips; hooks/screens are unchanged.
 *
 * Load-bearing money/authority semantics (contract + phase §5):
 *  - Money is **IRR digit-strings**, integer-safe via the money util; **never** `Number()`/float math.
 *    Toman is **display-only**. The three booking amounts satisfy `gross = commission + payout`.
 *  - The nurse **payable balance is derived from the ledger and MAY be negative** ("owed back") — model it
 *    as a **signed** string; **never clamp to zero**. A clawback **nets**, it does not auto-reverse.
 *  - **Eligibility is server truth** (EVV complete AND `dispute_window_ends_at < now`). The client only ever
 *    *renders* a cosmetic countdown off `disputeWindowEndsAt`; it **never computes eligibility** client-side.
 *  - **Read-only:** a nurse never triggers a transfer, retries a payout, or runs a batch (admin actions, f15).
 *  - The **BNPL provider commission is NEVER a nurse deduction** — it does not appear anywhere here; the nurse
 *    amount is payment-method-invariant (`gross − balinyaar_commission`, identical for card vs BNPL).
 *
 * Enums cross the wire as stable string codes — mirrored here as string-literal unions; labels are i18n keys.
 */

/**
 * The four money states a nurse cares about, per completed booking. This is a **client display model**
 * (there is no single wire enum for it — REQ-025); it is derived server-side from the ledger + dispute
 * window + payout link:
 *  - `pending` — still in escrow, the dispute window is open (not yet payout-eligible).
 *  - `eligible` — cleared (dispute window closed), awaiting the next weekly batch.
 *  - `paid` — transferred (carries `paidAt` + `transferReference` + the owning `nursePayoutId`).
 *  - `clawback_applied` — a refund-after-payout netted the original earning out of the total.
 */
export type EarningsState = 'pending' | 'eligible' | 'paid' | 'clawback_applied';

/** Stable render/filter order for the state-segmented list. */
export const EARNINGS_STATES: readonly EarningsState[] = [
  'pending',
  'eligible',
  'paid',
  'clawback_applied',
] as const;

/**
 * `PayoutStatus` (contract enum) — the per-payout lifecycle, **forward-only**. `paid` is an irreversible
 * transfer with no outgoing edge; `failed` re-submits on an **admin** retry. NB the contract uses
 * `submitted` (not "processing"): a payout handed to the bank rail, awaiting settlement.
 */
export type PayoutStatus = 'pending' | 'submitted' | 'paid' | 'failed';

/** `PayoutBatchStatus` (contract enum) — the batch lifecycle a payout's batch context reports. */
export type PayoutBatchStatus = 'draft' | 'processing' | 'partially_failed' | 'completed' | 'failed';

/** A payout is settled (no further movement the nurse can affect) once `paid` or `failed`. */
export function isTerminalPayoutStatus(status: PayoutStatus): boolean {
  return status === 'paid' || status === 'failed';
}

/**
 * The four-bucket earnings roll-up + the derived net payable balance (REQ-025 — the summary shape the
 * contract does not yet serve). Every amount is an IRR digit-string.
 */
export interface NurseEarningsSummary {
  /** Still in escrow, dispute window open. */
  pendingTotalIrr: string;
  /** Cleared, awaiting the next weekly batch. */
  eligibleTotalIrr: string;
  /** Lifetime transferred. */
  paidTotalIrr: string;
  /** Clawback receivable not yet recovered (netted from a future batch). */
  clawbackOutstandingIrr: string;
  /**
   * **Signed** IRR digit-string — what Balinyaar currently owes the nurse (ledger-derived). **MAY be
   * negative** when outstanding clawbacks exceed accrued-unpaid earnings ("owed back"). Never clamp.
   */
  netPayableBalanceIrr: string;
}

/** One completed booking contributing to earnings (REQ-025). Enough fields to deep-link + explain each state. */
export interface NurseEarningsItem {
  /** The booking this earning is for — deep-links to the f8 nurse booking detail. */
  bookingId: number;
  patientName: string;
  /** ISO date `YYYY-MM-DD`. */
  scheduledDate: string;
  /** The three amounts — IRR digit-strings; `grossPriceIrr = balinyaarCommissionIrr + nursePayoutAmount`. */
  grossPriceIrr: string;
  balinyaarCommissionIrr: string;
  nursePayoutAmount: string;
  state: EarningsState;
  /** Drives the **display-only** pending countdown; `null` once past. The server owns eligibility. */
  disputeWindowEndsAt: string | null;
  /** Server truth: when this amount became payout-eligible. `null` while pending. Never computed here. */
  payoutEligibleAt: string | null;
  /** `paid` only: when the transfer landed + its opaque reconciliation reference + the owning payout/batch. */
  paidAt: string | null;
  transferReference: string | null;
  nursePayoutId: number | null;
  batchId: number | null;
  /** `clawback_applied` only: the clawed-back amount and the resulting net (`= nursePayoutAmount − clawback`). */
  clawbackAppliedIrr: string | null;
  netAmountIrr: string | null;
}

/** One `nurse_payouts` row in the nurse's own history (`NursePayoutHistoryDto` + REQ-025 `failureReason`). */
export interface NursePayoutHistoryItem {
  id: number;
  batchId: number;
  status: PayoutStatus;
  /** `netAmountIrr = grossEarningsIrr − clawbackAppliedIrr`, guaranteed server-side. */
  grossEarningsIrr: string;
  clawbackAppliedIrr: string;
  netAmountIrr: string;
  /** Masked, **last-4 only** — an encrypted field; never a full IBAN. */
  maskedIban: string;
  transferReference: string | null;
  paidAt: string | null;
  /** The batch window (holiday-shifted server-side), ISO dates `YYYY-MM-DD`. */
  periodStart: string;
  periodEnd: string;
  /** `failed` only (REQ-025 — the nurse history DTO lacks it today). Read-only; the nurse cannot retry. */
  failureReason: string | null;
}

/** A booking a payout covered (`PayoutBookingLinkDto`). One booking appears in exactly one payout, forever. */
export interface NursePayoutBookingLink {
  bookingId: number;
  sessionId: number | null;
  /** This booking's nurse-payout share. Σ over a payout's links = its `grossEarningsIrr`. */
  payoutAmountIrr: string;
}

/** The `nurse_payout_batches` context a nurse sees for one of their payouts (subset of `PayoutBatchDto`). */
export interface NursePayoutBatchContext {
  id: number;
  /** Holiday-shifted server-side; ISO dates `YYYY-MM-DD`. */
  periodStart: string;
  periodEnd: string;
  processingDate: string;
  status: PayoutBatchStatus;
  totalAmount: string;
  payoutCount: number;
  processedAt: string | null;
}

/**
 * One payout expanded — the nurse's reconciliation view (REQ-025, nurse-scoped analogue of the admin
 * `PayoutDto`): the money decomposition, the batch window, and the exact bookings it covered.
 */
export interface NursePayoutDetail {
  id: number;
  batchId: number;
  status: PayoutStatus;
  grossEarningsIrr: string;
  clawbackAppliedIrr: string;
  /** `= grossEarningsIrr − clawbackAppliedIrr`. */
  netAmountIrr: string;
  /** What was actually transferred (`= netAmountIrr` on a clean payout). */
  amountIrr: string;
  maskedIban: string;
  transferReference: string | null;
  paidAt: string | null;
  failureReason: string | null;
  batch: NursePayoutBatchContext;
  bookings: NursePayoutBookingLink[];
}

/** `getNurseEarnings` query params — the state filter is part of the query key so each tab caches separately. */
export interface EarningsListParams extends PageParams {
  state?: EarningsState;
}

// ── Admin payout-batch actions (b13 `admin_payouts/*`) ────────────────────────────────────────────────
//
// The admin side of the same weekly engine: preview eligible earnings, open/run a batch, read batches +
// per-payout rows, retry a failed payout, record a reconciled transfer reference. These map published b13
// routes 1:1 (`clientApi.ts`) but the domain stays **mock-primary** this phase (see `constants.ts`).
// Money is IRR digit-strings; the client renders the **server's** eligibility + holiday-shifted date — it
// never computes them. Invariants: per eligible nurse / payout `gross − clawback = net`; a batch's
// `totalAmount = Σ its payouts' net`; a payout's booking links sum to its `grossEarningsIrr`.

/** One nurse's payout-eligible, unpaid earnings for a window (`EligibleNurseEarningsDto`). A nurse missing a
 *  verified primary IBAN is **flagged** (`hasVerifiedPrimaryIban:false`) here, not dropped. */
export interface EligibleNurseEarnings {
  nurseId: number;
  nurseName: string | null;
  bookingCount: number;
  grossEarningsIrr: string;
  clawbackAppliedIrr: string;
  /** `= grossEarningsIrr − clawbackAppliedIrr` (clawbacks netted into the preview). */
  netAmountIrr: string;
  hasVerifiedPrimaryIban: boolean;
}

/** A nurse excluded from a generated batch, with the reason (`SkippedNurseDto`; e.g. `no_verified_primary_iban`). */
export interface SkippedNurse {
  nurseId: number;
  nurseName: string | null;
  grossEarningsIrr: string;
  reason: string;
}

/** A `nurse_payout_batches` header for the admin reconciliation list (`PayoutBatchDto` + `holidayShifted`). */
export interface PayoutBatchSummary {
  id: number;
  /** Holiday-shifted server-side; ISO dates `YYYY-MM-DD`. */
  periodStart: string;
  periodEnd: string;
  processingDate: string;
  /** `= Σ its payouts' net_amount_irr`. IRR digit-string. */
  totalAmount: string;
  payoutCount: number;
  status: PayoutBatchStatus;
  processedAt: string | null;
  failureNotes: string | null;
  createdAt: string;
  /** Whether `processingDate` was shifted off a bank-closed day (server truth; the client only renders it). */
  holidayShifted: boolean;
}

/** The dry-run before a batch: the eligible nurses, the ones that would be skipped, and the shifted date. */
export interface PayoutBatchPreview {
  periodStart: string;
  periodEnd: string;
  processingDate: string;
  holidayShifted: boolean;
  eligible: EligibleNurseEarnings[];
  skipped: SkippedNurse[];
  /** `= Σ eligible.netAmountIrr`. IRR digit-string. */
  totalNetIrr: string;
}

/** One `nurse_payouts` row expanded for the admin batch detail (`PayoutDto`). Every row reconciles:
 *  `grossEarningsIrr − clawbackAppliedIrr = netAmountIrr`; on a clean payout `amountIrr = netAmountIrr`. */
export interface AdminPayoutRow {
  id: number;
  nurseId: number;
  nurseName: string | null;
  /** Masked, **last-4 only** — an encrypted field; never a full IBAN. */
  maskedIban: string;
  grossEarningsIrr: string;
  clawbackAppliedIrr: string;
  netAmountIrr: string;
  /** What was actually transferred (`PayoutDto.amount`); `= netAmountIrr` on a clean payout. */
  amountIrr: string;
  status: PayoutStatus;
  transferReference: string | null;
  paidAt: string | null;
  /** `failed` only. Empty otherwise. */
  failureReason: string | null;
  /** The bookings this payout covered; `Σ payoutAmountIrr = grossEarningsIrr`. */
  bookings: { bookingId: number; sessionId: number | null; payoutAmountIrr: string }[];
}

/** A batch header + its paginated payout rows (`PayoutBatchDetailDto`). */
export interface AdminPayoutBatchDetail {
  batch: PayoutBatchSummary;
  payouts: AdminPayoutRow[];
  total: number;
  page: number;
  pageSize: number;
}

/** `listPayoutBatches` filter — the optional status is part of the query key so each filter caches separately. */
export interface PayoutBatchFilters {
  status?: PayoutBatchStatus;
}

/**
 * The payouts API seam — the real HTTP client and the in-memory mock both implement this interface;
 * selection is by config (`USE_PAYOUTS_MOCK`), never scattered `if (mock)` checks.
 *
 * The **nurse read** methods are all reads (a nurse never writes payout state). The **admin** methods
 * (`admin_payouts/*`) add the batch actions: previewing eligibility, running a batch, retrying a failed
 * payout, and recording a transfer reference. `runPayoutBatch`/`retryPayout` are **idempotency-keyed** —
 * the same key returns the same result (never a double-pay).
 */
export interface PayoutsApi {
  // Nurse read side.
  getNurseEarningsBalance(): Promise<NurseEarningsSummary>;
  getNurseEarnings(params: EarningsListParams): Promise<Paginated<NurseEarningsItem>>;
  getNursePayoutHistory(params: PageParams): Promise<Paginated<NursePayoutHistoryItem>>;
  getNursePayoutDetail(payoutId: number): Promise<NursePayoutDetail>;

  // Admin batch actions.
  listPayoutBatches(filters: PayoutBatchFilters, params: PageParams): Promise<Paginated<PayoutBatchSummary>>;
  previewPayoutBatch(periodStart: string, periodEnd: string): Promise<PayoutBatchPreview>;
  runPayoutBatch(periodStart: string, periodEnd: string, idempotencyKey: string): Promise<PayoutBatchSummary>;
  getPayoutBatchDetail(batchId: number, page: number): Promise<AdminPayoutBatchDetail>;
  retryPayout(payoutId: number, idempotencyKey: string): Promise<void>;
  recordTransferReference(payoutId: number, reference: string): Promise<void>;
}
