import { describe, it, expect, beforeEach } from 'vitest';
import { NumberBank } from '../NumberBank';

describe('NumberBank', () =>
{
    describe('constructor', () =>
    {
        it('should create bank with specified size', () =>
        {
            const bank = new NumberBank(5);

            // Should be able to reserve 5 numbers (LIFO order - pops from end)
            expect(bank.reserveNumber()).toBe(4);
            expect(bank.reserveNumber()).toBe(3);
            expect(bank.reserveNumber()).toBe(2);
            expect(bank.reserveNumber()).toBe(1);
            expect(bank.reserveNumber()).toBe(0);
            expect(bank.reserveNumber()).toBe(-1); // No more available
        });

        it('should handle negative size as zero', () =>
        {
            const bank = new NumberBank(-5);
            expect(bank.reserveNumber()).toBe(-1);
        });

        it('should handle zero size', () =>
        {
            const bank = new NumberBank(0);
            expect(bank.reserveNumber()).toBe(-1);
        });
    });

    describe('reserveNumber', () =>
    {
        let bank: NumberBank;

        beforeEach(() =>
        {
            bank = new NumberBank(3);
        });

        it('should return numbers in LIFO order (stack behavior)', () =>
        {
            // Numbers are added 0, 1, 2 to the array
            // pop() returns from the end, so we get 2, 1, 0
            expect(bank.reserveNumber()).toBe(2);
            expect(bank.reserveNumber()).toBe(1);
            expect(bank.reserveNumber()).toBe(0);
        });

        it('should return -1 when no numbers available', () =>
        {
            bank.reserveNumber();
            bank.reserveNumber();
            bank.reserveNumber();

            expect(bank.reserveNumber()).toBe(-1);
        });
    });

    describe('freeNumber', () =>
    {
        let bank: NumberBank;

        beforeEach(() =>
        {
            bank = new NumberBank(3);
        });

        it('should make number available again after freeing', () =>
        {
            const num = bank.reserveNumber();
            bank.freeNumber(num);

            // The freed number should be available again
            expect(bank.reserveNumber()).toBe(num);
        });

        it('should handle freeing in different order', () =>
        {
            const n1 = bank.reserveNumber();
            const n2 = bank.reserveNumber();
            const n3 = bank.reserveNumber();

            // Free in middle order
            bank.freeNumber(n2);
            bank.freeNumber(n1);

            // Should get them back in LIFO order
            expect(bank.reserveNumber()).toBe(n1);
            expect(bank.reserveNumber()).toBe(n2);
        });

        it('should ignore freeing numbers not in reserved list', () =>
        {
            bank.reserveNumber(); // reserves 2

            // Try to free a number that wasn't reserved
            bank.freeNumber(999);

            // Should still work normally
            expect(bank.reserveNumber()).toBe(1);
        });

        it('should allow reusing freed numbers', () =>
        {
            // Reserve all
            const n1 = bank.reserveNumber();
            const n2 = bank.reserveNumber();
            const n3 = bank.reserveNumber();

            // Free and re-reserve multiple times
            bank.freeNumber(n1);
            const reused1 = bank.reserveNumber();
            expect(reused1).toBe(n1);

            bank.freeNumber(n2);
            bank.freeNumber(reused1);

            expect(bank.reserveNumber()).toBe(reused1);
            expect(bank.reserveNumber()).toBe(n2);
        });
    });

    describe('dispose', () =>
    {
        it('should set internal arrays to null', () =>
        {
            const bank = new NumberBank(5);
            bank.reserveNumber();

            bank.dispose();

            // After dispose, reserveNumber should fail (arrays are null)
            // This will throw an error, which is expected behavior
            expect(() => bank.reserveNumber()).toThrow();
        });
    });

    describe('edge cases', () =>
    {
        it('should handle large bank size', () =>
        {
            const bank = new NumberBank(1000);

            // Reserve all
            for (let i = 0; i < 1000; i++)
            {
                expect(bank.reserveNumber()).toBeGreaterThanOrEqual(0);
            }

            expect(bank.reserveNumber()).toBe(-1);
        });

        it('should maintain consistency after multiple reserve/free cycles', () =>
        {
            const bank = new NumberBank(10);
            const reserved: number[] = [];

            // Reserve 5
            for (let i = 0; i < 5; i++)
            {
                reserved.push(bank.reserveNumber());
            }

            // Free 3
            bank.freeNumber(reserved[0]);
            bank.freeNumber(reserved[2]);
            bank.freeNumber(reserved[4]);

            // Reserve 3 more (should get the freed ones)
            const newReserved: number[] = [];
            for (let i = 0; i < 3; i++)
            {
                newReserved.push(bank.reserveNumber());
            }

            // All previously freed numbers should be reserved again
            expect(newReserved).toContain(reserved[0]);
            expect(newReserved).toContain(reserved[2]);
            expect(newReserved).toContain(reserved[4]);
        });
    });
});
