/**
 * Money Value Object
 *
 * Encapsulates currency and amount with proper validation
 *
 * Business Rules:
 * - Amount must be non-negative
 * - Currency must be valid ISO 4217 code
 * - Money is immutable
 * - Arithmetic operations return new Money instances
 */

export class Money {
  private readonly amount: number;
  private readonly currency: string;

  private static readonly SUPPORTED_CURRENCIES = ['USD', 'EUR', 'GBP', 'CNY', 'JPY'];

  private constructor(amount: number, currency: string) {
    this.amount = amount;
    this.currency = currency;
  }

  static create(amount: number, currency: string): Money {
    if (amount < 0) {
      throw new Error('Amount cannot be negative.');
    }

    const normalizedCurrency = currency.trim().toUpperCase();

    if (!Money.isValidCurrency(normalizedCurrency)) {
      throw new Error(
        `Invalid currency code: ${currency}. Supported currencies: ${Money.SUPPORTED_CURRENCIES.join(
          ', '
        )}`
      );
    }

    // Round to 2 decimal places to avoid floating point issues
    const roundedAmount = Math.round(amount * 100) / 100;

    return new Money(roundedAmount, normalizedCurrency);
  }

  static zero(currency: string): Money {
    return Money.create(0, currency);
  }

  private static isValidCurrency(currency: string): boolean {
    return Money.SUPPORTED_CURRENCIES.includes(currency);
  }

  getAmount(): number {
    return this.amount;
  }

  getCurrency(): string {
    return this.currency;
  }

  add(other: Money): Money {
    this.ensureSameCurrency(other);
    return Money.create(this.amount + other.amount, this.currency);
  }

  subtract(other: Money): Money {
    this.ensureSameCurrency(other);
    const result = this.amount - other.amount;
    if (result < 0) {
      throw new Error('Subtraction would result in negative amount.');
    }
    return Money.create(result, this.currency);
  }

  multiply(multiplier: number): Money {
    if (multiplier < 0) {
      throw new Error('Multiplier cannot be negative.');
    }
    return Money.create(this.amount * multiplier, this.currency);
  }

  divide(divisor: number): Money {
    if (divisor <= 0) {
      throw new Error('Divisor must be positive.');
    }
    return Money.create(this.amount / divisor, this.currency);
  }

  isGreaterThan(other: Money): boolean {
    this.ensureSameCurrency(other);
    return this.amount > other.amount;
  }

  isLessThan(other: Money): boolean {
    this.ensureSameCurrency(other);
    return this.amount < other.amount;
  }

  isEqualTo(other: Money): boolean {
    return this.currency === other.currency && this.amount === other.amount;
  }

  isZero(): boolean {
    return this.amount === 0;
  }

  private ensureSameCurrency(other: Money): void {
    if (this.currency !== other.currency) {
      throw new Error(`Currency mismatch: ${this.currency} vs ${other.currency}`);
    }
  }

  /**
   * Format as string with currency symbol
   */
  format(): string {
    const symbols: { [key: string]: string } = {
      USD: '$',
      EUR: '€',
      GBP: '£',
      CNY: '¥',
      JPY: '¥',
    };

    const symbol = symbols[this.currency] || this.currency;
    return `${symbol}${this.amount.toFixed(2)}`;
  }

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

  toObject(): { amount: number; currency: string } {
    return {
      amount: this.amount,
      currency: this.currency,
    };
  }
}
