import { Entity, Column, Index, Check, ManyToOne, OneToMany, JoinColumn } from 'typeorm';
import { BaseEntity } from '../database/base.entity';
import { QueryEntity } from './query.entity';
import { QueryQuoteEntity, PricingStrategy, MarkupType } from './query-quote.entity';
import { CurrencyEntity } from './currency.entity';
import { BookingHotelItemEntity } from './booking-hotel-item.entity';
import { BookingTransportItemEntity } from './booking-transport-item.entity';
import { BookingActivityItemEntity } from './booking-activity-item.entity';
import { BookingSpecialItemEntity } from './booking-special-item.entity';
import { BookingFlightDetailEntity } from './booking-flight-detail.entity';

export enum BookingStatus {
  UPCOMING = 'upcoming',
  ONGOING = 'ongoing',
  COMPLETED = 'completed',
  CANCELLED = 'cancelled',
  /** Manual pause on a booking — set by ops (`booking:edit`) with a
   *  required reason. Movement Dashboard still surfaces the row so
   *  dispatch can see it, but supplier / driver / vehicle / cost cells
   *  render read-only. The `pre_hold_status` column captures where the
   *  booking was before the hold so Resume knows the target state.
   *  Exit transitions live in [booking.service.ts:changeStatus]. */
  ON_HOLD = 'on_hold',
}

/**
 * Booking-level payment rollup. Recomputed by
 * `PaymentSettlementService.recomputeBookingPaymentStatus` after every
 * settlement / payment / itinerary delta. Cached on the row so the
 * bookings list page can show the pill in a column without a
 * sub-query per row.
 *
 *   UNPAID      — at least one COLLECTION exists and no payment has
 *                 landed against any of them yet.
 *   PARTIAL     — payments received against COLLECTIONs but the
 *                 running total is below the expected total.
 *   PAID        — every COLLECTION is SETTLED and there are no
 *                 outstanding REFUNDs.
 *   REFUND_DUE  — at least one REFUND row is not yet SETTLED. Wins
 *                 over PAID/PARTIAL so finance sees money-out flags
 *                 immediately.
 */
export enum BookingPaymentStatus {
  UNPAID = 'UNPAID',
  PARTIAL = 'PARTIAL',
  PAID = 'PAID',
  REFUND_DUE = 'REFUND_DUE',
}

/**
 * Booking entity — tenant-scoped.
 *
 * Single source of truth for operational + selling pricing of a confirmed trip.
 * Basic details (guest info, dates, pax, destination, currency, sales_team, tags)
 * live on the linked QueryEntity and are read via the `query` relation.
 * Edits to basic details are routed to QueryService.update under the hood.
 */
@Entity('bookings')
@Index(['tenant_id', 'booking_number'], { unique: true, where: '"is_deleted" = false' })
// (tenant_id, status) — settlements + MD chart + bookings list all
// filter by status. Partial index keeps only live rows in the index.
@Index('ix_bookings_tenant_status', ['tenant_id', 'status'], { where: '"is_deleted" = false' })
// CHECK constraints — reject typos at write time on the two enum
// columns. Values kept in sync with BookingStatus + BookingPaymentStatus.
@Check(
  'chk_bookings_status',
  `"status" IN ('upcoming','ongoing','completed','cancelled','on_hold')`,
)
@Check(
  'chk_bookings_payment_status',
  `"payment_status" IN ('UNPAID','PARTIAL','PAID','REFUND_DUE')`,
)
export class BookingEntity extends BaseEntity {
  @Column({ type: 'varchar', length: 20 })
  booking_number: string;

  // --- Single source of truth for basic details ---
  @ManyToOne(() => QueryEntity)
  @JoinColumn({ name: 'query_id' })
  query: QueryEntity;

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

  // --- Selected quote snapshot ---
  @ManyToOne(() => QueryQuoteEntity)
  @JoinColumn({ name: 'quote_id' })
  quote: QueryQuoteEntity;

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

  // --- Totals (operational cost in query.currency) ---
  @Column({ type: 'decimal', precision: 12, scale: 2, default: 0 })
  hotel_total: number;

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

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

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

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

  // --- Selling Price ---
  @Column({ type: 'uuid', nullable: true })
  selling_currency_id: string | null;

  @ManyToOne(() => CurrencyEntity, { nullable: true })
  @JoinColumn({ name: 'selling_currency_id' })
  selling_currency: CurrencyEntity;

  @Column({ type: 'decimal', precision: 12, scale: 6, default: 1 })
  exchange_rate: number;

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

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

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

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

  /** FINAL customer price — cost + markup + markup_tax_amount. This is
   *  the figure settlements, invoices and accounting bill. */
  @Column({ type: 'decimal', precision: 12, scale: 2, default: 0 })
  selling_total: number;

  /** Tax on the markup (service charge), selling currency. Derived
   *  server-side from selling_total via splitFinalPrice. */
  @Column({ type: 'decimal', precision: 12, scale: 2, default: 0 })
  markup_tax_amount: number;

  // --- Markup config (booking-owned; copied from the quote on create,
  //     editable on the booking thereafter — migration 1787900000000) ---
  @Column({ type: 'varchar', length: 30, default: PricingStrategy.OVERALL })
  pricing_strategy: PricingStrategy;

  @Column({ type: 'varchar', length: 10, default: MarkupType.AMOUNT })
  markup_type: MarkupType;

  @Column({ type: 'int', default: 1 })
  rounding: number;

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

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

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

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

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

  @Column({ type: 'int', default: 0 })
  transport_excluded_pax: number;

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

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

  // --- Booking stage (independent of query.stage) ---
  @Column({ type: 'varchar', length: 20, default: BookingStatus.UPCOMING })
  status: BookingStatus;

  /** Free-text reason captured when ops flips status → on_hold. Nulled
   *  on any transition out of hold. Analogous to cancellation_reason
   *  on the query — a minimum length is enforced at the service layer
   *  so the approver has real context. */
  @Column({ type: 'varchar', length: 500, nullable: true })
  hold_reason: string | null;

  /** Snapshot of the status the booking was in BEFORE the on_hold
   *  transition — so Resume can send it back to the right place
   *  (upcoming ↔ on_hold ↔ upcoming; ongoing ↔ on_hold ↔ ongoing).
   *  Cleared on every transition out of hold. Nullable + varchar so
   *  the schema doesn't require an enum column. */
  @Column({ type: 'varchar', length: 20, nullable: true })
  pre_hold_status: string | null;

  // --- Payment rollup (cached, recomputed by PaymentSettlementService) ---
  // See BookingPaymentStatus for the rule set.
  @Column({ type: 'varchar', length: 20, default: BookingPaymentStatus.UNPAID })
  payment_status: BookingPaymentStatus;

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

  /** Generated filename under privateUploads/doc/. Never a public URL. */
  @Column({ type: 'varchar', length: 255, nullable: true })
  pan_card_filename: string | null;

  /**
   * Flag: true when the booking was created via the Quick Booking flow
   * (walk-in / ad-hoc). Renders a ⚡ icon in the list + badge on detail.
   * The underlying query + quote rows are still real — just auto-created.
   */
  @Column({ type: 'boolean', default: false })
  is_quick_booking: boolean;

  /**
   * "Mark as Guarantee" — ops vouches for the customer, so the Movement
   * Dashboard unlocks every route without checking day-wise payment
   * coverage. False ⇒ routes unlock only once collected payment covers
   * the cumulative day-wise itinerary amount up to that day.
   */
  @Column({ type: 'boolean', default: false })
  is_guaranteed: boolean;

  /** Optional note captured when marking as guarantee; cleared on removal. */
  @Column({ type: 'text', nullable: true })
  guarantee_description: string | null;

  // ── Tax config (added 2026-05-08 — see migration 1777800000000) ─────
  // Mirrors the quote's tax_slab_id + tax_inclusive choice. Copied
  // from the selected quote on booking-create (createFromQuery), then
  // editable on the booking thereafter. Independent of per-line
  // tax_breakdown snapshots — those drive invoice math; these drive
  // the markup-view presentation.
  @Column({ type: 'uuid', nullable: true })
  tax_slab_id: string | null;

  // Default flipped to `true` 2026-05-13 per client feedback (same
  // change as on QueryQuoteEntity). Existing booking rows keep their
  // historic flag — see migration `1778300000000-DefaultTaxInclusiveTrue`.
  @Column({ type: 'boolean', default: true })
  tax_inclusive: boolean;

  // --- Child Items ---
  @OneToMany(() => BookingHotelItemEntity, (i) => i.booking)
  hotel_items: BookingHotelItemEntity[];

  @OneToMany(() => BookingTransportItemEntity, (i) => i.booking)
  transport_items: BookingTransportItemEntity[];

  @OneToMany(() => BookingActivityItemEntity, (i) => i.booking)
  activity_items: BookingActivityItemEntity[];

  @OneToMany(() => BookingSpecialItemEntity, (i) => i.booking)
  special_items: BookingSpecialItemEntity[];

  /**
   * Flight rows attached to this booking. A booking typically holds
   * 0–2 flights (arrival + departure) but the relation is unbounded
   * for multi-leg / connecting itineraries.
   */
  @OneToMany(() => BookingFlightDetailEntity, (f) => f.booking)
  flight_details: BookingFlightDetailEntity[];
}
