import { parseIcs, selectUpcoming } from '../src/features/calendar/icsParser';

describe('parseIcs', () => {
    test('returns empty array on invalid / empty input', () => {
        expect(parseIcs('')).toEqual([]);
        expect(parseIcs(null as any)).toEqual([]);
        expect(parseIcs(undefined as any)).toEqual([]);
        expect(parseIcs('random text no VEVENT')).toEqual([]);
    });

    test('parses single timed event', () => {
        const ics = [
            'BEGIN:VCALENDAR',
            'BEGIN:VEVENT',
            'SUMMARY:팀 미팅',
            'DTSTART:20260520T130000Z',
            'DTEND:20260520T140000Z',
            'LOCATION:회의실 A',
            'END:VEVENT',
            'END:VCALENDAR',
        ].join('\r\n');
        const events = parseIcs(ics);
        expect(events).toHaveLength(1);
        expect(events[0].summary).toBe('팀 미팅');
        expect(events[0].location).toBe('회의실 A');
        expect(events[0].allDay).toBe(false);
        // 2026-05-20 13:00 UTC
        expect(events[0].start.getUTCFullYear()).toBe(2026);
        expect(events[0].start.getUTCMonth()).toBe(4);
        expect(events[0].start.getUTCDate()).toBe(20);
        expect(events[0].start.getUTCHours()).toBe(13);
        expect(events[0].end?.getUTCHours()).toBe(14);
    });

    test('parses all-day event via VALUE=DATE', () => {
        const ics = [
            'BEGIN:VEVENT',
            'SUMMARY:생일',
            'DTSTART;VALUE=DATE:20260615',
            'DTEND;VALUE=DATE:20260616',
            'END:VEVENT',
        ].join('\n');
        const events = parseIcs(ics);
        expect(events).toHaveLength(1);
        expect(events[0].allDay).toBe(true);
        expect(events[0].start.getFullYear()).toBe(2026);
        expect(events[0].start.getMonth()).toBe(5);
        expect(events[0].start.getDate()).toBe(15);
    });

    test('unfolds line continuations (RFC 5545)', () => {
        // ICS 75자 wrap — 다음 줄이 공백/탭으로 시작하면 같은 필드의 연속.
        const ics = [
            'BEGIN:VEVENT',
            'SUMMARY:긴 제목 첫 부분',
            ' 두번째 줄 이어짐',
            'DTSTART:20260520T130000Z',
            'END:VEVENT',
        ].join('\r\n');
        const events = parseIcs(ics);
        expect(events[0].summary).toBe('긴 제목 첫 부분두번째 줄 이어짐');
    });

    test('unescapes ICS escape sequences in summary / location', () => {
        const ics = [
            'BEGIN:VEVENT',
            'SUMMARY:1\\, 2\\, 3 — 회의\\nFollow-up',
            'LOCATION:Zoom\\, 본관 3층',
            'DTSTART:20260520T130000Z',
            'END:VEVENT',
        ].join('\n');
        const events = parseIcs(ics);
        expect(events[0].summary).toBe('1, 2, 3 — 회의 Follow-up');
        expect(events[0].location).toBe('Zoom, 본관 3층');
    });

    test('parses multiple VEVENTs and gives default summary when missing', () => {
        const ics = [
            'BEGIN:VEVENT',
            'DTSTART:20260520T130000Z',
            'END:VEVENT',
            'BEGIN:VEVENT',
            'SUMMARY:두번째',
            'DTSTART:20260521T130000Z',
            'END:VEVENT',
        ].join('\n');
        const events = parseIcs(ics);
        expect(events).toHaveLength(2);
        expect(events[0].summary).toBe('(제목 없음)');
        expect(events[1].summary).toBe('두번째');
    });

    test('drops VEVENT without DTSTART', () => {
        const ics = [
            'BEGIN:VEVENT',
            'SUMMARY:DTSTART 누락',
            'END:VEVENT',
        ].join('\n');
        expect(parseIcs(ics)).toEqual([]);
    });
});

describe('selectUpcoming', () => {
    const now = new Date('2026-05-20T12:00:00Z');
    const mkEv = (offsetMin: number, label = 'evt'): any => ({
        start: new Date(now.getTime() + offsetMin * 60 * 1000),
        end: undefined, summary: label, location: '', description: '', allDay: false,
    });

    test('filters past events older than 1 hour', () => {
        const events = [
            mkEv(-120, 'old'),    // 2 hours ago — dropped
            mkEv(-30, 'recent'),  // 30 min ago — kept (within 1h window)
            mkEv(60, 'soon'),     // 1 hour future — kept
        ];
        const upcoming = selectUpcoming(events, 14, now);
        expect(upcoming.map((e) => e.summary)).toEqual(['recent', 'soon']);
    });

    test('filters events beyond cutoff days', () => {
        const events = [
            mkEv(60, 'soon'),
            mkEv(60 * 24 * 15, 'too-far'), // 15 days — beyond 14-day cutoff
        ];
        const upcoming = selectUpcoming(events, 14, now);
        expect(upcoming.map((e) => e.summary)).toEqual(['soon']);
    });

    test('sorts upcoming events by start time', () => {
        const events = [mkEv(180, 'c'), mkEv(60, 'a'), mkEv(120, 'b')];
        const upcoming = selectUpcoming(events, 14, now);
        expect(upcoming.map((e) => e.summary)).toEqual(['a', 'b', 'c']);
    });
});
