import { Entity, Column, Index, ManyToOne, JoinColumn, OneToMany } from 'typeorm';
import { BaseEntity } from '../database/base.entity';
import { BookingEntity } from './booking.entity';
import { CurrencyEntity } from './currency.entity';
import { BookingChangeRequestEntity } from './booking-change-request.entity';
import { SettlementPaymentEntity } from './settlement-payment.entity';
import { SupplierEntity } from './supplier.entity';
import { BookingSettlementMasterEntity } from './booking-settlement-master.entity';
import { UserEntity } from './user.entity';

/**
 * Who the settlement is with.
 *
 *   CUSTOMER — money flowing to/from the guest who booked the trip.
 *              `supplier_id` is null, `booking_id` carries the row.
 *   SUPPLIER — money flowing to/from a supplier we owe (hotel,
 *              transport, activity). `supplier_id` identifies the
 *              party; `booking_id` stays populated so the row remains
 *              discoverable from the booking detail page.
 *              `source_item_*` points at the booking line that
 *              spawned the row (e.g. `booking_transport_item` + uuid).
 *
 * The booking-level `payment_status` rollup considers ONLY CUSTOMER
 * rows. Supplier-side rows surface on the Settlements list and on
 * the Supplier detail page, but never affect the customer pill.
 */
export enum SettlementParty {
  CUSTOMER = 'CUSTOMER',
  SUPPLIER = 'SUPPLIER',
}

/**
 * Direction of money flow.
 *
 *   COLLECTION — money we expect to receive from the customer.
 *                Created on booking confirmation (full booking total)
 *                and on every itinerary edit that increases the booking
 *                total (delta = new − old).
 *   REFUND     — money we owe back to the customer.
 *                Created when an itinerary edit decreases the booking
 *                total. Always positive amount; sign is carried by the
 *                `type`, not by the number.
 */
export enum SettlementType {
  COLLECTION = 'COLLECTION',
  REFUND = 'REFUND',
}

/**
 * Lifecycle status of a settlement row.
 *
 *   PENDING  — no payment received yet.
 *   PARTIAL  — at least one payment recorded but the running total
 *              has not reached `amount`. Set by `recordPayment` when
 *              `amount_settled < amount`.
 *   SETTLED  — running total of payments equals `amount`. Set by
 *              `recordPayment` when the threshold is hit; flips the
 *              row read-only for further payments.
 */
export enum SettlementStatus {
  PENDING = 'PENDING',
  PARTIAL = 'PARTIAL',
  SETTLED = 'SETTLED',
}

/**
 * Payment Settlement — one row per money event tied to a booking.
 *
 * Created automatically by `PaymentSettlementService.recordFromBooking`
 * at booking confirmation (initial COLLECTION) and by every itinerary
 * edit that produces a non-zero delta. Settlements never delete; once
 * created, the `status` lifecycle is the only mutation surface.
 *
 * Partial payments are tracked via the `settlement_payments` child
 * ledger (see SettlementPaymentEntity) — `amount_settled` here is the
 * running sum kept in sync by `PaymentSettlementService.recordPayment`
 * for O(1) reads on the list page.
 *
 * Booking-level rollup (`bookings.payment_status`) is recomputed from
 * the full set of active settlements for the booking on every change.
 */
@Entity('payment_settlements')
// Common filter on the list page is (status, due_date) → composite
// helps the overdue-banner query stay sub-millisecond on large tenants.
@Index(['tenant_id', 'status', 'due_date'])
@Index(['tenant_id', 'booking_id'])
export class PaymentSettlementEntity extends BaseEntity {
  @ManyToOne(() => BookingEntity, { onDelete: 'CASCADE' })
  @JoinColumn({ name: 'booking_id' })
  booking: BookingEntity;

  @Column({ type: 'uuid' })
  booking_id: string;

  @Column({ type: 'enum', enum: SettlementType })
  type: SettlementType;

  /**
   * Always positive (sign carried by `type`). Frozen at creation
   * time — never edited except by the admin-only "edit amount"
   * action, which is gated on BOOKING_APPROVAL:edit.
   */
  @Column({ type: 'decimal', precision: 12, scale: 2 })
  amount: number;

  /**
   * Running sum of payments recorded against this settlement.
   * Maintained by `PaymentSettlementService.recordPayment` — never
   * written from the controller layer. When `amount_settled = amount`,
   * `status` flips to SETTLED in the same transaction.
   */
  @Column({ type: 'decimal', precision: 12, scale: 2, default: 0 })
  amount_settled: number;

  @ManyToOne(() => CurrencyEntity)
  @JoinColumn({ name: 'currency_id' })
  currency: CurrencyEntity;

  @Column({ type: 'uuid' })
  currency_id: string;

  @Column({ type: 'enum', enum: SettlementStatus, default: SettlementStatus.PENDING })
  status: SettlementStatus;

  /**
   * Default = travel_start of the booking (set by recordFromBooking
   * at creation). Used for the overdue badge on the list page and
   * for receivables aging buckets on the dashboard.
   */
  @Column({ type: 'date' })
  due_date: string;

  /**
   * Stamp on the LAST payment that flipped the row to SETTLED. Null
   * while status is PENDING / PARTIAL. Used by the settle-this-month
   * stat and the export. Do not confuse with `created_at`, which is
   * the audit-trail "when this settlement was raised" timestamp.
   */
  @Column({ type: 'date', nullable: true })
  settled_date: string | null;

  /**
   * Free-form append-only notes. Each /notes POST adds a paragraph;
   * the column itself is not edited piece-by-piece — the service does
   * a controlled append with a timestamp prefix.
   */
  @Column({ type: 'text', nullable: true })
  notes: string | null;

  /**
   * Snapshot of the itinerary delta that produced this settlement —
   * `{ added: [...], dropped: [...], modified: [...] }`. Null on the
   * initial-booking COLLECTION row (no delta — full booking total).
   * Frozen at create time; serves as the "service change breakdown"
   * shown on the detail page so finance doesn't have to chase a
   * separate audit lookup to know what changed.
   */
  @Column({ type: 'jsonb', nullable: true })
  change_breakdown: {
    added?: Array<Record<string, unknown>>;
    dropped?: Array<Record<string, unknown>>;
    modified?: Array<Record<string, unknown>>;
  } | null;

  /**
   * Set when this settlement was produced by a Change Request reaching
   * APPROVED state (rather than a direct admin save). Lets the detail
   * page link back to the CR for audit traceability.
   */
  @ManyToOne(() => BookingChangeRequestEntity, { nullable: true, onDelete: 'SET NULL' })
  @JoinColumn({ name: 'triggered_by_change_request_id' })
  triggered_by_change_request: BookingChangeRequestEntity | null;

  @Column({ type: 'uuid', nullable: true })
  triggered_by_change_request_id: string | null;

  /**
   * Source of the settlement — informational only, used to render a
   * small badge on the list page so finance can spot Movement-Chart
   * amendments at a glance. Stored as a free-form short code to avoid
   * a migration every time a new source appears.
   *   INITIAL          — created at booking confirmation (full total)
   *   ITINERARY_EDIT   — booking-side itinerary editor
   *   MOVEMENT_CHART   — "+ Add amended transport" from Movement Chart
   */
  @Column({ type: 'varchar', length: 32, default: 'INITIAL' })
  source: string;

  // ── Master link (per-booking rollup) ──────────────────────────────
  // Every settlement is a CHILD of a per-booking BookingSettlementMaster
  // row. Master maintains the parent rollup totals + status. Service
  // layer wires this via `ensureMaster()` on every child create;
  // `recomputeMaster()` runs after every mutation. Nullable only for
  // backfill safety — runtime saves always populate it.
  @ManyToOne(() => BookingSettlementMasterEntity, { nullable: true, onDelete: 'CASCADE' })
  @JoinColumn({ name: 'master_id' })
  master: BookingSettlementMasterEntity | null;

  @Column({ type: 'uuid', nullable: true })
  master_id: string | null;

  // ── Sales agent attribution (commission analytics) ────────────────
  // Snapshot of `booking.query.assigned_to` at settlement-create time.
  // Frozen on this row — if the query is later reassigned, this
  // attribution stays put so commission calculations don't shift.
  // Powers the by-sales-agent analytical view.
  @ManyToOne(() => UserEntity, { nullable: true, onDelete: 'SET NULL' })
  @JoinColumn({ name: 'sales_agent_id' })
  sales_agent: UserEntity | null;

  @Column({ type: 'uuid', nullable: true })
  sales_agent_id: string | null;

  // ── Phase F: party generalisation ──────────────────────────────────
  // SUPPLIER-side rows tie back to a specific booking line so the
  // ops team can dispute / annotate the right transport leg. CUSTOMER
  // rows leave source_item_* null.
  @Column({ type: 'varchar', length: 20, default: SettlementParty.CUSTOMER })
  party_type: SettlementParty;

  @ManyToOne(() => SupplierEntity, { nullable: true, onDelete: 'SET NULL' })
  @JoinColumn({ name: 'supplier_id' })
  supplier: SupplierEntity | null;

  @Column({ type: 'uuid', nullable: true })
  supplier_id: string | null;

  /**
   * Loose table-name string rather than a hard FK so a future
   * `booking_activity_item` source can join in without a migration.
   * Read together with `source_item_id`.
   */
  @Column({ type: 'varchar', length: 50, nullable: true })
  source_item_type: string | null;

  @Column({ type: 'uuid', nullable: true })
  source_item_id: string | null;

  // Payment ledger — append-only. settlement_payments rows are
  // created one per Settle action and are never edited or deleted.
  @OneToMany(() => SettlementPaymentEntity, (p) => p.settlement)
  payments: SettlementPaymentEntity[];
}
