/**
 * Booking Entity
 *
 * Represents a freight booking
 *
 * Business Rules:
 * - Must have valid rate quote
 * - Shipper and consignee are required
 * - Status transitions must follow allowed paths
 * - Containers can be added/updated until confirmed
 * - Cannot modify confirmed bookings (except status)
 */

import { BookingNumber } from '../value-objects/booking-number.vo';
import { BookingStatus } from '../value-objects/booking-status.vo';

export interface Address {
  street: string;
  city: string;
  postalCode: string;
  country: string;
}

export interface Party {
  name: string;
  address: Address;
  contactName: string;
  contactEmail: string;
  contactPhone: string;
}

export interface BookingContainer {
  id: string;
  type: string;
  containerNumber?: string;
  vgm?: number; // Verified Gross Mass in kg
  temperature?: number; // For reefer containers
  sealNumber?: string;
}

export interface BookingProps {
  id: string;
  bookingNumber: BookingNumber;
  userId: string;
  organizationId: string;
  rateQuoteId: string;
  status: BookingStatus;
  shipper: Party;
  consignee: Party;
  cargoDescription: string;
  containers: BookingContainer[];
  specialInstructions?: string;
  commissionRate?: number;
  commissionAmountEur?: number;
  createdAt: Date;
  updatedAt: Date;
}

export class Booking {
  private readonly props: BookingProps;

  private constructor(props: BookingProps) {
    this.props = props;
  }

  /**
   * Factory method to create a new Booking
   */
  static create(
    props: Omit<BookingProps, 'bookingNumber' | 'status' | 'createdAt' | 'updatedAt'> & {
      id: string;
      bookingNumber?: BookingNumber;
      status?: BookingStatus;
    }
  ): Booking {
    const now = new Date();

    const bookingProps: BookingProps = {
      ...props,
      bookingNumber: props.bookingNumber || BookingNumber.generate(),
      status: props.status || BookingStatus.create('draft'),
      createdAt: now,
      updatedAt: now,
    };

    // Validate business rules
    Booking.validate(bookingProps);

    return new Booking(bookingProps);
  }

  /**
   * Validate business rules
   */
  private static validate(props: BookingProps): void {
    if (!props.userId) {
      throw new Error('User ID is required');
    }

    if (!props.organizationId) {
      throw new Error('Organization ID is required');
    }

    if (!props.rateQuoteId) {
      throw new Error('Rate quote ID is required');
    }

    if (!props.shipper || !props.shipper.name) {
      throw new Error('Shipper information is required');
    }

    if (!props.consignee || !props.consignee.name) {
      throw new Error('Consignee information is required');
    }

    if (!props.cargoDescription || props.cargoDescription.length < 10) {
      throw new Error('Cargo description must be at least 10 characters');
    }
  }

  // Getters
  get id(): string {
    return this.props.id;
  }

  get bookingNumber(): BookingNumber {
    return this.props.bookingNumber;
  }

  get userId(): string {
    return this.props.userId;
  }

  get organizationId(): string {
    return this.props.organizationId;
  }

  get rateQuoteId(): string {
    return this.props.rateQuoteId;
  }

  get status(): BookingStatus {
    return this.props.status;
  }

  get shipper(): Party {
    return { ...this.props.shipper };
  }

  get consignee(): Party {
    return { ...this.props.consignee };
  }

  get cargoDescription(): string {
    return this.props.cargoDescription;
  }

  get containers(): BookingContainer[] {
    return [...this.props.containers];
  }

  get specialInstructions(): string | undefined {
    return this.props.specialInstructions;
  }

  get commissionRate(): number | undefined {
    return this.props.commissionRate;
  }

  get commissionAmountEur(): number | undefined {
    return this.props.commissionAmountEur;
  }

  get createdAt(): Date {
    return this.props.createdAt;
  }

  get updatedAt(): Date {
    return this.props.updatedAt;
  }

  /**
   * Update booking status
   */
  updateStatus(newStatus: BookingStatus): Booking {
    if (!this.status.canTransitionTo(newStatus)) {
      throw new Error(`Cannot transition from ${this.status.value} to ${newStatus.value}`);
    }

    return new Booking({
      ...this.props,
      status: newStatus,
      updatedAt: new Date(),
    });
  }

  /**
   * Add container to booking
   */
  addContainer(container: BookingContainer): Booking {
    if (!this.status.canBeModified()) {
      throw new Error('Cannot modify containers after booking is confirmed');
    }

    return new Booking({
      ...this.props,
      containers: [...this.props.containers, container],
      updatedAt: new Date(),
    });
  }

  /**
   * Update container information
   */
  updateContainer(containerId: string, updates: Partial<BookingContainer>): Booking {
    if (!this.status.canBeModified()) {
      throw new Error('Cannot modify containers after booking is confirmed');
    }

    const containerIndex = this.props.containers.findIndex(c => c.id === containerId);
    if (containerIndex === -1) {
      throw new Error(`Container ${containerId} not found`);
    }

    const updatedContainers = [...this.props.containers];
    updatedContainers[containerIndex] = {
      ...updatedContainers[containerIndex],
      ...updates,
    };

    return new Booking({
      ...this.props,
      containers: updatedContainers,
      updatedAt: new Date(),
    });
  }

  /**
   * Remove container from booking
   */
  removeContainer(containerId: string): Booking {
    if (!this.status.canBeModified()) {
      throw new Error('Cannot modify containers after booking is confirmed');
    }

    return new Booking({
      ...this.props,
      containers: this.props.containers.filter(c => c.id !== containerId),
      updatedAt: new Date(),
    });
  }

  /**
   * Update cargo description
   */
  updateCargoDescription(description: string): Booking {
    if (!this.status.canBeModified()) {
      throw new Error('Cannot modify cargo description after booking is confirmed');
    }

    if (description.length < 10) {
      throw new Error('Cargo description must be at least 10 characters');
    }

    return new Booking({
      ...this.props,
      cargoDescription: description,
      updatedAt: new Date(),
    });
  }

  /**
   * Update special instructions
   */
  updateSpecialInstructions(instructions: string): Booking {
    return new Booking({
      ...this.props,
      specialInstructions: instructions,
      updatedAt: new Date(),
    });
  }

  /**
   * Apply commission to the booking
   */
  applyCommission(ratePercent: number, baseAmountEur: number): Booking {
    const commissionAmount = Math.round(baseAmountEur * ratePercent) / 100;
    return new Booking({
      ...this.props,
      commissionRate: ratePercent,
      commissionAmountEur: commissionAmount,
      updatedAt: new Date(),
    });
  }

  /**
   * Check if booking can be cancelled
   */
  canBeCancelled(): boolean {
    return !this.status.isFinal();
  }

  /**
   * Cancel booking
   */
  cancel(): Booking {
    if (!this.canBeCancelled()) {
      throw new Error('Cannot cancel booking in final state');
    }

    return this.updateStatus(BookingStatus.create('cancelled'));
  }

  /**
   * Equality check
   */
  equals(other: Booking): boolean {
    return this.id === other.id;
  }
}
