/**
 * Container Entity
 *
 * Represents a shipping container in a booking
 *
 * Business Rules:
 * - Container number must follow ISO 6346 format (when provided)
 * - VGM (Verified Gross Mass) is required for export shipments
 * - Temperature must be within valid range for reefer containers
 */

export enum ContainerCategory {
  DRY = 'DRY',
  REEFER = 'REEFER',
  OPEN_TOP = 'OPEN_TOP',
  FLAT_RACK = 'FLAT_RACK',
  TANK = 'TANK',
}

export enum ContainerSize {
  TWENTY = '20',
  FORTY = '40',
  FORTY_FIVE = '45',
}

export enum ContainerHeight {
  STANDARD = 'STANDARD',
  HIGH_CUBE = 'HIGH_CUBE',
}

export interface ContainerProps {
  id: string;
  bookingId?: string; // Optional until container is assigned to a booking
  type: string; // e.g., '20DRY', '40HC', '40REEFER'
  category: ContainerCategory;
  size: ContainerSize;
  height: ContainerHeight;
  containerNumber?: string; // ISO 6346 format (assigned by carrier)
  sealNumber?: string;
  vgm?: number; // Verified Gross Mass in kg
  tareWeight?: number; // Empty container weight in kg
  maxGrossWeight?: number; // Maximum gross weight in kg
  temperature?: number; // For reefer containers (°C)
  humidity?: number; // For reefer containers (%)
  ventilation?: string; // For reefer containers
  isHazmat: boolean;
  imoClass?: string; // IMO hazmat class (if hazmat)
  cargoDescription?: string;
  createdAt: Date;
  updatedAt: Date;
}

export class Container {
  private readonly props: ContainerProps;

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

  /**
   * Factory method to create a new Container
   */
  static create(props: Omit<ContainerProps, 'createdAt' | 'updatedAt'>): Container {
    const now = new Date();

    // Validate container number format if provided
    if (props.containerNumber && !Container.isValidContainerNumber(props.containerNumber)) {
      throw new Error('Invalid container number format. Must follow ISO 6346 standard.');
    }

    // Validate VGM if provided
    if (props.vgm !== undefined && props.vgm <= 0) {
      throw new Error('VGM must be positive.');
    }

    // Validate temperature for reefer containers
    if (props.category === ContainerCategory.REEFER) {
      if (props.temperature === undefined) {
        throw new Error('Temperature is required for reefer containers.');
      }
      if (props.temperature < -40 || props.temperature > 40) {
        throw new Error('Temperature must be between -40°C and +40°C.');
      }
    }

    // Validate hazmat
    if (props.isHazmat && !props.imoClass) {
      throw new Error('IMO class is required for hazmat containers.');
    }

    return new Container({
      ...props,
      createdAt: now,
      updatedAt: now,
    });
  }

  /**
   * Factory method to reconstitute from persistence
   */
  static fromPersistence(props: ContainerProps): Container {
    return new Container(props);
  }

  /**
   * Validate ISO 6346 container number format
   * Format: 4 letters (owner code) + 6 digits + 1 check digit
   * Example: MSCU1234567
   */
  private static isValidContainerNumber(containerNumber: string): boolean {
    const pattern = /^[A-Z]{4}\d{7}$/;
    if (!pattern.test(containerNumber)) {
      return false;
    }

    // Validate check digit (ISO 6346 algorithm)
    const ownerCode = containerNumber.substring(0, 4);
    const serialNumber = containerNumber.substring(4, 10);
    const checkDigit = parseInt(containerNumber.substring(10, 11), 10);

    // Convert letters to numbers (A=10, B=12, C=13, ..., Z=38)
    const letterValues: { [key: string]: number } = {};
    'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').forEach((letter, index) => {
      letterValues[letter] = 10 + index + Math.floor(index / 2);
    });

    // Calculate sum
    let sum = 0;
    for (let i = 0; i < ownerCode.length; i++) {
      sum += letterValues[ownerCode[i]] * Math.pow(2, i);
    }
    for (let i = 0; i < serialNumber.length; i++) {
      sum += parseInt(serialNumber[i], 10) * Math.pow(2, i + 4);
    }

    // Check digit = sum % 11 (if 10, use 0)
    const calculatedCheckDigit = sum % 11 === 10 ? 0 : sum % 11;

    return calculatedCheckDigit === checkDigit;
  }

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

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

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

  get category(): ContainerCategory {
    return this.props.category;
  }

  get size(): ContainerSize {
    return this.props.size;
  }

  get height(): ContainerHeight {
    return this.props.height;
  }

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

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

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

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

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

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

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

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

  get isHazmat(): boolean {
    return this.props.isHazmat;
  }

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

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

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

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

  // Business methods
  isReefer(): boolean {
    return this.props.category === ContainerCategory.REEFER;
  }

  isDry(): boolean {
    return this.props.category === ContainerCategory.DRY;
  }

  isHighCube(): boolean {
    return this.props.height === ContainerHeight.HIGH_CUBE;
  }

  getTEU(): number {
    // Twenty-foot Equivalent Unit
    if (this.props.size === ContainerSize.TWENTY) {
      return 1;
    } else if (
      this.props.size === ContainerSize.FORTY ||
      this.props.size === ContainerSize.FORTY_FIVE
    ) {
      return 2;
    }
    return 0;
  }

  getPayload(): number | undefined {
    if (this.props.vgm !== undefined && this.props.tareWeight !== undefined) {
      return this.props.vgm - this.props.tareWeight;
    }
    return undefined;
  }

  assignContainerNumber(containerNumber: string): void {
    if (!Container.isValidContainerNumber(containerNumber)) {
      throw new Error('Invalid container number format.');
    }
    this.props.containerNumber = containerNumber;
    this.props.updatedAt = new Date();
  }

  assignSealNumber(sealNumber: string): void {
    this.props.sealNumber = sealNumber;
    this.props.updatedAt = new Date();
  }

  setVGM(vgm: number): void {
    if (vgm <= 0) {
      throw new Error('VGM must be positive.');
    }
    this.props.vgm = vgm;
    this.props.updatedAt = new Date();
  }

  setTemperature(temperature: number): void {
    if (!this.isReefer()) {
      throw new Error('Cannot set temperature for non-reefer container.');
    }
    if (temperature < -40 || temperature > 40) {
      throw new Error('Temperature must be between -40°C and +40°C.');
    }
    this.props.temperature = temperature;
    this.props.updatedAt = new Date();
  }

  setCargoDescription(description: string): void {
    this.props.cargoDescription = description;
    this.props.updatedAt = new Date();
  }

  assignToBooking(bookingId: string): void {
    this.props.bookingId = bookingId;
    this.props.updatedAt = new Date();
  }

  /**
   * Convert to plain object for persistence
   */
  toObject(): ContainerProps {
    return { ...this.props };
  }
}
