import { CalendarEvent } from "@calchat/shared";
import { expandRecurringEvents } from "./recurrenceExpander";

// Helper: create a CalendarEvent with sensible defaults
function makeEvent(
  overrides: Partial<CalendarEvent> & { startTime: Date; endTime: Date },
): CalendarEvent {
  return {
    id: "evt-1",
    userId: "user-1",
    title: "Test Event",
    ...overrides,
  };
}

// Helper: create a date from "YYYY-MM-DD HH:mm" (local time)
function d(dateStr: string): Date {
  const [datePart, timePart] = dateStr.split(" ");
  const [y, m, day] = datePart.split("-").map(Number);
  if (timePart) {
    const [h, min] = timePart.split(":").map(Number);
    return new Date(y, m - 1, day, h, min);
  }
  return new Date(y, m - 1, day);
}

describe("expandRecurringEvents", () => {
  // Range: 2025-06-01 to 2025-06-30
  const rangeStart = d("2025-06-01 00:00");
  const rangeEnd = d("2025-06-30 23:59");

  describe("non-recurring events", () => {
    it("returns event within the range", () => {
      const event = makeEvent({
        startTime: d("2025-06-10 09:00"),
        endTime: d("2025-06-10 10:00"),
      });

      const result = expandRecurringEvents([event], rangeStart, rangeEnd);

      expect(result).toHaveLength(1);
      expect(result[0].occurrenceStart).toEqual(d("2025-06-10 09:00"));
      expect(result[0].occurrenceEnd).toEqual(d("2025-06-10 10:00"));
    });

    it("excludes event outside the range", () => {
      const event = makeEvent({
        startTime: d("2025-07-05 09:00"),
        endTime: d("2025-07-05 10:00"),
      });

      const result = expandRecurringEvents([event], rangeStart, rangeEnd);

      expect(result).toHaveLength(0);
    });

    it("includes event that starts before range and ends within", () => {
      const event = makeEvent({
        startTime: d("2025-05-31 22:00"),
        endTime: d("2025-06-01 02:00"),
      });

      const result = expandRecurringEvents([event], rangeStart, rangeEnd);

      expect(result).toHaveLength(1);
    });

    it("includes event that spans the entire range", () => {
      const event = makeEvent({
        startTime: d("2025-05-01 00:00"),
        endTime: d("2025-07-31 23:59"),
      });

      const result = expandRecurringEvents([event], rangeStart, rangeEnd);

      expect(result).toHaveLength(1);
    });

    it("returns empty array for empty input", () => {
      const result = expandRecurringEvents([], rangeStart, rangeEnd);

      expect(result).toEqual([]);
    });
  });

  describe("recurring events", () => {
    it("expands weekly event to all occurrences in range", () => {
      // Weekly on Mondays, starting 2025-06-02 (a Monday)
      const event = makeEvent({
        startTime: d("2025-06-02 10:00"),
        endTime: d("2025-06-02 11:00"),
        recurrenceRule: "FREQ=WEEKLY;BYDAY=MO",
      });

      const result = expandRecurringEvents([event], rangeStart, rangeEnd);

      // Mondays in June 2025: 2, 9, 16, 23, 30
      expect(result).toHaveLength(5);
      expect(result[0].occurrenceStart).toEqual(d("2025-06-02 10:00"));
      expect(result[1].occurrenceStart).toEqual(d("2025-06-09 10:00"));
      expect(result[2].occurrenceStart).toEqual(d("2025-06-16 10:00"));
      expect(result[3].occurrenceStart).toEqual(d("2025-06-23 10:00"));
      expect(result[4].occurrenceStart).toEqual(d("2025-06-30 10:00"));
    });

    it("daily event with UNTIL stops at the right date", () => {
      const event = makeEvent({
        startTime: d("2025-06-01 08:00"),
        endTime: d("2025-06-01 09:00"),
        recurrenceRule: "FREQ=DAILY;UNTIL=20250605T235959",
      });

      const result = expandRecurringEvents([event], rangeStart, rangeEnd);

      // June 1, 2, 3, 4, 5
      expect(result).toHaveLength(5);
      expect(result[4].occurrenceStart).toEqual(d("2025-06-05 08:00"));
    });

    it("skips occurrences on exception dates (EXDATE)", () => {
      const event = makeEvent({
        startTime: d("2025-06-02 10:00"),
        endTime: d("2025-06-02 11:00"),
        recurrenceRule: "FREQ=WEEKLY;BYDAY=MO",
        exceptionDates: ["2025-06-09", "2025-06-23"],
      });

      const result = expandRecurringEvents([event], rangeStart, rangeEnd);

      // 5 Mondays minus 2 exceptions = 3
      expect(result).toHaveLength(3);
      const dates = result.map((r) => r.occurrenceStart.getDate());
      expect(dates).toEqual([2, 16, 30]);
    });

    it("handles RRULE: prefix (strips it)", () => {
      const event = makeEvent({
        startTime: d("2025-06-01 08:00"),
        endTime: d("2025-06-01 09:00"),
        recurrenceRule: "RRULE:FREQ=DAILY;COUNT=3",
      });

      const result = expandRecurringEvents([event], rangeStart, rangeEnd);

      expect(result).toHaveLength(3);
    });

    it("falls back to single occurrence on invalid RRULE", () => {
      const consoleSpy = jest.spyOn(console, "error").mockImplementation();

      const event = makeEvent({
        startTime: d("2025-06-10 09:00"),
        endTime: d("2025-06-10 10:00"),
        recurrenceRule: "COMPLETELY_INVALID_RULE",
      });

      const result = expandRecurringEvents([event], rangeStart, rangeEnd);

      expect(result).toHaveLength(1);
      expect(result[0].occurrenceStart).toEqual(d("2025-06-10 09:00"));
      expect(consoleSpy).toHaveBeenCalled();

      consoleSpy.mockRestore();
    });
  });

  describe("multi-day events", () => {
    it("finds event starting before range that ends within range", () => {
      // 3-day recurring event starting May 15, weekly
      const event = makeEvent({
        startTime: d("2025-05-15 08:00"),
        endTime: d("2025-05-18 08:00"),
        recurrenceRule: "FREQ=WEEKLY",
      });

      // The occurrence starting May 29 ends June 1 → overlaps with range
      const result = expandRecurringEvents([event], rangeStart, rangeEnd);

      const starts = result.map((r) => r.occurrenceStart.getDate());
      // May 29 (ends June 1), June 5, 12, 19, 26
      expect(starts).toContain(29); // May 29
    });
  });

  describe("sorting", () => {
    it("returns events sorted by occurrenceStart", () => {
      const laterEvent = makeEvent({
        id: "evt-later",
        startTime: d("2025-06-20 14:00"),
        endTime: d("2025-06-20 15:00"),
      });
      const earlierEvent = makeEvent({
        id: "evt-earlier",
        startTime: d("2025-06-05 09:00"),
        endTime: d("2025-06-05 10:00"),
      });

      // Pass in reverse order
      const result = expandRecurringEvents(
        [laterEvent, earlierEvent],
        rangeStart,
        rangeEnd,
      );

      expect(result).toHaveLength(2);
      expect(result[0].id).toBe("evt-earlier");
      expect(result[1].id).toBe("evt-later");
    });
  });
});
