import { BaseEntity } from '@edr/api-common';
import { SchedulingStatus } from '@edr/types';
import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
// import { Customer } from '../../customers/entities/customer.entity';
import { Company } from '../../companies/entities/company.entity';
import { CompanyProfile } from '../../companies/entities/company-profile.entity';
import { CargoType } from '../../rule-engine/entities/cargo-type.entity';
import { ServiceType } from '../../rule-engine/entities/service-type.entity';
import { ShippingLine } from '../../rule-engine/entities/shipping-line.entity';
import { Yard } from '../../rule-engine/entities/yard.entity';
import { Train } from '../../trains/entities/train.entity';
import { FileRecord } from '../../files/entities/file.entity';
import { BookingApprovalStep } from './booking-approval-step.entity';
import { BookingCargoModifier } from './booking-cargo-modifier.entity';
import { BookingContainer } from './booking-container.entity';
import { BookingRateSnapshot } from './booking-rate-snapshot.entity';
import { BookingReviewNote } from './booking-review-note.entity';

export const BOOKING_STATUSES = [
  'DRAFT',
  'SUBMITTED',
  'PRICE_CHANGED_PENDING_CONFIRM',
  'CHANGES_REQUESTED',
  'PENDING_APPROVAL',
  'APPROVED_PENDING_SIGNATURE',
  'APPROVED',
  'READY_FOR_ASSIGNMENT',
  'WAGON_ASSIGNED',
  'INVOICED',
  'CONTRACT_READY',
  'SIGNED_CUSTOMER',
  'FULLY_EXECUTED',
  'SELECTED_FOR_BATCH',
  'EXPIRED',
  'PNR_GENERATED',
  'PAYMENT_VERIFICATION_IN_PROGRESS',
  'PAID',
  'IN_TRANSIT',
  'COMPLETED',
  'REJECTED',
  'CANCELLED',
  'PENDING_CONSOLIDATION',
  'CONSOLIDATED',
  'CONTRACT_ACTIVE',
  'CONTRACT_CLOSED',
  // Post counter-sign document-clearance gate (GL workflow).
  'AWAITING_DOCUMENTS',
  'DOCUMENTS_UNDER_REVIEW',
  'CLEARANCE_READY',
  // Road (truck) drawdown orders skip the train batch pool and wait here for
  // truck dispatch after Marketing accepts; billed by KM, not wagons.
  'ROAD_DISPATCH_PENDING',
  'OPERATION_REQUESTED',
  // Operations review gate: customer picks a schedule day and submits the
  // operation request; the operations team reviews capacity/docs/route before
  // the booking enters the batch holding pool.
  'OPERATION_REQUEST_PENDING',
  'OPERATION_CHANGES_REQUESTED',
  'OPERATION_PRICE_PENDING_CONFIRM',
] as const;

export type BookingStatus = (typeof BOOKING_STATUSES)[number];

export const BOOKING_TYPES = ['ONE_TIME', 'GENERAL_CONTRACT'] as const;
export type BookingTypeValue = (typeof BOOKING_TYPES)[number];

export const PAYMENT_STATUSES = [
  'PENDING',
  'PNR_GENERATED',
  'VERIFICATION_IN_PROGRESS',
  'PAID',
  'FAILED',
] as const;

export type PaymentStatus = (typeof PAYMENT_STATUSES)[number];

export const FREIGHT_TYPES = ['CONTAINER', 'BULK'] as const;
export type FreightType = (typeof FREIGHT_TYPES)[number];

export const SCHEDULING_STATUSES = [
  SchedulingStatus.NotScheduled,
  SchedulingStatus.Holding,
  SchedulingStatus.Eligible,
  SchedulingStatus.Scheduled,
  SchedulingStatus.Dispatched,
] as const;

export type BookingSchedulingStatus = (typeof SCHEDULING_STATUSES)[number];

/** Statuses where the customer may edit booking fields. */
export const CUSTOMER_EDITABLE_STATUSES: BookingStatus[] = [
  'DRAFT',
  'CHANGES_REQUESTED',
];

@Entity({ schema: 'freight', name: 'bookings' })
export class Booking extends BaseEntity {
  @Column({ name: 'reference', type: 'varchar', length: 64, unique: true })
  reference!: string;

  // Legacy — superseded by companyId (column kept in DB)
  // @Column({ name: 'customer_id', type: 'uuid' })
  // customerId!: string;
  // @ManyToOne(() => Customer)
  // @JoinColumn({ name: 'customer_id' })
  // customer?: Customer;

  @Column({ name: 'company_id', type: 'uuid', nullable: true })
  companyId?: string | null;

  @ManyToOne(() => Company, { nullable: true })
  @JoinColumn({ name: 'company_id' })
  company?: Company | null;

  /**
   * The operational profile (importer/exporter/forwarder) this booking belongs
   * to. Stamped at creation from the booking's trade direction (IMPORT→importer,
   * EXPORT→exporter) or the user's active profile for DOMESTIC/forwarder.
   * Customer portal lists and dashboard KPIs are scoped by this. Nullable for
   * legacy/government/staff-created bookings.
   */
  @Column({ name: 'company_profile_id', type: 'uuid', nullable: true })
  companyProfileId?: string | null;

  @ManyToOne(() => CompanyProfile, { nullable: true })
  @JoinColumn({ name: 'company_profile_id' })
  companyProfile?: CompanyProfile | null;

  @Column({ name: 'is_government', type: 'boolean', default: false })
  isGovernment!: boolean;

  @Column({ name: 'government_institution', type: 'varchar', length: 255, nullable: true })
  governmentInstitution?: string | null;

  /** @deprecated Fleet master data link — scheduling uses train_schedule_bookings instead. */
  @Column({ name: 'train_id', type: 'uuid', nullable: true })
  trainId?: string | null;

  /** @deprecated Use train_schedule_bookings for operational scheduling. */
  @ManyToOne(() => Train, { nullable: true })
  @JoinColumn({ name: 'train_id' })
  train?: Train | null;

  @Column({ name: 'status', type: 'varchar', length: 40, default: 'DRAFT' })
  status!: string;

  /**
   * ONE_TIME for a normal single-shipment booking; GENERAL_CONTRACT for an
   * umbrella contract that is signed/paid once and then drawn down by many
   * orders (each order spawns its own ONE_TIME child booking).
   */
  @Column({ name: 'booking_type', type: 'varchar', length: 20, default: 'ONE_TIME' })
  bookingType!: string;

  /**
   * Nullable: general contracts have no shipment date at creation — the date is
   * chosen per drawdown order. One-time bookings always set this (the pool day key).
   */
  @Column({ name: 'scheduled_date', type: 'timestamptz', nullable: true })
  scheduledDate?: Date | null;

  /**
   * General contracts only: when the ordering window closes, computed from the
   * global CONTRACT_PERIOD_MONTHS setting at activation. Null for one-time
   * bookings and for contracts that are not yet active.
   */
  @Column({ name: 'expires_at', type: 'timestamptz', nullable: true })
  expiresAt?: Date | null;

  @Column({ name: 'total_amount', type: 'numeric', precision: 14, scale: 2, default: 0 })
  totalAmount!: number;

  /**
   * Staff-adjusted total price. When set, it overrides the computed totalAmount
   * for the customer, who is shown an "Adjusted by EDR" badge.
   */
  @Column({ name: 'adjusted_total_amount', type: 'numeric', precision: 14, scale: 2, nullable: true })
  adjustedTotalAmount?: number | null;

  @Column({ name: 'adjusted_by_staff_id', type: 'uuid', nullable: true })
  adjustedByStaffId?: string | null;

  @Column({ name: 'adjusted_at', type: 'timestamptz', nullable: true })
  adjustedAt?: Date | null;

  @Column({ name: 'adjustment_reason', type: 'text', nullable: true })
  adjustmentReason?: string | null;

  /**
   * Contract validity window, set by the backoffice at the accept step. The
   * staff enter a number of days; the contract is valid from contractValidFrom
   * (the accept moment) through contractValidUntil (validFrom + N days). Outside
   * this window the contract is expired and the booking cannot proceed.
   */
  @Column({ name: 'contract_validity_days', type: 'int', nullable: true })
  contractValidityDays?: number | null;

  @Column({ name: 'contract_valid_from', type: 'timestamptz', nullable: true })
  contractValidFrom?: Date | null;

  @Column({ name: 'contract_valid_until', type: 'timestamptz', nullable: true })
  contractValidUntil?: Date | null;

  @Column({ name: 'payment_status', type: 'varchar', length: 20, default: 'PENDING' })
  paymentStatus!: string;

  @Column({ name: 'contract_type', type: 'varchar', length: 20 })
  contractType!: string;

  @Column({ name: 'previous_contract_id', type: 'uuid', nullable: true })
  previousContractId?: string | null;

  @ManyToOne(() => Booking, { nullable: true })
  @JoinColumn({ name: 'previous_contract_id' })
  previousContract?: Booking | null;

  @Column({ name: 'service_type_id', type: 'uuid' })
  serviceTypeId!: string;

  @ManyToOne(() => ServiceType)
  @JoinColumn({ name: 'service_type_id' })
  serviceType?: ServiceType;

  @Column({ name: 'first_mile_pickup_address', type: 'text', nullable: true })
  firstMilePickupAddress?: string | null;

  @Column({ name: 'first_mile_pickup_lat', type: 'numeric', precision: 10, scale: 7, nullable: true })
  firstMilePickupLat?: number | null;

  @Column({ name: 'first_mile_pickup_lng', type: 'numeric', precision: 10, scale: 7, nullable: true })
  firstMilePickupLng?: number | null;

  @Column({ name: 'last_mile_delivery_address', type: 'text', nullable: true })
  lastMileDeliveryAddress?: string | null;

  @Column({ name: 'last_mile_delivery_lat', type: 'numeric', precision: 10, scale: 7, nullable: true })
  lastMileDeliveryLat?: number | null;

  @Column({ name: 'last_mile_delivery_lng', type: 'numeric', precision: 10, scale: 7, nullable: true })
  lastMileDeliveryLng?: number | null;

  @Column({ name: 'customs_clearing_enabled', type: 'boolean', default: false })
  customsClearingEnabled!: boolean;

  @Column({ name: 'customs_clearing_agent', type: 'varchar', length: 200, nullable: true })
  customsClearingAgent?: string | null;

  @Column({ name: 'equipment_return', type: 'varchar', length: 20 })
  equipmentReturn!: string;

  @Column({ name: 'origin_yard_id', type: 'uuid' })
  originYardId!: string;

  @ManyToOne(() => Yard)
  @JoinColumn({ name: 'origin_yard_id' })
  originYard?: Yard;

  @Column({ name: 'destination_yard_id', type: 'uuid' })
  destinationYardId!: string;

  @ManyToOne(() => Yard)
  @JoinColumn({ name: 'destination_yard_id' })
  destinationYard?: Yard;

  @Column({ name: 'trade_direction', type: 'varchar', length: 10 })
  tradeDirection!: string;

  @Column({ name: 'freight_type', type: 'varchar', length: 20 })
  freightType!: string;

  @Column({ name: 'cargo_type_id', type: 'uuid', nullable: true })
  cargoTypeId?: string | null;

  @ManyToOne(() => CargoType)
  @JoinColumn({ name: 'cargo_type_id' })
  cargoType?: CargoType;

  @Column({ name: 'cargo_free_text', type: 'varchar', length: 200, nullable: true })
  cargoFreeText?: string | null;

  @Column({ name: 'shipping_line_id', type: 'uuid', nullable: true })
  shippingLineId?: string | null;

  @ManyToOne(() => ShippingLine, { nullable: true })
  @JoinColumn({ name: 'shipping_line_id' })
  shippingLine?: ShippingLine | null;

  @Column({ name: 'cargo_total_weight_vgm', type: 'numeric', precision: 12, scale: 3 })
  cargoTotalWeightVgm!: number;

  @Column({ name: 'is_hazardous', type: 'boolean', default: false })
  isHazardous!: boolean;

  /**
   * Refrigerated cargo flag. For one-time bookings reefer is derived from the
   * container type; for general-contract drawdown orders the customer enters a
   * reefer quantity per order, which sets this flag on the spawned child so the
   * REEFER_SURCHARGE rate applies even when the container type is not a reefer.
   */
  @Column({ name: 'is_reefer', type: 'boolean', default: false })
  isReefer!: boolean;

  @Column({ name: 'payment_currency', type: 'varchar', length: 5 })
  paymentCurrency!: string;

  @Column({ name: 'pnr_code', type: 'varchar', length: 50, nullable: true })
  pnrCode?: string | null;

  @Column({ name: 'start_date', type: 'date', nullable: true })
  startDate?: Date | null;

  @Column({ name: 'end_date', type: 'date', nullable: true })
  endDate?: Date | null;

  @Column({ name: 'financial_terms', type: 'text', nullable: true })
  financialTerms?: string | null;

  @Column({ name: 'version_number', type: 'int', default: 1 })
  versionNumber!: number;

  @Column({ name: 'approved_by_staff_id', type: 'uuid', nullable: true })
  approvedByStaffId?: string | null;

  @Column({ name: 'approved_by_staff_at', type: 'timestamptz', nullable: true })
  approvedByStaffAt?: Date | null;

  @Column({ name: 'signed_by_director_id', type: 'uuid', nullable: true })
  signedByDirectorId?: string | null;

  @Column({ name: 'signed_by_director_at', type: 'timestamptz', nullable: true })
  signedByDirectorAt?: Date | null;

  @Column({ name: 'signed_by_ceo_id', type: 'uuid', nullable: true })
  signedByCeoId?: string | null;

  @Column({ name: 'signed_by_ceo_at', type: 'timestamptz', nullable: true })
  signedByCeoAt?: Date | null;

  @Column({ name: 'customer_signed_at', type: 'timestamptz', nullable: true })
  customerSignedAt?: Date | null;

  @Column({ name: 'fully_executed_at', type: 'timestamptz', nullable: true })
  fullyExecutedAt?: Date | null;

  @Column({ name: 'marketing_approved_by_id', type: 'uuid', nullable: true })
  marketingApprovedById?: string | null;

  @Column({ name: 'marketing_approved_at', type: 'timestamptz', nullable: true })
  marketingApprovedAt?: Date | null;

  @Column({ name: 'contract_summary', type: 'text', nullable: true })
  contractSummary?: string | null;

  @Column({ name: 'contract_template_key', type: 'varchar', length: 80, nullable: true })
  contractTemplateKey?: string | null;

  @Column({ name: 'contract_generated_at', type: 'timestamptz', nullable: true })
  contractGeneratedAt?: Date | null;

  @Column({ name: 'pricing_breakdown', type: 'jsonb', nullable: true })
  pricingBreakdown?: Record<string, unknown> | null;

  @Column({ name: 'locked_at', type: 'timestamptz', nullable: true })
  lockedAt?: Date | null;

  @Column({ name: 'priority_score', type: 'int', default: 0 })
  priorityScore!: number;

  @Column({ name: 'consolidation_partner_id', type: 'uuid', nullable: true })
  consolidationPartnerId?: string | null;

  @ManyToOne(() => Booking, { nullable: true })
  @JoinColumn({ name: 'consolidation_partner_id' })
  consolidationPartner?: Booking | null;

  @Column({ name: 'wagons_required', type: 'numeric', precision: 6, scale: 2, nullable: true })
  wagonsRequired?: number | null;

  @Column({ name: 'scheduling_status', type: 'varchar', length: 30, default: 'NOT_SCHEDULED' })
  schedulingStatus!: string;

  @Column({ name: 'hold_started_at', type: 'timestamptz', nullable: true })
  holdStartedAt?: Date | null;

  @Column({ name: 'hold_expires_at', type: 'timestamptz', nullable: true })
  holdExpiresAt?: Date | null;
  

  @Column({ name: 'scheduled_at', type: 'timestamptz', nullable: true })
  scheduledAt?: Date | null;

  /**
   * The train this booking is assigned to. FK to train_schedules.
   *
   * Day-level pooling: customers no longer pick a train — they pick a DAY, and
   * this stays null at creation. The batch engine sets it when it assigns the
   * booking to a specific train within its (route, day) pool; staff may also
   * pin it manually. The day-level pool is keyed on
   * (origin_yard_id, destination_yard_id, day of scheduled_date), not this column.
   */
  @Column({ name: 'train_schedule_id', type: 'uuid', nullable: true })
  trainScheduleId?: string | null;

  /** End of the pay window once the booking is SELECTED_FOR_BATCH. */
  @Column({ name: 'payment_deadline', type: 'timestamptz', nullable: true })
  paymentDeadline?: Date | null;

  /** When the batch engine picked this booking and opened the pay window. */
  @Column({ name: 'selected_for_batch_at', type: 'timestamptz', nullable: true })
  selectedForBatchAt?: Date | null;

  @OneToMany(() => BookingContainer, (bc) => bc.booking)
  bookingContainers?: BookingContainer[];

  @OneToMany(() => BookingCargoModifier, (m) => m.booking)
  cargoModifiers?: BookingCargoModifier[];

  @OneToMany(() => BookingApprovalStep, (s) => s.booking)
  approvalSteps?: BookingApprovalStep[];

  @OneToMany(() => BookingRateSnapshot, (s) => s.booking)
  rateSnapshots?: BookingRateSnapshot[];

  @OneToMany(() => BookingReviewNote, (n) => n.booking)
  reviewNotes?: BookingReviewNote[];

  @OneToMany(() => FileRecord, (file) => file.resourceId, {
    createForeignKeyConstraints: false,
  })
  files?: FileRecord[];
}
