/**
 * Port Entity
 *
 * Represents a maritime port (based on UN/LOCODE standard)
 *
 * Business Rules:
 * - Port code must follow UN/LOCODE format (2-letter country + 3-letter location)
 * - Coordinates must be valid latitude/longitude
 */

export interface PortCoordinates {
  latitude: number;
  longitude: number;
}

export interface PortProps {
  id: string;
  code: string; // UN/LOCODE (e.g., 'NLRTM' for Rotterdam)
  name: string; // Port name
  city: string;
  country: string; // ISO 3166-1 alpha-2 country code
  countryName: string; // Full country name
  coordinates: PortCoordinates;
  timezone?: string; // IANA timezone (e.g., 'Europe/Amsterdam')
  isActive: boolean;
  createdAt: Date;
  updatedAt: Date;
}

export class Port {
  private readonly props: PortProps;

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

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

    // Validate UN/LOCODE format
    if (!Port.isValidUNLOCODE(props.code)) {
      throw new Error('Invalid port code format. Must follow UN/LOCODE format (e.g., NLRTM).');
    }

    // Validate country code
    if (!Port.isValidCountryCode(props.country)) {
      throw new Error('Invalid country code. Must be ISO 3166-1 alpha-2 format (e.g., NL).');
    }

    // Validate coordinates
    if (!Port.isValidCoordinates(props.coordinates)) {
      throw new Error('Invalid coordinates.');
    }

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

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

  /**
   * Validate UN/LOCODE format (5 characters: 2-letter country code + 3-letter location code)
   */
  private static isValidUNLOCODE(code: string): boolean {
    const unlocodePattern = /^[A-Z]{2}[A-Z0-9]{3}$/;
    return unlocodePattern.test(code);
  }

  /**
   * Validate ISO 3166-1 alpha-2 country code
   */
  private static isValidCountryCode(code: string): boolean {
    const countryCodePattern = /^[A-Z]{2}$/;
    return countryCodePattern.test(code);
  }

  /**
   * Validate coordinates
   */
  private static isValidCoordinates(coords: PortCoordinates): boolean {
    const { latitude, longitude } = coords;
    return latitude >= -90 && latitude <= 90 && longitude >= -180 && longitude <= 180;
  }

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

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

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

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

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

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

  get coordinates(): PortCoordinates {
    return { ...this.props.coordinates };
  }

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

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

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

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

  // Business methods
  /**
   * Get display name (e.g., "Rotterdam, Netherlands (NLRTM)")
   */
  getDisplayName(): string {
    return `${this.props.name}, ${this.props.countryName} (${this.props.code})`;
  }

  /**
   * Calculate distance to another port (Haversine formula)
   * Returns distance in kilometers
   */
  distanceTo(otherPort: Port): number {
    const R = 6371; // Earth's radius in kilometers
    const lat1 = this.toRadians(this.props.coordinates.latitude);
    const lat2 = this.toRadians(otherPort.coordinates.latitude);
    const deltaLat = this.toRadians(
      otherPort.coordinates.latitude - this.props.coordinates.latitude
    );
    const deltaLon = this.toRadians(
      otherPort.coordinates.longitude - this.props.coordinates.longitude
    );

    const a =
      Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
      Math.cos(lat1) * Math.cos(lat2) * Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2);

    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

    return R * c;
  }

  private toRadians(degrees: number): number {
    return degrees * (Math.PI / 180);
  }

  updateCoordinates(coordinates: PortCoordinates): void {
    if (!Port.isValidCoordinates(coordinates)) {
      throw new Error('Invalid coordinates.');
    }
    this.props.coordinates = { ...coordinates };
    this.props.updatedAt = new Date();
  }

  updateTimezone(timezone: string): void {
    this.props.timezone = timezone;
    this.props.updatedAt = new Date();
  }

  deactivate(): void {
    this.props.isActive = false;
    this.props.updatedAt = new Date();
  }

  activate(): void {
    this.props.isActive = true;
    this.props.updatedAt = new Date();
  }

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