/**
 * RED test scaffold: broker/vevent.ts — VEVENT builder (CAL-04, CAL-07)
 *
 * Behaviors under test:
 *   1. buildVeventString produces a VCALENDAR with a VEVENT for a timed event
 *      with DTSTART using UTC 'Z' suffix (D-13 contract — no TZID)
 *   2. buildVeventString produces a VCALENDAR with DTSTART as a DATE value
 *      (no time component, no TZID) for all-day events (D-13, Pitfall 3)
 *   3. buildVeventString with rruleString produces a VCALENDAR with an RRULE property (CAL-07)
 *
 * These tests FAIL (RED) because broker/vevent.ts does not exist yet.
 * They will turn GREEN in Plan 03-02 when the implementation is added.
 */

import { describe, it, expect } from 'vitest';

// This import fails (RED) — broker/vevent.ts does not exist yet.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore intentional RED import
import { buildVeventString } from '../../src/broker/vevent.js';

describe('buildVeventString', () => {
  it('produces a VCALENDAR string containing a VEVENT for a timed event', () => {
    const result = buildVeventString({
      summary: 'Team standup',
      allDay: false,
      dtstart: new Date('2026-06-10T09:00:00Z'),
      dtend: new Date('2026-06-10T09:30:00Z'),
    });

    expect(result).toHaveProperty('uid');
    expect(result).toHaveProperty('icsString');
    expect(result.icsString).toContain('BEGIN:VCALENDAR');
    expect(result.icsString).toContain('BEGIN:VEVENT');
    expect(result.icsString).toContain('SUMMARY:Team standup');
    expect(result.icsString).toContain('END:VEVENT');
    expect(result.icsString).toContain('END:VCALENDAR');
  });

  it('produces DTSTART with Z suffix (UTC) for a timed event — not TZID', () => {
    const result = buildVeventString({
      summary: 'Morning meeting',
      allDay: false,
      dtstart: new Date('2026-06-10T14:00:00Z'),
      dtend: new Date('2026-06-10T15:00:00Z'),
    });

    // D-13 timed contract: DATETIME in UTC → 'Z' suffix, no TZID
    expect(result.icsString).toMatch(/DTSTART:\d{8}T\d{6}Z/);
    expect(result.icsString).not.toMatch(/DTSTART;TZID=/);
  });

  it('produces DTSTART as DATE (no time, no TZID) for an all-day event', () => {
    const result = buildVeventString({
      summary: 'Birthday',
      allDay: true,
      dtstart: '2026-06-15',
      dtend: '2026-06-16',
    });

    // D-13 all-day contract: VALUE=DATE, no time component, no TZID
    // ical.js represents DATE as DTSTART;VALUE=DATE:YYYYMMDD
    expect(result.icsString).toMatch(/DTSTART[^:]*:20260615/);
    // Must NOT contain a time component (no 'T' after the date)
    expect(result.icsString).not.toMatch(/DTSTART[^:]*:20260615T/);
    // Must NOT contain TZID on the DTSTART property
    expect(result.icsString).not.toMatch(/DTSTART;TZID=/);
  });

  it('includes an RRULE property when rruleString is provided (CAL-07)', () => {
    const result = buildVeventString({
      summary: 'Weekly sync',
      allDay: false,
      dtstart: new Date('2026-06-09T10:00:00Z'),
      dtend: new Date('2026-06-09T11:00:00Z'),
      rruleString: 'FREQ=WEEKLY;BYDAY=MO',
    });

    expect(result.icsString).toContain('RRULE:FREQ=WEEKLY;BYDAY=MO');
  });

  it('does NOT include RRULE when rruleString is omitted', () => {
    const result = buildVeventString({
      summary: 'One-off lunch',
      allDay: false,
      dtstart: new Date('2026-06-10T12:00:00Z'),
      dtend: new Date('2026-06-10T13:00:00Z'),
    });

    expect(result.icsString).not.toContain('RRULE:');
  });

  // D-06: RRULE COUNT — verified ical.js 2.2.1 output
  it('serializes COUNT in RRULE for a timed event (D-06)', () => {
    const result = buildVeventString({
      summary: 'Weekly',
      allDay: false,
      dtstart: new Date('2026-06-10T09:00:00Z'),
      dtend: new Date('2026-06-10T10:00:00Z'),
      rruleString: 'FREQ=WEEKLY;COUNT=5',
    });
    expect(result.icsString).toContain('RRULE:FREQ=WEEKLY;COUNT=5');
  });

  // D-06: RRULE UNTIL DATE form — all-day event must NOT contain T235959Z
  it('serializes UNTIL as DATE form for all-day events (D-06)', () => {
    const result = buildVeventString({
      summary: 'Daily standup',
      allDay: true,
      dtstart: '2026-06-10',
      dtend: '2026-06-11',
      rruleString: 'FREQ=DAILY;UNTIL=20260630',
    });
    expect(result.icsString).toContain('RRULE:FREQ=DAILY;UNTIL=20260630');
    expect(result.icsString).not.toContain('T235959Z');
  });

  // D-06: RRULE UNTIL DATETIME UTC form — timed event
  it('serializes UNTIL as DATETIME UTC form for timed events (D-06)', () => {
    const result = buildVeventString({
      summary: 'Weekly',
      allDay: false,
      dtstart: new Date('2026-06-10T09:00:00Z'),
      dtend: new Date('2026-06-10T10:00:00Z'),
      rruleString: 'FREQ=WEEKLY;UNTIL=20260630T235959Z',
    });
    expect(result.icsString).toContain('RRULE:FREQ=WEEKLY;UNTIL=20260630T235959Z');
  });

  it('uses the provided uid when given', () => {
    const uid = 'custom-uid-001@familysync';
    const result = buildVeventString({
      uid,
      summary: 'Test event',
      allDay: false,
      dtstart: new Date('2026-06-10T09:00:00Z'),
      dtend: new Date('2026-06-10T10:00:00Z'),
    });

    expect(result.uid).toBe(uid);
    expect(result.icsString).toContain(`UID:${uid}`);
  });

  it('generates a uid when none is provided', () => {
    const result = buildVeventString({
      summary: 'Auto uid event',
      allDay: false,
      dtstart: new Date('2026-06-10T09:00:00Z'),
      dtend: new Date('2026-06-10T10:00:00Z'),
    });

    expect(result.uid).toBeTruthy();
    expect(result.uid.length).toBeGreaterThan(10);
  });
});

describe('buildVeventString — D-13 form-parsed contract', () => {
  it('timed event: produces BEGIN:VCALENDAR, SUMMARY, UID, timed DTSTART (Z suffix), and DTEND', () => {
    // Simulates the field shape parsed by the worker from the stored form JSON
    const result = buildVeventString({
      uid: 'u1@familysync',
      summary: 'Lunch',
      allDay: false,
      dtstart: new Date('2026-06-10T12:00:00Z'),
      dtend: new Date('2026-06-10T13:00:00Z'),
    });

    expect(result.icsString).toContain('BEGIN:VCALENDAR');
    expect(result.icsString).toContain('SUMMARY:Lunch');
    expect(result.icsString).toContain('UID:u1@familysync');
    // D-13 timed contract: DATETIME in UTC → 'Z' suffix, no TZID
    expect(result.icsString).toMatch(/DTSTART:\d{8}T\d{6}Z/);
    // DTEND must be present
    expect(result.icsString).toMatch(/DTEND:\d{8}T\d{6}Z/);
  });

  it('single-day all-day event: DTSTART is DATE format and DTEND = DTSTART + 1 day (RFC-5545 exclusive end, WR-04)', () => {
    // Simulates a single-day all-day event where start and end are the same calendar day.
    // WR-04: the outbox worker passes the user-entered inclusive end; vevent.ts must advance it.
    const result = buildVeventString({
      summary: 'Birthday',
      allDay: true,
      dtstart: '2026-06-10',
      dtend: '2026-06-10',
    });

    // DTSTART must be DATE format (no time component, no TZID) — D-13 all-day contract
    expect(result.icsString).toMatch(/DTSTART[^:]*:20260610/);
    expect(result.icsString).not.toMatch(/DTSTART[^:]*:20260610T/);

    // WR-04: DTEND must be DTSTART + 1 day (RFC-5545 exclusive end)
    expect(result.icsString).toMatch(/DTEND[^:]*:20260611/);
    // DTEND date string must NOT equal DTSTART date string (owning-boundary assertion)
    const dtendMatch = result.icsString.match(/DTEND[^:]*:(\d{8})/);
    const dtstartMatch = result.icsString.match(/DTSTART[^:]*:(\d{8})/);
    expect(dtendMatch?.[1]).not.toBe(dtstartMatch?.[1]);
  });
});
