import { PortCode } from '../value-objects/port-code.vo';
import { ContainerType } from '../value-objects/container-type.vo';
import { Money } from '../value-objects/money.vo';
import { Volume } from '../value-objects/volume.vo';
import { Surcharge, SurchargeCollection } from '../value-objects/surcharge.vo';
import { DateRange } from '../value-objects/date-range.vo';

/**
 * Volume Range - Valid range for CBM
 */
export interface VolumeRange {
  minCBM: number;
  maxCBM: number;
}

/**
 * Weight Range - Valid range for KG
 */
export interface WeightRange {
  minKG: number;
  maxKG: number;
}

/**
 * Rate Pricing - Pricing structure for CSV rates
 */
export interface RatePricing {
  pricePerCBM: number;
  pricePerKG: number;
  basePriceUSD: Money;
  basePriceEUR: Money;
}

/**
 * CSV Rate Entity
 *
 * Represents a shipping rate loaded from CSV file.
 * Contains all information needed to calculate freight costs.
 *
 * Business Rules:
 * - Price is calculated as: max(volumeCBM * pricePerCBM, weightKG * pricePerKG) + surcharges
 * - Rate must be valid (within validity period) to be used
 * - Volume and weight must be within specified ranges
 */
export class CsvRate {
  constructor(
    public readonly companyName: string,
    public readonly companyEmail: string,
    public readonly origin: PortCode,
    public readonly destination: PortCode,
    public readonly containerType: ContainerType,
    public readonly volumeRange: VolumeRange,
    public readonly weightRange: WeightRange,
    public readonly palletCount: number,
    public readonly pricing: RatePricing,
    public readonly currency: string, // Primary currency (USD or EUR)
    public readonly surcharges: SurchargeCollection,
    public readonly transitDays: number,
    public readonly validity: DateRange
  ) {
    this.validate();
  }

  private validate(): void {
    if (!this.companyName || this.companyName.trim().length === 0) {
      throw new Error('Company name is required');
    }

    if (!this.companyEmail || this.companyEmail.trim().length === 0) {
      throw new Error('Company email is required');
    }

    if (this.volumeRange.minCBM < 0 || this.volumeRange.maxCBM < 0) {
      throw new Error('Volume range cannot be negative');
    }

    if (this.volumeRange.minCBM > this.volumeRange.maxCBM) {
      throw new Error('Min volume cannot be greater than max volume');
    }

    if (this.weightRange.minKG < 0 || this.weightRange.maxKG < 0) {
      throw new Error('Weight range cannot be negative');
    }

    if (this.weightRange.minKG > this.weightRange.maxKG) {
      throw new Error('Min weight cannot be greater than max weight');
    }

    if (this.palletCount < 0) {
      throw new Error('Pallet count cannot be negative');
    }

    if (this.pricing.pricePerCBM < 0 || this.pricing.pricePerKG < 0) {
      throw new Error('Prices cannot be negative');
    }

    if (this.transitDays <= 0) {
      throw new Error('Transit days must be positive');
    }

    if (this.currency !== 'USD' && this.currency !== 'EUR') {
      throw new Error('Currency must be USD or EUR');
    }
  }

  /**
   * Calculate total price for given volume and weight
   *
   * Business Logic:
   * 1. Calculate volume-based price: volumeCBM * pricePerCBM
   * 2. Calculate weight-based price: weightKG * pricePerKG
   * 3. Take the maximum (freight class rule)
   * 4. Add surcharges
   */
  calculatePrice(volume: Volume): Money {
    // Freight class rule: max(volume price, weight price)
    const freightPrice = volume.calculateFreightPrice(
      this.pricing.pricePerCBM,
      this.pricing.pricePerKG
    );

    // Create Money object in the rate's currency
    let totalPrice = Money.create(freightPrice, this.currency);

    // Add surcharges in the same currency
    const surchargeTotal = this.surcharges.getTotalAmount(this.currency);
    totalPrice = totalPrice.add(surchargeTotal);

    return totalPrice;
  }

  /**
   * Get price in specific currency (USD or EUR)
   */
  getPriceInCurrency(volume: Volume, targetCurrency: 'USD' | 'EUR'): Money {
    const price = this.calculatePrice(volume);

    // If already in target currency, return as-is
    if (price.getCurrency() === targetCurrency) {
      return price;
    }

    // Otherwise, use the pre-calculated base price in target currency
    // and recalculate proportionally
    const basePriceInPrimaryCurrency =
      this.currency === 'USD' ? this.pricing.basePriceUSD : this.pricing.basePriceEUR;

    const basePriceInTargetCurrency =
      targetCurrency === 'USD' ? this.pricing.basePriceUSD : this.pricing.basePriceEUR;

    // Calculate conversion ratio
    const ratio = basePriceInTargetCurrency.getAmount() / basePriceInPrimaryCurrency.getAmount();

    // Apply ratio to calculated price
    const convertedAmount = price.getAmount() * ratio;
    return Money.create(convertedAmount, targetCurrency);
  }

  /**
   * Check if rate is valid for a specific date
   */
  isValidForDate(date: Date): boolean {
    return this.validity.contains(date);
  }

  /**
   * Check if rate is currently valid (today is within validity period)
   */
  isCurrentlyValid(): boolean {
    return this.validity.isCurrentRange();
  }

  /**
   * Check if volume and weight match this rate's range
   */
  matchesVolume(volume: Volume): boolean {
    return volume.isWithinRange(
      this.volumeRange.minCBM,
      this.volumeRange.maxCBM,
      this.weightRange.minKG,
      this.weightRange.maxKG
    );
  }

  /**
   * Check if pallet count matches
   * 0 means "any pallet count" (flexible)
   * Otherwise must match exactly or be within range
   */
  matchesPalletCount(palletCount: number): boolean {
    // If rate has 0 pallets, it's flexible
    if (this.palletCount === 0) {
      return true;
    }
    // Otherwise must match exactly
    return this.palletCount === palletCount;
  }

  /**
   * Check if rate matches a specific route
   */
  matchesRoute(origin: PortCode, destination: PortCode): boolean {
    return this.origin.equals(origin) && this.destination.equals(destination);
  }

  /**
   * Check if rate has separate surcharges
   */
  hasSurcharges(): boolean {
    return !this.surcharges.isEmpty();
  }

  /**
   * Get surcharge details as formatted string
   */
  getSurchargeDetails(): string {
    return this.surcharges.getDetails();
  }

  /**
   * Check if this is an "all-in" rate (no separate surcharges)
   */
  isAllInPrice(): boolean {
    return this.surcharges.isEmpty();
  }

  /**
   * Get route description
   */
  getRouteDescription(): string {
    return `${this.origin.getValue()} → ${this.destination.getValue()}`;
  }

  /**
   * Get company and route summary
   */
  getSummary(): string {
    return `${this.companyName}: ${this.getRouteDescription()} (${this.containerType.getValue()})`;
  }

  toString(): string {
    return this.getSummary();
  }
}
