import {
    clampDayToMonth,
    generateMonthlyDueDates,
    generateQuarterlyDueDates,
} from './fees.installment-dates';

const d = (iso: string) => new Date(iso);

describe('clampDayToMonth', () => {
    it('clamps 31 to 28 for Feb 2027 (non-leap)', () => {
        expect(clampDayToMonth(2027, 2, 31)).toBe(28);
    });
    it('clamps 31 to 29 for Feb 2028 (leap)', () => {
        expect(clampDayToMonth(2028, 2, 31)).toBe(29);
    });
    it('clamps 31 to 30 for April', () => {
        expect(clampDayToMonth(2027, 4, 31)).toBe(30);
    });
    it('leaves 10 unchanged for any month', () => {
        expect(clampDayToMonth(2027, 2, 10)).toBe(10);
    });
});

describe('generateMonthlyDueDates', () => {
    it('calendar AY 2027: Jan 1 2027 → Dec 31 2027, dueDay=10 → 12 installments Jan..Dec 2027', () => {
        const dates = generateMonthlyDueDates(d('2027-01-01T00:00:00Z'), d('2027-12-31T00:00:00Z'), 10);
        expect(dates).toHaveLength(12);
        expect(dates[0].toISOString().slice(0, 10)).toBe('2027-01-10');
        expect(dates[11].toISOString().slice(0, 10)).toBe('2027-12-10');
    });

    it('April-start AY: Apr 1 2029 → Mar 31 2030, dueDay=10 → Apr 2029..Mar 2030', () => {
        const dates = generateMonthlyDueDates(d('2029-04-01T00:00:00Z'), d('2030-03-31T00:00:00Z'), 10);
        expect(dates).toHaveLength(12);
        expect(dates[0].toISOString().slice(0, 10)).toBe('2029-04-10');
        expect(dates[1].toISOString().slice(0, 10)).toBe('2029-05-10');
        expect(dates[11].toISOString().slice(0, 10)).toBe('2030-03-10');
    });

    it('June-start AY: Jun 1 2025 → May 31 2026, dueDay=1 → Jun 2025..May 2026', () => {
        const dates = generateMonthlyDueDates(d('2025-06-01T00:00:00Z'), d('2026-05-31T00:00:00Z'), 1);
        expect(dates.map(x => x.toISOString().slice(0, 7))).toEqual([
            '2025-06', '2025-07', '2025-08', '2025-09', '2025-10', '2025-11',
            '2025-12', '2026-01', '2026-02', '2026-03', '2026-04', '2026-05',
        ]);
    });

    it('range smaller than 12 months emits one per month (e.g. 6 months)', () => {
        const dates = generateMonthlyDueDates(d('2027-01-01T00:00:00Z'), d('2027-06-30T00:00:00Z'), 5);
        expect(dates).toHaveLength(6);
    });

    it('clamps dueDay=31 per-month (Feb → 28 in 2027)', () => {
        const dates = generateMonthlyDueDates(d('2027-01-01T00:00:00Z'), d('2027-03-31T00:00:00Z'), 31);
        expect(dates[0].toISOString().slice(0, 10)).toBe('2027-01-31');
        expect(dates[1].toISOString().slice(0, 10)).toBe('2027-02-28');
        expect(dates[2].toISOString().slice(0, 10)).toBe('2027-03-31');
    });

    it('IST-drift safe: endDate stored as 2027-12-31T18:30:00Z (end-of-day IST) still resolves to year 2027', () => {
        // Form inputs in IST are commonly serialized as T18:30:00Z = 00:00 IST next day.
        // With UTC-strict reading, this correctly resolves to year 2027 month 12,
        // not year 2028 month 1 (which was the old getFullYear() bug on an IST server).
        const dates = generateMonthlyDueDates(
            d('2027-01-01T00:00:00Z'),
            d('2027-12-31T18:30:00Z'),
            10,
        );
        expect(dates).toHaveLength(12);
        expect(dates[0].toISOString().slice(0, 7)).toBe('2027-01');
        expect(dates[11].toISOString().slice(0, 7)).toBe('2027-12');
    });
});

describe('generateQuarterlyDueDates', () => {
    it('April-start AY → Apr, Jul, Oct 2029; Jan 2030', () => {
        const dates = generateQuarterlyDueDates(d('2029-04-01T00:00:00Z'), d('2030-03-31T00:00:00Z'), 10);
        expect(dates).toHaveLength(4);
        expect(dates.map(x => x.toISOString().slice(0, 7))).toEqual([
            '2029-04', '2029-07', '2029-10', '2030-01',
        ]);
    });

    it('calendar AY 2027 → Jan, Apr, Jul, Oct 2027', () => {
        const dates = generateQuarterlyDueDates(d('2027-01-01T00:00:00Z'), d('2027-12-31T00:00:00Z'), 10);
        expect(dates.map(x => x.toISOString().slice(0, 7))).toEqual([
            '2027-01', '2027-04', '2027-07', '2027-10',
        ]);
    });
});
