/**
 * Value Object representing a monetary amount with currency.
 * Immutable, validated, with private constructor and static factory.
 */
export class Money {
  private constructor(
    private readonly _amount: number,
    private readonly _currency: string,
  ) {}

  /**
   * Create a Money value object from a numeric amount and optional currency.
   * @param amount - Must be >= 0
   * @param currency - ISO 4217 currency code, defaults to 'USD'
   */
  static create(amount: number, currency: string = 'USD'): Money {
    if (amount < 0) {
      throw new Error('Money amount cannot be negative');
    }
    if (!Number.isFinite(amount)) {
      throw new Error('Money amount must be a finite number');
    }
    const normalized = Math.round(amount * 100) / 100;
    const normalizedCurrency = currency.toUpperCase().trim();
    if (normalizedCurrency.length < 2 || normalizedCurrency.length > 10) {
      throw new Error('Invalid currency code');
    }
    return new Money(normalized, normalizedCurrency);
  }

  /**
   * Reconstruct from a stored string value (e.g. from database numeric column).
   */
  static fromString(value: string, currency: string = 'USD'): Money {
    const amount = parseFloat(value);
    if (isNaN(amount)) {
      throw new Error('Invalid money string value');
    }
    return Money.create(amount, currency);
  }

  get amount(): number {
    return this._amount;
  }

  get currency(): string {
    return this._currency;
  }

  /**
   * Returns the string representation suitable for database storage.
   */
  toStorageString(): string {
    return this._amount.toFixed(2);
  }

  /**
   * Returns display string, e.g. "100.00 USD".
   */
  toString(): string {
    return `${this._amount.toFixed(2)} ${this._currency}`;
  }

  equals(other: Money): boolean {
    return this._amount === other._amount && this._currency === other._currency;
  }

  add(other: Money): Money {
    if (this._currency !== other._currency) {
      throw new Error('Cannot add Money with different currencies');
    }
    return Money.create(this._amount + other._amount, this._currency);
  }

  subtract(other: Money): Money {
    if (this._currency !== other._currency) {
      throw new Error('Cannot subtract Money with different currencies');
    }
    return Money.create(this._amount - other._amount, this._currency);
  }

  multiply(factor: number): Money {
    return Money.create(this._amount * factor, this._currency);
  }

  isGreaterThan(other: Money): boolean {
    if (this._currency !== other._currency) {
      throw new Error('Cannot compare Money with different currencies');
    }
    return this._amount > other._amount;
  }

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