import { SettlementOrder } from './settlement-order.aggregate';
import { UserId, SettlementStatus, SettleCurrency, Money } from '@/domain/value-objects';
import Decimal from 'decimal.js';

jest.mock('@/shared/exceptions/domain.exception', () => ({
  DomainError: class DomainError extends Error {
    constructor(message: string) {
      super(message);
      this.name = 'DomainError';
    }
  },
}));

describe('SettlementOrder Aggregate', () => {
  describe('create', () => {
    it('should create a new settlement order', () => {
      const order = SettlementOrder.create({
        userId: UserId.create(1),
        usdtAmount: Money.USDT(100),
        settleCurrency: SettleCurrency.BNB,
      });

      expect(order.userId.value).toBe(BigInt(1));
      expect(order.usdtAmount.value).toBe(100);
      expect(order.settleCurrency).toBe(SettleCurrency.BNB);
      expect(order.status).toBe(SettlementStatus.PENDING);
      expect(order.isPending).toBe(true);
      expect(order.isCompleted).toBe(false);
      expect(order.swapTxHash).toBeNull();
      expect(order.receivedAmount).toBeNull();
    });
  });

  describe('startSwapping', () => {
    it('should start swapping process', () => {
      const order = SettlementOrder.create({
        userId: UserId.create(1),
        usdtAmount: Money.USDT(100),
        settleCurrency: SettleCurrency.OG,
      });

      order.startSwapping();

      expect(order.status).toBe(SettlementStatus.SWAPPING);
    });

    it('should throw error when not pending', () => {
      const order = SettlementOrder.create({
        userId: UserId.create(1),
        usdtAmount: Money.USDT(100),
        settleCurrency: SettleCurrency.BNB,
      });

      order.startSwapping();
      expect(() => order.startSwapping()).toThrow('Only pending');
    });
  });

  describe('complete', () => {
    it('should complete settlement from pending status', () => {
      const order = SettlementOrder.create({
        userId: UserId.create(1),
        usdtAmount: Money.USDT(100),
        settleCurrency: SettleCurrency.BNB,
      });

      order.complete('swap_tx_123', Money.BNB(0.05));

      expect(order.status).toBe(SettlementStatus.COMPLETED);
      expect(order.isCompleted).toBe(true);
      expect(order.swapTxHash).toBe('swap_tx_123');
      expect(order.receivedAmount?.value).toBe(0.05);
      expect(order.settledAt).not.toBeNull();
    });

    it('should complete settlement from swapping status', () => {
      const order = SettlementOrder.create({
        userId: UserId.create(1),
        usdtAmount: Money.USDT(50),
        settleCurrency: SettleCurrency.OG,
      });

      order.startSwapping();
      order.complete('swap_tx_456', Money.OG(10));

      expect(order.status).toBe(SettlementStatus.COMPLETED);
    });
  });

  describe('fail', () => {
    it('should mark settlement as failed', () => {
      const order = SettlementOrder.create({
        userId: UserId.create(1),
        usdtAmount: Money.USDT(100),
        settleCurrency: SettleCurrency.DST,
      });

      order.fail();

      expect(order.status).toBe(SettlementStatus.FAILED);
    });

    it('should throw error when completing already completed settlement', () => {
      const order = SettlementOrder.create({
        userId: UserId.create(1),
        usdtAmount: Money.USDT(100),
        settleCurrency: SettleCurrency.BNB,
      });

      order.complete('tx', Money.BNB(0.1));
      expect(() => order.fail()).toThrow('Completed settlements');
    });
  });

  describe('reconstruct', () => {
    it('should reconstruct from database record', () => {
      const order = SettlementOrder.reconstruct({
        id: BigInt(1),
        userId: BigInt(100),
        usdtAmount: new Decimal(200),
        settleCurrency: 'BNB',
        swapTxHash: 'tx_swap',
        receivedAmount: new Decimal(0.1),
        status: 'COMPLETED',
        settledAt: new Date(),
        createdAt: new Date(),
      });

      expect(order.id).toBe(BigInt(1));
      expect(order.status).toBe(SettlementStatus.COMPLETED);
      expect(order.isCompleted).toBe(true);
      expect(order.receivedAmount?.value).toBe(0.1);
    });
  });
});
