import { describe, it, expect } from 'bun:test';
import { computeBalances, computeSettlements } from './settlement';
import type { Expense, Transfer, Settlement } from './types';

describe('computeBalances', () => {
  it('returns empty map for no people', () => {
    const balances = computeBalances([], [], []);
    expect(balances.size).toBe(0);
  });

  it('returns zero balances for people with no expenses', () => {
    const balances = computeBalances(['alice', 'bob'], [], []);
    expect(balances.get('alice')).toBe(0);
    expect(balances.get('bob')).toBe(0);
  });

  it('splits an expense evenly among all members', () => {
    const expense: Expense = {
      id: 'e1', ledgerId: 'L1', paidBy: 'alice',
      amount: 30_00, description: 'Dinner',
      splitAmong: ['alice', 'bob', 'charlie'],
      createdAt: '2024-01-01T00:00:00Z'
    };
    const balances = computeBalances(
      ['alice', 'bob', 'charlie'], [expense], []
    );
    expect(balances.get('alice')).toBe(20_00);
    expect(balances.get('bob')).toBe(-10_00);
    expect(balances.get('charlie')).toBe(-10_00);
  });

  it('handles uneven splits (payer not in split)', () => {
    const expense: Expense = {
      id: 'e1', ledgerId: 'L1', paidBy: 'alice',
      amount: 50_00, description: 'Groceries',
      splitAmong: ['bob', 'charlie'],
      createdAt: '2024-01-01T00:00:00Z'
    };
    const balances = computeBalances(
      ['alice', 'bob', 'charlie'], [expense], []
    );
    expect(balances.get('alice')).toBe(50_00);
    expect(balances.get('bob')).toBe(-25_00);
    expect(balances.get('charlie')).toBe(-25_00);
  });

  it('accounts for direct transfers', () => {
    const transfer: Transfer = {
      id: 't1', ledgerId: 'L1',
      from: 'bob', to: 'alice', amount: 15_00,
      createdAt: '2024-01-01T00:00:00Z'
    };
    const balances = computeBalances(
      ['alice', 'bob'], [], [transfer]
    );
    expect(balances.get('alice')).toBe(15_00);
    expect(balances.get('bob')).toBe(-15_00);
  });

  it('combines expenses and transfers', () => {
    const expense: Expense = {
      id: 'e1', ledgerId: 'L1', paidBy: 'alice',
      amount: 40_00, description: 'Dinner',
      splitAmong: ['alice', 'bob'],
      createdAt: '2024-01-01T00:00:00Z'
    };
    const transfer: Transfer = {
      id: 't1', ledgerId: 'L1',
      from: 'bob', to: 'alice', amount: 5_00,
      createdAt: '2024-01-01T00:00:00Z'
    };
    const balances = computeBalances(
      ['alice', 'bob'], [expense], [transfer]
    );
    expect(balances.get('alice')).toBe(25_00);
    expect(balances.get('bob')).toBe(-25_00);
  });

  it('handles multiple expenses', () => {
    const e1: Expense = {
      id: 'e1', ledgerId: 'L1', paidBy: 'alice',
      amount: 100_00, description: 'Hotel',
      splitAmong: ['alice', 'bob'],
      createdAt: '2024-01-01T00:00:00Z'
    };
    const e2: Expense = {
      id: 'e2', ledgerId: 'L1', paidBy: 'bob',
      amount: 60_00, description: 'Food',
      splitAmong: ['alice', 'bob'],
      createdAt: '2024-01-02T00:00:00Z'
    };
    const balances = computeBalances(
      ['alice', 'bob'], [e1, e2], []
    );
    expect(balances.get('alice')).toBe(20_00);
    expect(balances.get('bob')).toBe(-20_00);
  });
});

describe('computeSettlements', () => {
  it('returns empty array when everyone is settled', () => {
    const balances = new Map([['a', 0], ['b', 0]]);
    expect(computeSettlements(balances)).toEqual([]);
  });

  it('settles one person owing another', () => {
    const balances = new Map([['alice', 100], ['bob', -100]]);
    const settlements = computeSettlements(balances);
    expect(settlements).toEqual([
      { from: 'bob', to: 'alice', amount: 100 },
    ]);
  });

  it('settles 3 people with minimum transfers', () => {
    const balances = new Map([
      ['alice', 200],
      ['bob', -150],
      ['charlie', -50],
    ]);
    const settlements = computeSettlements(balances);
    expect(settlements).toHaveLength(2);
    // Alice should not pay anyone
    const fromAlice = settlements
      .filter((s: Settlement) => s.from === 'alice')
      .reduce((sum: number, s: Settlement) => sum + s.amount, 0);
    expect(fromAlice).toBe(0);
    // All amounts positive
    for (const s of settlements) {
      expect(s.amount).toBeGreaterThan(0);
    }
  });

  it('handles chain settlement (2 creditors, 1 debtor)', () => {
    const balances = new Map([
      ['alice', 50],
      ['bob', 50],
      ['charlie', -100],
    ]);
    const settlements = computeSettlements(balances);
    const total = settlements.reduce((s: number, x: Settlement) => s + x.amount, 0);
    expect(total).toBe(100);
    expect(settlements.length).toBeLessThanOrEqual(3);
  });

  it('handles complex 4-person scenario', () => {
    const balances = new Map([
      ['alice', 30],
      ['bob', -10],
      ['charlie', -5],
      ['dave', -15],
    ]);
    const settlements = computeSettlements(balances);
    // Sum of settlements = sum of positive balances
    const totalSettled = settlements.reduce((s: number, x: Settlement) => s + x.amount, 0);
    expect(totalSettled).toBe(30);
    // Verify net-after is ~0
    const after = new Map(balances);
    for (const s of settlements) {
      after.set(s.from, (after.get(s.from) ?? 0) + s.amount);
      after.set(s.to, (after.get(s.to) ?? 0) - s.amount);
    }
    for (const [_, v] of after) {
      expect(Math.abs(v)).toBeLessThan(1);
    }
  });

  it('handles penny-rounding edge case', () => {
    const balances = new Map([
      ['alice', 10_00],
      ['bob', -3_33],
      ['charlie', -3_33],
      ['dave', -3_34],
    ]);
    const settlements = computeSettlements(balances);
    const after = new Map(balances);
    for (const s of settlements) {
      after.set(s.from, (after.get(s.from) ?? 0) + s.amount);
      after.set(s.to, (after.get(s.to) ?? 0) - s.amount);
    }
    for (const [_, v] of after) {
      expect(Math.abs(v)).toBeLessThanOrEqual(1);
    }
  });
});
