import {
    parseLimit,
    parseInput,
    parseISO,
    createDateRange,
    createMonthRange,
    formatStartDate,
    dateToString,
    formatInputDate,
    addDays,
    differenceInDays,
    getCalendarHeight,
    parseDateOptions,
} from '../utils';
import { CUSTOM_VALUE } from '../constants';
import type { DatePickerOptions } from '../types';

describe('parseLimit', () => {
    test('parses valid limits correctly', () => {
        expect(parseLimit('2022-01-01', '2022-06-30')).toStrictEqual({
            start: new Date(Date.UTC(2022, 0, 1)),
            end: new Date(Date.UTC(2022, 5, 30)),
        });
    });

    test('defaults to start of last year to end of current when limits are invalid', () => {
        const currentYear = new Date().getUTCFullYear();
        expect(parseLimit('invalid', 'invalid')).toStrictEqual({
            start: new Date(Date.UTC(currentYear - 1, 0, 1)),
            end: new Date(Date.UTC(currentYear, 11, 31)),
        });
    });
});

describe('parseInput', () => {
    test.each([
        ['22/01/01', [2022, 0, 1]],
        ['22/06/30', [2022, 5, 30]],
        ['1922/01/01', [1922, 0, 1]],
        ['2080/06/30', [2080, 5, 30]],
        ['22-01-01', [2022, 0, 1]],
        ['22-06-30', [2022, 5, 30]],
        ['1922-01-01', [1922, 0, 1]],
        ['2080-06-30', [2080, 5, 30]],
    ])('parses valid date "%s" correctly', (input, output) => {
        expect(parseInput(input)?.getTime()).toEqual(
            new Date(Date.UTC(output[0], output[1], output[2])).getTime()
        );
    });

    test.each(['invalid', '06/30', '01-12-2023'])(
        'returns undefined when given invalid date: "%s"',
        (date) => {
            expect(parseInput(date)).toBe(undefined);
        }
    );
});

describe('parseISO', () => {
    test('parses valid dates correctly', () => {
        expect(parseISO('2022-01-01')?.getTime()).toEqual(new Date(Date.UTC(2022, 0, 1)).getTime());
        expect(parseISO('2022-06-30')?.getTime()).toEqual(
            new Date(Date.UTC(2022, 5, 30)).getTime()
        );
    });

    test('returns undefined when dates are invalid', () => {
        expect(parseISO('invalid')).toBe(undefined);
        expect(parseISO('06/30')).toEqual(undefined);
    });
});

describe('formatStartDate', () => {
    [
        {
            start: new Date(Date.UTC(2022, 1, 28)),
            end: new Date(Date.UTC(2022, 2, 31)),
            result: 'Feb 28',
        },
        {
            start: new Date(Date.UTC(2021, 1, 28)),
            end: new Date(Date.UTC(2022, 2, 31)),
            result: 'Feb 28, 2021',
        },
        {
            start: new Date(Date.UTC(2022, 11, 31)),
            end: new Date(Date.UTC(2023, 2, 31)),
            result: 'Dec 31, 2022',
        },
    ].forEach(({ start, end, result }) => {
        test(`${start.toUTCString()} - ${end.toUTCString()}`, () => {
            expect(formatStartDate(start, end, 'en')).toBe(result);
        });
    });
});

describe('createDateRange', () => {
    [
        { year: 2022, month: 0, count: 31 },
        { year: 2022, month: 1, count: 28 },
        { year: 2024, month: 1, count: 29 },
    ].forEach(({ year, month, count }) => {
        test(`${year}-${month}`, () => {
            const result = createDateRange(year, month);
            expect(result.length).toBe(count);
            expect(result[0].getUTCFullYear()).toBe(year);
            expect(result[0].getUTCMonth()).toBe(month);
            expect(result[0].getUTCDate()).toBe(1);
        });
    });
});

describe('createMonthRange', () => {
    const values = [
        [
            {
                start: new Date(Date.UTC(2022, 0, 1)),
                end: new Date(Date.UTC(2022, 0, 31)),
            },
            [{ year: 2022, month: 0 }],
        ],
        [
            {
                start: new Date(Date.UTC(2022, 0, 31)),
                end: new Date(Date.UTC(2022, 1, 28)),
            },
            [
                { year: 2022, month: 0 },
                { year: 2022, month: 1 },
            ],
        ],
        [
            {
                start: new Date(Date.UTC(2021, 11, 31)),
                end: new Date(Date.UTC(2022, 0, 1)),
            },
            [
                { year: 2021, month: 11 },
                { year: 2022, month: 0 },
            ],
        ],
    ] as const;

    values.forEach(([limit, result]) => {
        test(`${limit.start.toUTCString()} - ${limit.end.toUTCString()}`, () => {
            expect(createMonthRange(limit)).toStrictEqual(result);
        });
    });
});

describe('toDateString', () => {
    test('outputs correct date strings', () => {
        expect(dateToString(new Date(Date.UTC(2022, 1, 28)))).toBe('2022-02-28');
        expect(dateToString(new Date(Date.UTC(2021, 11, 31)))).toBe('2021-12-31');
    });
});

describe('formatInputDate', () => {
    test('formats correctly', () => {
        expect(formatInputDate(new Date(Date.UTC(2022, 1, 28)))).toEqual('2022/02/28');
        expect(formatInputDate(new Date(Date.UTC(1999, 11, 31)))).toEqual('1999/12/31');
    });
});

describe('addDays', () => {
    test('returns date with expected date difference', () => {
        expect(addDays(new Date(Date.UTC(2022, 1, 28)), 31)).toEqual(
            new Date(Date.UTC(2022, 2, 31))
        );
        expect(addDays(new Date(Date.UTC(1999, 0, 1)), 7)).toEqual(new Date(Date.UTC(1999, 0, 8)));
        expect(addDays(new Date(Date.UTC(1999, 11, 31)), 365)).toEqual(
            new Date(Date.UTC(2000, 11, 30))
        );
    });
});

describe('differenceInDays', () => {
    test('returns correct number of date difference', () => {
        const date1 = new Date(Date.UTC(2022, 1, 28));
        const date2 = new Date(Date.UTC(1999, 11, 31));
        const date3 = new Date(Date.UTC(2023, 4, 1));
        const date4 = new Date(Date.UTC(2023, 4, 10));
        expect(differenceInDays(date1, date2)).toEqual(8096);
        expect(differenceInDays(date1, date3)).toEqual(428);
        expect(differenceInDays(date2, date3)).toEqual(8523);
        expect(differenceInDays(date3, date4)).toEqual(10);
    });
});

describe('getCalendarHeight', () => {
    const heights = [
        [0, 220],
        [1, 184],
        [2, 220],
        [3, 220],
        [4, 256],
        [5, 220],
        [6, 220],
        [7, 256],
        [8, 220],
        [9, 220],
        [10, 220],
        [11, 220],
    ];
    test.each(heights)('2021, month: %d', (index, res) => {
        expect(getCalendarHeight(2021, index)).toBe(res);
    });
});

describe('parseDateOptions', () => {
    const baseOptions: DatePickerOptions = [
        {
            value: 'last-7-days',
            label: 'Last 7 days',
            start: '2022-01-01',
            end: '2022-01-07',
        },
        {
            value: 'last-30-days',
            label: 'Last 30 days',
            start: '2022-01-01',
            end: '2022-01-30',
        },
    ];

    test('finds and parses existing option by value', () => {
        const result = parseDateOptions(baseOptions, { value: 'last-7-days' });

        expect(result).toBeDefined();
        expect(result?.value).toBe('last-7-days');
        expect(result?.label).toBe('Last 7 days');
        expect(result?.start).toEqual(new Date(Date.UTC(2022, 0, 1)));
        expect(result?.end).toEqual(new Date(Date.UTC(2022, 0, 7)));
    });

    test('handles mixed options correctly', () => {
        const optionsWithMixed: DatePickerOptions = [
            {
                value: 'mixed-range',
                label: 'Mixed Range',
                start: '2022-01-01',
                end: '2022-01-31',
                mixed: true,
            },
        ];

        const result = parseDateOptions(optionsWithMixed, { value: 'mixed-range' });

        expect(result).toBeDefined();
        expect(result?.value).toBe('mixed-range');
        expect(result?.label).toBe('Mixed Range');
        expect(result?.mixed).toBe(true);
        expect(result?.start).toBeUndefined();
        expect(result?.end).toBeUndefined();
    });

    test('creates custom option without label (value is CUSTOM_VALUE)', () => {
        const result = parseDateOptions(baseOptions, {
            start: '2022-02-01',
            end: '2022-02-15',
        });

        expect(result).toBeDefined();
        expect(result?.value).toBe(CUSTOM_VALUE);
        expect(result?.label).toBeUndefined();
        expect(result?.start).toEqual(new Date(Date.UTC(2022, 1, 1)));
        expect(result?.end).toEqual(new Date(Date.UTC(2022, 1, 15)));
    });

    test('preserves label property from existing options', () => {
        const optionsWithLabel: DatePickerOptions = [
            {
                value: 'custom-q1',
                label: 'Q1 2022',
                start: '2022-01-01',
                end: '2022-03-31',
            },
        ];

        // Select from existing options - label should be preserved
        const selectedResult = parseDateOptions(optionsWithLabel, { value: 'custom-q1' });
        expect(selectedResult?.label).toBe('Q1 2022');
    });

    test('handles custom dates without labels (backward compatibility)', () => {
        const result = parseDateOptions(baseOptions, {
            start: '2022-05-01',
            end: '2022-05-31',
        });

        expect(result).toBeDefined();
        expect(result?.value).toBe(CUSTOM_VALUE);
        expect(result?.label).toBeUndefined();
        expect(result?.start).toEqual(new Date(Date.UTC(2022, 4, 1)));
        expect(result?.end).toEqual(new Date(Date.UTC(2022, 4, 31)));
    });

    test('handles custom dates with empty end date', () => {
        const result = parseDateOptions(baseOptions, {
            start: '2022-06-01',
        });

        expect(result).toBeDefined();
        expect(result?.value).toBe(CUSTOM_VALUE);
        expect(result?.start).toEqual(new Date(Date.UTC(2022, 5, 1)));
        expect(result?.end).toBeUndefined();
    });

    test('returns undefined when option not found and no start date provided', () => {
        const result = parseDateOptions(baseOptions, { value: 'non-existent' });

        expect(result).toBeUndefined();
    });

    test('handles options in groups', () => {
        const groupedOptions: DatePickerOptions = [
            {
                label: 'Presets',
                options: [
                    {
                        value: 'preset-1',
                        label: 'Preset 1',
                        start: '2022-01-01',
                        end: '2022-01-07',
                    },
                ],
            },
        ];

        const result = parseDateOptions(groupedOptions, { value: 'preset-1' });

        expect(result).toBeDefined();
        expect(result?.value).toBe('preset-1');
        expect(result?.label).toBe('Preset 1');
    });

    test('handles mixed flag in custom options', () => {
        const result = parseDateOptions(baseOptions, {
            start: '2022-08-01',
            end: '2022-08-31',
            mixed: true,
        });

        expect(result).toBeDefined();
        expect(result?.mixed).toBe(true);
        expect(result?.value).toBe(CUSTOM_VALUE);
    });
});
