import { Balance } from './balance.vo';
import { Money } from './money.vo';

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

describe('Balance Value Object', () => {
  describe('create', () => {
    it('should create balance with available and frozen amounts', () => {
      const balance = Balance.create(Money.USDT(100), Money.USDT(20));
      expect(balance.available.value).toBe(100);
      expect(balance.frozen.value).toBe(20);
    });

    it('should create zero balance', () => {
      const balance = Balance.zero('USDT');
      expect(balance.available.isZero()).toBe(true);
      expect(balance.frozen.isZero()).toBe(true);
    });

    it('should throw error when currencies mismatch', () => {
      expect(() => Balance.create(Money.USDT(100), Money.BNB(20))).toThrow('same currency');
    });
  });

  describe('total', () => {
    it('should calculate total correctly', () => {
      const balance = Balance.create(Money.USDT(100), Money.USDT(50));
      expect(balance.total.value).toBe(150);
    });
  });

  describe('add', () => {
    it('should add to available balance', () => {
      const balance = Balance.create(Money.USDT(100), Money.USDT(20));
      const newBalance = balance.add(Money.USDT(50));
      expect(newBalance.available.value).toBe(150);
      expect(newBalance.frozen.value).toBe(20);
    });
  });

  describe('deduct', () => {
    it('should deduct from available balance', () => {
      const balance = Balance.create(Money.USDT(100), Money.USDT(20));
      const newBalance = balance.deduct(Money.USDT(30));
      expect(newBalance.available.value).toBe(70);
    });

    it('should throw error when insufficient balance', () => {
      const balance = Balance.create(Money.USDT(50), Money.USDT(0));
      expect(() => balance.deduct(Money.USDT(100))).toThrow('Insufficient');
    });
  });

  describe('freeze', () => {
    it('should move from available to frozen', () => {
      const balance = Balance.create(Money.USDT(100), Money.USDT(20));
      const newBalance = balance.freeze(Money.USDT(30));
      expect(newBalance.available.value).toBe(70);
      expect(newBalance.frozen.value).toBe(50);
    });

    it('should throw error when insufficient available balance to freeze', () => {
      const balance = Balance.create(Money.USDT(20), Money.USDT(0));
      expect(() => balance.freeze(Money.USDT(50))).toThrow('Insufficient');
    });
  });

  describe('unfreeze', () => {
    it('should move from frozen to available', () => {
      const balance = Balance.create(Money.USDT(100), Money.USDT(50));
      const newBalance = balance.unfreeze(Money.USDT(30));
      expect(newBalance.available.value).toBe(130);
      expect(newBalance.frozen.value).toBe(20);
    });

    it('should throw error when insufficient frozen balance', () => {
      const balance = Balance.create(Money.USDT(100), Money.USDT(20));
      expect(() => balance.unfreeze(Money.USDT(50))).toThrow('Insufficient');
    });
  });
});
