import { describe, expect, it } from 'vitest';
import {
    addDays,
    addOneYear,
    computeGaps,
    daysInRange,
    hasOverlap,
    projectTemplate,
    rangesOverlap,
    resolveSeasonConflicts,
    seasonRangeKey,
    subtractRange,
    type PricedSeason,
    type SeasonTemplate,
} from './season-range-utils';

/**
 * Test fixture mirroring `config/season-templates.php` (the production list is
 * never duplicated in shipped TS code — pages receive it as an Inertia prop).
 */
const SEASON_TEMPLATES: SeasonTemplate[] = [
    { name: 'Moyenne', start: '04-16', end: '04-27', intensity: 'mid' },
    { name: 'Vac. Pâques', start: '04-28', end: '05-04', intensity: 'event' },
    { name: 'Aïd Adha', start: '05-14', end: '05-17', intensity: 'event' },
    { name: 'Pic juin', start: '06-15', end: '06-30', intensity: 'high' },
    { name: 'Très haute été', start: '07-01', end: '08-31', intensity: 'peak' },
    { name: 'Haute sept', start: '09-01', end: '09-15', intensity: 'high' },
    { name: 'Basse', start: '11-01', end: '12-20', intensity: 'low' },
    { name: 'Fêtes / Diaspora', start: '12-21', end: '01-06', intensity: 'peak' },
    { name: 'Basse hiver', start: '01-07', end: '03-19', intensity: 'low' },
    { name: 'Aïd Fitr', start: '03-05', end: '03-10', intensity: 'event' },
];

const tpl = (name: string): SeasonTemplate => {
    const found = SEASON_TEMPLATES.find((t) => t.name === name);
    if (!found) throw new Error(`Unknown template: ${name}`);
    return found;
};

const TODAY = '2026-06-11'; // reference day of the functional spec

describe('projectTemplate', () => {
    it('projects an upcoming same-year template as-is (Pic juin)', () => {
        expect(projectTemplate(tpl('Pic juin'), TODAY)).toEqual({ start_date: '2026-06-15', end_date: '2026-06-30' });
    });

    it('projects a year-crossing template with end year = start year + 1 (Fêtes / Diaspora)', () => {
        expect(projectTemplate(tpl('Fêtes / Diaspora'), TODAY)).toEqual({ start_date: '2026-12-21', end_date: '2027-01-06' });
    });

    it('projects an already-past season onto next year (Aïd Fitr seen from June 11)', () => {
        expect(projectTemplate(tpl('Aïd Fitr'), TODAY)).toEqual({ start_date: '2027-03-05', end_date: '2027-03-10' });
    });

    it('clamps the start to today for an in-progress season', () => {
        expect(projectTemplate(tpl('Très haute été'), '2026-07-15')).toEqual({ start_date: '2026-07-15', end_date: '2026-08-31' });
    });

    it('clamps an in-progress year-crossing season whose start was last year', () => {
        expect(projectTemplate(tpl('Fêtes / Diaspora'), '2027-01-03')).toEqual({ start_date: '2027-01-03', end_date: '2027-01-06' });
    });

    it('keeps the full range when today is exactly the start date', () => {
        expect(projectTemplate(tpl('Pic juin'), '2026-06-15')).toEqual({ start_date: '2026-06-15', end_date: '2026-06-30' });
    });

    it('still picks the current occurrence when today is its last day (end >= today)', () => {
        expect(projectTemplate(tpl('Pic juin'), '2026-06-30')).toEqual({ start_date: '2026-06-30', end_date: '2026-06-30' });
    });

    it('matches the full spec example for the remaining templates', () => {
        expect(projectTemplate(tpl('Très haute été'), TODAY)).toEqual({ start_date: '2026-07-01', end_date: '2026-08-31' });
        expect(projectTemplate(tpl('Basse hiver'), TODAY)).toEqual({ start_date: '2027-01-07', end_date: '2027-03-19' });
    });
});

describe('rangesOverlap (inclusive bounds)', () => {
    it('detects a plain overlap', () => {
        expect(rangesOverlap({ start_date: '2026-07-01', end_date: '2026-08-31' }, { start_date: '2026-08-30', end_date: '2026-09-10' })).toBe(true);
    });

    it('treats a shared single day as an overlap', () => {
        expect(rangesOverlap({ start_date: '2026-07-01', end_date: '2026-07-10' }, { start_date: '2026-07-10', end_date: '2026-07-20' })).toBe(true);
    });

    it('treats adjacent ranges (end + 1 day = start) as NOT overlapping', () => {
        expect(rangesOverlap({ start_date: '2026-07-01', end_date: '2026-07-10' }, { start_date: '2026-07-11', end_date: '2026-07-20' })).toBe(false);
    });

    it('detects full containment', () => {
        expect(rangesOverlap({ start_date: '2027-01-07', end_date: '2027-03-19' }, { start_date: '2027-03-05', end_date: '2027-03-10' })).toBe(true);
    });
});

describe('hasOverlap (season set guard)', () => {
    it('is false for an empty list or a single season', () => {
        expect(hasOverlap([])).toBe(false);
        expect(hasOverlap([{ start_date: '2026-07-01', end_date: '2026-08-31' }])).toBe(false);
    });

    it('accepts a contiguous, non-overlapping set (adjacent ranges)', () => {
        expect(
            hasOverlap([
                { start_date: '2026-07-01', end_date: '2026-07-10' },
                { start_date: '2026-07-11', end_date: '2026-07-20' },
                { start_date: '2026-07-21', end_date: '2026-07-31' },
            ]),
        ).toBe(false);
    });

    it('flags a shared-endpoint collision the old strict check missed', () => {
        expect(
            hasOverlap([
                { start_date: '2026-07-01', end_date: '2026-07-10' },
                { start_date: '2026-07-10', end_date: '2026-07-20' },
            ]),
        ).toBe(true);
    });

    it('flags containment and identical ranges', () => {
        expect(
            hasOverlap([
                { start_date: '2026-07-01', end_date: '2026-08-31' },
                { start_date: '2026-07-15', end_date: '2026-07-20' },
            ]),
        ).toBe(true);
        expect(
            hasOverlap([
                { start_date: '2026-07-01', end_date: '2026-07-10' },
                { start_date: '2026-07-01', end_date: '2026-07-10' },
            ]),
        ).toBe(true);
    });

    it('detects an overlap between non-adjacent rows in a larger set', () => {
        expect(
            hasOverlap([
                { start_date: '2026-07-01', end_date: '2026-07-10' },
                { start_date: '2026-08-01', end_date: '2026-08-31' },
                { start_date: '2026-07-05', end_date: '2026-07-08' }, // collides with the first
            ]),
        ).toBe(true);
    });
});

describe('subtractRange', () => {
    it('returns the range untouched when there is no overlap', () => {
        expect(subtractRange({ start_date: '2026-07-01', end_date: '2026-07-10' }, { start_date: '2026-08-01', end_date: '2026-08-31' })).toEqual([
            { start_date: '2026-07-01', end_date: '2026-07-10' },
        ]);
    });

    it('keeps the left fragment when cut from the right', () => {
        expect(subtractRange({ start_date: '2026-09-01', end_date: '2026-09-15' }, { start_date: '2026-09-10', end_date: '2026-09-30' })).toEqual([
            { start_date: '2026-09-01', end_date: '2026-09-09' },
        ]);
    });

    it('splits into two fragments when the cut is in the middle', () => {
        expect(subtractRange({ start_date: '2026-09-01', end_date: '2026-09-30' }, { start_date: '2026-09-10', end_date: '2026-09-20' })).toEqual([
            { start_date: '2026-09-01', end_date: '2026-09-09' },
            { start_date: '2026-09-21', end_date: '2026-09-30' },
        ]);
    });
});

describe('resolveSeasonConflicts (trim / split, never delete)', () => {
    const hauteSept = { start_date: '2026-09-01', end_date: '2026-09-15', daily_price: 40, name: 'Haute sept' };

    it('appends untouched when there is no overlap', () => {
        const res = resolveSeasonConflicts<PricedSeason>({ start_date: '2026-10-01', end_date: '2026-10-31', daily_price: 20 }, [hauteSept], {});
        expect(res).toEqual([hauteSept, { start_date: '2026-10-01', end_date: '2026-10-31', daily_price: 20 }]);
    });

    it('NEW wins by default: existing trims, the rest keeps its old price (the reported bug case)', () => {
        const res = resolveSeasonConflicts<PricedSeason>({ start_date: '2026-09-10', end_date: '2026-09-30', daily_price: 10 }, [hauteSept], {});
        expect(res).toEqual([
            { start_date: '2026-09-01', end_date: '2026-09-09', daily_price: 40, name: 'Haute sept' },
            { start_date: '2026-09-10', end_date: '2026-09-30', daily_price: 10 },
        ]);
    });

    it('EXISTING wins when chosen: the existing season keeps the contested days', () => {
        const res = resolveSeasonConflicts<PricedSeason>({ start_date: '2026-09-10', end_date: '2026-09-30', daily_price: 10 }, [hauteSept], {
            [seasonRangeKey(hauteSept)]: 'existing',
        });
        expect(res).toEqual([
            { start_date: '2026-09-01', end_date: '2026-09-15', daily_price: 40, name: 'Haute sept' },
            { start_date: '2026-09-16', end_date: '2026-09-30', daily_price: 10 },
        ]);
    });

    it('SPLITS an existing season when the new one lands in its middle', () => {
        const full = { start_date: '2026-09-01', end_date: '2026-09-30', daily_price: 40, name: 'Sept' };
        const res = resolveSeasonConflicts<PricedSeason>({ start_date: '2026-09-10', end_date: '2026-09-20', daily_price: 10 }, [full], {});
        expect(res).toEqual([
            { start_date: '2026-09-01', end_date: '2026-09-09', daily_price: 40, name: 'Sept' },
            { start_date: '2026-09-10', end_date: '2026-09-20', daily_price: 10 },
            { start_date: '2026-09-21', end_date: '2026-09-30', daily_price: 40, name: 'Sept' },
        ]);
    });

    it('preserves extra_price through trimming', () => {
        const e = { start_date: '2026-09-01', end_date: '2026-09-15', daily_price: 40, extra_price: 5, name: 'E' };
        const res = resolveSeasonConflicts<PricedSeason>({ start_date: '2026-09-10', end_date: '2026-09-30', daily_price: 10, extra_price: 2 }, [e], {});
        expect(res).toEqual([
            { start_date: '2026-09-01', end_date: '2026-09-09', daily_price: 40, extra_price: 5, name: 'E' },
            { start_date: '2026-09-10', end_date: '2026-09-30', daily_price: 10, extra_price: 2 },
        ]);
    });
});

describe('computeGaps', () => {
    it('reproduces the exact spec C example (base price gaps on June 11, 2026)', () => {
        const horizon = addOneYear(TODAY); // 2027-06-11
        const gaps = computeGaps(TODAY, horizon, [
            { start_date: '2026-11-01', end_date: '2026-12-20' }, // Basse
            { start_date: '2026-07-01', end_date: '2026-08-31' }, // Très haute été (unsorted on purpose)
        ]);
        expect(gaps).toEqual([
            { start_date: '2026-06-11', end_date: '2026-06-30' },
            { start_date: '2026-09-01', end_date: '2026-10-31' },
            { start_date: '2026-12-21', end_date: '2027-06-11' },
        ]);
    });

    it('returns the whole window as a single gap when there are no ranges', () => {
        expect(computeGaps(TODAY, '2027-06-11', [])).toEqual([{ start_date: '2026-06-11', end_date: '2027-06-11' }]);
    });

    it('returns no gap when one range covers the whole window', () => {
        expect(computeGaps(TODAY, '2027-06-11', [{ start_date: '2026-01-01', end_date: '2028-01-01' }])).toEqual([]);
    });

    it('merges adjacent ranges instead of emitting an empty gap between them', () => {
        const gaps = computeGaps(TODAY, '2026-09-30', [
            { start_date: '2026-06-11', end_date: '2026-06-30' },
            { start_date: '2026-07-01', end_date: '2026-08-31' }, // adjacent to the previous one
        ]);
        expect(gaps).toEqual([{ start_date: '2026-09-01', end_date: '2026-09-30' }]);
    });

    it('ignores overlapping/contained ranges without producing negative gaps', () => {
        const gaps = computeGaps(TODAY, '2026-12-31', [
            { start_date: '2026-07-01', end_date: '2026-08-31' },
            { start_date: '2026-07-10', end_date: '2026-07-20' }, // contained
            { start_date: '2026-08-15', end_date: '2026-09-15' }, // overlapping
        ]);
        expect(gaps).toEqual([
            { start_date: '2026-06-11', end_date: '2026-06-30' },
            { start_date: '2026-09-16', end_date: '2026-12-31' },
        ]);
    });

    it('clamps ranges that spill outside the window', () => {
        const gaps = computeGaps(TODAY, '2026-12-31', [{ start_date: '2026-01-01', end_date: '2026-06-20' }]);
        expect(gaps).toEqual([{ start_date: '2026-06-21', end_date: '2026-12-31' }]);
    });
});

describe('addOneYear / addDays / daysInRange', () => {
    it('adds one calendar year', () => {
        expect(addOneYear('2026-06-11')).toBe('2027-06-11');
    });

    it('maps Feb 29 to Feb 28 of the next year', () => {
        expect(addOneYear('2028-02-29')).toBe('2029-02-28');
    });

    it('addDays crosses month and year boundaries', () => {
        expect(addDays('2026-12-31', 1)).toBe('2027-01-01');
        expect(addDays('2026-03-01', -1)).toBe('2026-02-28');
    });

    it('daysInRange counts inclusive days', () => {
        expect(daysInRange({ start_date: '2026-06-01', end_date: '2026-06-03' })).toBe(3);
        expect(daysInRange({ start_date: '2026-06-11', end_date: '2026-06-11' })).toBe(1);
    });
});
