import { describe, it, expect } from 'vitest';
import { toIcs } from './ics';

const utc = (y: number, mo: number, d: number, h = 0, mi = 0) =>
  Date.UTC(y, mo, d, h, mi, 0, 0);

describe('toIcs', () => {
  it('produces a valid empty calendar for []', () => {
    const ics = toIcs([]);
    expect(ics).toContain('BEGIN:VCALENDAR');
    expect(ics).toContain('VERSION:2.0');
    expect(ics).toContain('END:VCALENDAR');
    expect(ics).not.toContain('BEGIN:VEVENT');
  });

  it('uses CRLF line endings', () => {
    const ics = toIcs([]);
    expect(ics).toContain('\r\n');
    expect(ics.endsWith('\r\n')).toBe(true);
    // No bare LF without a preceding CR.
    expect(/[^\r]\n/.test(ics)).toBe(false);
  });

  it('emits an all-day VEVENT with VALUE=DATE', () => {
    const ics = toIcs([
      { title: 'Final exam', dateMs: utc(2026, 5, 20), allDay: true },
    ]);
    expect(ics).toContain('BEGIN:VEVENT');
    expect(ics).toContain('DTSTART;VALUE=DATE:20260620');
    expect(ics).toContain('SUMMARY:Final exam');
  });

  it('emits a timed VEVENT with a UTC DTSTART', () => {
    const ics = toIcs([
      { title: 'Meeting', dateMs: utc(2026, 5, 20, 14, 30), allDay: false },
    ]);
    expect(ics).toContain('DTSTART:20260620T143000Z');
  });

  it('derives DTSTAMP from the event dateMs', () => {
    const ics = toIcs([
      { title: 'X', dateMs: utc(2026, 5, 20, 9, 0), allDay: true },
    ]);
    expect(ics).toContain('DTSTAMP:20260620T090000Z');
  });

  it('UID is deterministic (no randomness) and stable across calls', () => {
    const ev = { title: 'Exam', dateMs: utc(2026, 5, 20), allDay: true };
    const a = toIcs([ev]);
    const b = toIcs([ev]);
    expect(a).toBe(b);
    const uid = a.split('\r\n').find((l) => l.startsWith('UID:'));
    expect(uid).toBeDefined();
    expect(uid).toContain('@wisp');
  });

  it('changes UID when title or date changes', () => {
    const a = toIcs([{ title: 'A', dateMs: utc(2026, 5, 20), allDay: true }]);
    const b = toIcs([{ title: 'B', dateMs: utc(2026, 5, 20), allDay: true }]);
    const uidA = a.split('\r\n').find((l) => l.startsWith('UID:'));
    const uidB = b.split('\r\n').find((l) => l.startsWith('UID:'));
    expect(uidA).not.toBe(uidB);
  });

  it('escapes comma, semicolon, backslash and newline in SUMMARY', () => {
    const ics = toIcs([
      {
        title: 'a, b; c \\ d\ne',
        dateMs: utc(2026, 5, 20),
        allDay: true,
      },
    ]);
    const line = ics
      .replace(/\r\n /g, '') // unfold first
      .split('\r\n')
      .find((l) => l.startsWith('SUMMARY:'));
    expect(line).toBe('SUMMARY:a\\, b\\; c \\\\ d\\ne');
  });

  it('folds lines longer than 75 octets with a leading space', () => {
    const long = 'X'.repeat(200);
    const ics = toIcs([{ title: long, dateMs: utc(2026, 5, 20), allDay: true }]);
    for (const line of ics.split('\r\n')) {
      // Each physical line must be <=75 octets.
      expect(Buffer.byteLength(line, 'utf8')).toBeLessThanOrEqual(75);
    }
    // Unfolding restores the SUMMARY content.
    const unfolded = ics.replace(/\r\n /g, '');
    expect(unfolded).toContain(`SUMMARY:${long}`);
  });

  it('emits one VEVENT per event', () => {
    const ics = toIcs([
      { title: 'A', dateMs: utc(2026, 5, 20), allDay: true },
      { title: 'B', dateMs: utc(2026, 5, 21), allDay: true },
    ]);
    const count = ics.split('BEGIN:VEVENT').length - 1;
    expect(count).toBe(2);
  });
});
