import Decimal from 'decimal.js';
import { DomainError } from '@/shared/exceptions/domain.exception';

export class Money {
  private readonly _amount: Decimal;
  private readonly _currency: string;
  private readonly _allowNegative: boolean;

  private constructor(amount: Decimal, currency: string, allowNegative: boolean = false) {
    if (!allowNegative && amount.lessThan(0)) {
      throw new DomainError('Money amount cannot be negative');
    }
    this._amount = amount;
    this._currency = currency;
    this._allowNegative = allowNegative;
  }

  static create(amount: number | string | Decimal, currency: string): Money {
    return new Money(new Decimal(amount), currency);
  }

  // Create signed money (allows negative values for ledger entries)
  static signed(amount: number | string | Decimal, currency: string): Money {
    return new Money(new Decimal(amount), currency, true);
  }

  static USDT(amount: number | string | Decimal): Money {
    return new Money(new Decimal(amount), 'USDT');
  }

  static DST(amount: number | string | Decimal): Money {
    return new Money(new Decimal(amount), 'DST');
  }

  static BNB(amount: number | string | Decimal): Money {
    return new Money(new Decimal(amount), 'BNB');
  }

  static OG(amount: number | string | Decimal): Money {
    return new Money(new Decimal(amount), 'OG');
  }

  static RWAD(amount: number | string | Decimal): Money {
    return new Money(new Decimal(amount), 'RWAD');
  }

  static zero(currency: string = 'USDT'): Money {
    return new Money(new Decimal(0), currency);
  }

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

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

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

  add(other: Money): Money {
    this.ensureSameCurrency(other);
    return new Money(this._amount.plus(other._amount), this._currency, this._allowNegative);
  }

  subtract(other: Money): Money {
    this.ensureSameCurrency(other);
    const result = this._amount.minus(other._amount);
    if (!this._allowNegative && result.lessThan(0)) {
      throw new DomainError('Insufficient balance');
    }
    return new Money(result, this._currency, this._allowNegative);
  }

  lessThan(other: Money): boolean {
    this.ensureSameCurrency(other);
    return this._amount.lessThan(other._amount);
  }

  lessThanOrEqual(other: Money): boolean {
    this.ensureSameCurrency(other);
    return this._amount.lessThanOrEqualTo(other._amount);
  }

  greaterThan(other: Money): boolean {
    this.ensureSameCurrency(other);
    return this._amount.greaterThan(other._amount);
  }

  greaterThanOrEqual(other: Money): boolean {
    this.ensureSameCurrency(other);
    return this._amount.greaterThanOrEqualTo(other._amount);
  }

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

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

  isPositive(): boolean {
    return this._amount.greaterThan(0);
  }

  isNegative(): boolean {
    return this._amount.lessThan(0);
  }

  abs(): Money {
    return new Money(this._amount.abs(), this._currency, this._allowNegative);
  }

  negate(): Money {
    return new Money(this._amount.negated(), this._currency, true);
  }

  toDecimal(): Decimal {
    return this._amount;
  }

  toString(): string {
    return `${this._amount.toFixed(8)} ${this._currency}`;
  }

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