import { Entity, Column, ManyToOne, JoinColumn, Index } from 'typeorm';
import { BaseEntity } from '../database/base.entity';
import { BookingEntity } from './booking.entity';
import { QueryQuoteEntity } from './query-quote.entity';
import { UserEntity } from './user.entity';

export enum ChangeRequestKind {
  /** On-trip itinerary edit — sales proposes a new booking state while
   *  status is ONGOING. Payload holds a full UpdateBookingDto-shaped
   *  object. Manager approval applies it atomically. */
  ITINERARY = 'itinerary',
  /** Save attempted with one or more zero-priced services. Payload
   *  holds the proposed booking state + a list of the flagged items
   *  so the manager can see exactly what's being given for free. */
  ZERO_VALUE = 'zero_value',
  /** Save attempted with a transport line priced below the supplier's
   *  configured cost. Triggered by the selling-below-cost detector;
   *  payload mirrors ITINERARY (full booking snapshot).
   *
   *  ⚠ DEPLOYMENT NOTE: Postgres enum types require `ALTER TYPE …
   *  ADD VALUE 'selling_price'` to add this value to an existing
   *  database. With `synchronize: true` in dev that's automatic;
   *  for staging/prod a manual migration is required before this
   *  feature branch can deploy. See database.module.ts for the
   *  per-env synchronize toggle. */
  SELLING_PRICE = 'selling_price',
  /** Save attempted with a transport line priced below the configured
   *  floor (lowest acceptable selling price for the active season).
   *  Payload mirrors ITINERARY (full booking snapshot). Migration
   *  1777300000000-AddBelowFloorPriceCRKind adds the enum value. */
  BELOW_FLOOR_PRICE = 'below_floor_price',
  /** Booking start_date is TODAY in the destination's local timezone.
   *  Emitted automatically by both the Sales-side manual CR flow and
   *  the approver-side auto-CR path in BookingService.update. Users
   *  with booking_approval_bypass:view (Admin by default) skip this. */
  SAME_DAY_BOOKING = 'same_day_booking',
  /** Booking start_date is TOMORROW in the destination's local
   *  timezone. Same emission + bypass path as SAME_DAY_BOOKING. */
  NEXT_DAY_BOOKING = 'next_day_booking',
  /** Line-item edit on a booking whose status is already `confirmed`
   *  / `ongoing` / `completed`. Fires whenever the update DTO carries
   *  any of transport_items, activity_items, hotel_items, or
   *  special_items on a post-confirmation booking. */
  POST_CONFIRM_EDIT = 'post_confirm_edit',
}

export enum ChangeRequestStatus {
  PENDING = 'pending',
  APPROVED = 'approved',
  REJECTED = 'rejected',
  ON_HOLD = 'on_hold',
  /** Requester cancelled their own pending request before a manager
   *  decided. Terminal state — no further mutations allowed. */
  WITHDRAWN = 'withdrawn',
}

/**
 * BookingChangeRequest — approval workflow log.
 *
 * Sales (or anyone without booking_approval:edit) can't PATCH a
 * booking when it's ONGOING, and can't save any itinerary that
 * contains a zero-priced service. Instead they submit a change
 * request here; a manager (booking_approval:edit) approves,
 * rejects, or holds it. Approval applies the snapshot payload
 * atomically and this row becomes the audit trail.
 */
@Entity('booking_change_requests')
@Index(['tenant_id', 'status'])
@Index(['booking_id'])
@Index(['tenant_id', 'quote_id'])
export class BookingChangeRequestEntity extends BaseEntity {
  /** Either booking_id OR quote_id is set, never both — enforced by a
   *  CHECK constraint on the table. Booking-scoped CRs flow through
   *  the on-trip / zero-value approval queue; quote-scoped CRs are
   *  raised automatically when sales saves a quote with a zero-priced
   *  service or below-floor transport line. */
  @ManyToOne(() => BookingEntity, { onDelete: 'CASCADE', nullable: true })
  @JoinColumn({ name: 'booking_id' })
  booking: BookingEntity | null;

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

  @ManyToOne(() => QueryQuoteEntity, { onDelete: 'CASCADE', nullable: true })
  @JoinColumn({ name: 'quote_id' })
  quote: QueryQuoteEntity | null;

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

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

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

  /** User who submitted the request. Set from CLS user_id at create. */
  @ManyToOne(() => UserEntity, { nullable: true })
  @JoinColumn({ name: 'requester_id' })
  requester: UserEntity | null;

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

  /** Justification from the requester for manually-submitted CRs
   *  (validated at the DTO layer, min 40 chars). Auto-generated quote
   *  CRs leave this NULL — the system explanation lives in the kind
   *  + payload combo, not a free-text remark. */
  @Column({ type: 'text', nullable: true })
  requester_remarks: string | null;

  /** User who approved / rejected / held. Null while pending. */
  @ManyToOne(() => UserEntity, { nullable: true })
  @JoinColumn({ name: 'decided_by' })
  decided_by_user: UserEntity | null;

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

  /** Manager's reason. Null while pending. 40+ chars on any decision. */
  @Column({ type: 'text', nullable: true })
  decision_remarks: string | null;

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

  /**
   * Proposed booking state (UpdateBookingDto-shaped) for ITINERARY, or
   * { update: UpdateBookingDto, zero_items: [...refs] } for ZERO_VALUE.
   * Stored as jsonb so we can present a diff later without rehydrating
   * the whole editor.
   */
  @Column({ type: 'jsonb' })
  payload: Record<string, unknown>;

  /**
   * Booking state at the moment the request was submitted, captured
   * with the same shape as the live `payload`. The change-request
   * dialog diffs `payload` against `before_snapshot` so the "what
   * actually changed" panel keeps working AFTER the request is
   * approved (point at which the live booking now equals the
   * payload, making a `payload vs current` diff useless).
   *
   * Nullable for backward-compat: requests created before this
   * column existed have no snapshot — the FE falls back to the
   * live booking, which is correct for those legacy pending rows
   * and acceptable for already-decided rows (they show no diff
   * but the requester remarks + decision remarks still narrate
   * the change).
   */
  @Column({ type: 'jsonb', nullable: true })
  before_snapshot: Record<string, unknown> | null;
}
