import { Entity, Column, Index, ManyToOne, JoinColumn, OneToMany } from 'typeorm';
import { BaseEntity } from '../database/base.entity';
import { BankAccountEntity } from './bank-account.entity';
import { CurrencyEntity } from './currency.entity';
import { SettlementPaymentEntity, SettlementPaymentMode } from './settlement-payment.entity';
import { SettlementPaymentType } from '../common/constants/settlement-payment-type';
import { TripSourceEntity } from './trip-source.entity';

/**
 * Phase D §12d — who sent the money on this collection. CUSTOMER is
 * the historical default (a guest paying for their own booking);
 * SOURCE is the trip-source / agent wiring on behalf of customers.
 */
export enum CollectionPartyType {
  CUSTOMER = 'CUSTOMER',
  SOURCE = 'SOURCE',
}

/**
 * Excess money received from the customer on a collection event.
 * `CREDIT` keeps it on file against future bookings; `ADVANCE` flags
 * it as an explicit prepayment. Phase D consumes this to draw down
 * from a customer_credit_ledger on the next collection.
 */
export enum CollectionExcessTreatment {
  CREDIT = 'CREDIT',
  ADVANCE = 'ADVANCE',
}

/**
 * Payment Collection — one customer-side collection event that
 * allocates a single received amount across N settlements (across
 * N bookings + service types). Parent of `settlement_payments` rows
 * via the nullable `collection_id` link.
 *
 * Receipt model: one collection = one receipt_no. All child
 * settlement_payments inherit the same receipt_no so a printed
 * receipt covers the whole allocation in one document.
 *
 * Append-only with admin-only void (matches settlement_payments).
 * Voiding a collection cascades to voiding every child payment
 * (handled in the service, not the FK).
 */
@Entity('payment_collections')
@Index(['tenant_id', 'customer_mobile', 'payment_date'])
@Index(['tenant_id', 'payment_date'])
@Index(['tenant_id', 'receipt_year', 'receipt_no'], { unique: true })
export class PaymentCollectionEntity extends BaseEntity {
  @Column({ type: 'varchar', length: 20 })
  receipt_no: string;

  @Column({ type: 'int' })
  receipt_year: number;

  /**
   * Who sent the money on this collection. CUSTOMER → `customer_mobile`
   * carries the key; SOURCE → `source_id` carries the key. A DB CHECK
   * keeps exactly one set per row — see the D2.5 migration.
   */
  @Column({ type: 'varchar', length: 16, default: CollectionPartyType.CUSTOMER })
  party_type: CollectionPartyType;

  /** Customer mobile — required when party_type='CUSTOMER', NULL when
   *  party_type='SOURCE'. Stakeholder-locked key; no Customer Master in
   *  Phase 1. */
  @Column({ type: 'varchar', length: 20, nullable: true })
  customer_mobile: string | null;

  @ManyToOne(() => TripSourceEntity, { nullable: true })
  @JoinColumn({ name: 'source_id' })
  source: TripSourceEntity | null;

  /** Source id — required when party_type='SOURCE', NULL when
   *  party_type='CUSTOMER'. References the trip_source (agent /
   *  channel) that wired the money. */
  @Column({ type: 'uuid', nullable: true })
  source_id: string | null;

  /** Snapshot of the guest name (CUSTOMER mode) or agent name (SOURCE
   *  mode) at collect-time so historical receipts stay readable when
   *  the underlying record changes downstream. */
  @Column({ type: 'varchar', length: 200, nullable: true })
  customer_name: string | null;

  @Column({ type: 'date' })
  payment_date: string;

  /** Operator's stated intent — allocate against invoices, or park the
   *  whole amount as credit on the party's ledger. */
  @Column({ type: 'varchar', length: 16, default: SettlementPaymentType.BILL_TO_BILL })
  payment_type: SettlementPaymentType;

  /** Reuses the settlement_payments mode enum so reports across the
   *  two tables share the same vocabulary. */
  @Column({ type: 'varchar', length: 32 })
  mode: SettlementPaymentMode;

  @Column({ type: 'varchar', length: 100, nullable: true })
  ref_no: string | null;

  /**
   * Optional debit / credit bank accounts on every payment mode.
   * Either side may be null. When both are set the IDs must differ
   * (enforced in the service + DB CHECK).
   */
  @ManyToOne(() => BankAccountEntity, { nullable: true })
  @JoinColumn({ name: 'debit_bank_account_id' })
  debit_bank_account: BankAccountEntity | null;

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

  @ManyToOne(() => BankAccountEntity, { nullable: true })
  @JoinColumn({ name: 'credit_bank_account_id' })
  credit_bank_account: BankAccountEntity | null;

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

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

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

  @Column({ type: 'decimal', precision: 12, scale: 2 })
  received_amount: number;

  /**
   * Currency the client actually paid in. May differ from `currency_id`
   * (bill / invoice currency). Null on collections recorded before
   * receive-currency support.
   */
  @ManyToOne(() => CurrencyEntity, { nullable: true })
  @JoinColumn({ name: 'receive_currency_id' })
  receive_currency: CurrencyEntity | null;

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

  /**
   * Rate used to convert `received_amount_receive_currency` into
   * `received_amount` (bill currency), before adding `roe`.
   */
  @Column({ type: 'decimal', precision: 12, scale: 6, nullable: true })
  exchange_rate: number | null;

  /**
   * The rate as the user entered it, in their natural quote direction
   * ("1 Bill = X Receive", e.g. 1 AED = 26.37 INR) — kept verbatim for
   * faithful display on receipts/history. Reciprocal of `exchange_rate`;
   * null for same-currency / legacy rows. See migration 1787700000000.
   */
  @Column({ type: 'decimal', precision: 18, scale: 6, nullable: true })
  exchange_rate_quote: number | null;

  /** Amount received in `receive_currency_id`, before conversion. */
  @Column({ type: 'decimal', precision: 12, scale: 2, nullable: true })
  received_amount_receive_currency: number | null;

  /** Optional rate of exchange captured at collect-time (receipt/invoice). */
  @Column({ type: 'decimal', precision: 12, scale: 6, nullable: true })
  roe: number | null;

  /** Sum of allocations across child settlement_payments. Maintained
   *  by the service in the same transaction as the child writes. */
  @Column({ type: 'decimal', precision: 12, scale: 2, default: 0 })
  allocated_amount: number;

  /** received_amount - allocated_amount. Persisted (not derived) so a
   *  report can filter on "has excess" without recomputing across
   *  joins. */
  @Column({ type: 'decimal', precision: 12, scale: 2, default: 0 })
  excess_amount: number;

  /** §12d Gap #2 — credit drawn down from this party's
   *  customer_credit_ledger and applied alongside the cash received.
   *  Allocation cap = received_amount + credit_applied. */
  @Column({ type: 'decimal', precision: 12, scale: 2, default: 0 })
  credit_applied: number;

  @Column({ type: 'varchar', length: 16, nullable: true })
  excess_treatment: CollectionExcessTreatment | null;

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

  /** Office Collect Payment — who physically received the money. */
  @Column({ type: 'varchar', length: 20, nullable: true })
  collected_by_type: 'OWN' | 'SUPPLIER' | null;

  @Column({ type: 'varchar', length: 255, nullable: true })
  collector_name: string | null;

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

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

  // ── Void (admin-only, mirrors settlement_payments) ──────────────
  @Column({ type: 'boolean', default: false })
  is_void: boolean;

  @Column({ type: 'timestamptz', nullable: true })
  voided_at: Date | null;

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

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

  @OneToMany(() => SettlementPaymentEntity, (p) => p.collection)
  payments: SettlementPaymentEntity[];
}
