import { describe, expect, test } from "vitest";
import { dueSlot, isValidTimezone, mostRecentSlot, nextSlot, type Schedule } from "@/lib/schedule";

const at = (iso: string) => new Date(iso);
const iso = (date: Date | null) => date?.toISOString() ?? null;

const ist: Schedule = { frequency: "DAILY", checkTime: "09:00", timezone: "Asia/Kolkata" };
const newYork: Schedule = { frequency: "DAILY", checkTime: "09:00", timezone: "America/New_York" };

describe("daily schedule in Asia/Kolkata (UTC+5:30)", () => {
  test("before 09:00 the most recent slot is yesterday's", () => {
    expect(iso(mostRecentSlot(ist, at("2026-09-17T03:29:59Z")))).toBe("2026-09-16T03:30:00.000Z");
  });
  test("at exactly 09:00 the slot is today's", () => {
    expect(iso(mostRecentSlot(ist, at("2026-09-17T03:30:00Z")))).toBe("2026-09-17T03:30:00.000Z");
  });
  test("next slot is strictly in the future", () => {
    expect(iso(nextSlot(ist, at("2026-09-17T03:30:00Z")))).toBe("2026-09-18T03:30:00.000Z");
  });
  test("works when the local date differs from the UTC date", () => {
    expect(iso(mostRecentSlot({ ...ist, checkTime: "00:15" }, at("2026-09-16T19:00:00Z")))).toBe(
      "2026-09-16T18:45:00.000Z",
    );
  });
  test("month and year boundaries", () => {
    expect(iso(mostRecentSlot(ist, at("2026-10-01T01:00:00Z")))).toBe("2026-09-30T03:30:00.000Z");
    expect(iso(nextSlot({ ...ist, checkTime: "23:30" }, at("2026-12-31T18:30:00Z")))).toBe("2027-01-01T18:00:00.000Z");
  });
});

describe("future frequencies need no new code", () => {
  test("every 6 hours, anchored at the check time", () => {
    const six: Schedule = { ...ist, frequency: "EVERY_6_HOURS" };
    expect(iso(mostRecentSlot(six, at("2026-09-17T10:30:00Z")))).toBe("2026-09-17T09:30:00.000Z"); // 15:00 IST
    expect(iso(mostRecentSlot(six, at("2026-09-16T20:30:00Z")))).toBe("2026-09-16T15:30:00.000Z"); // 21:00 IST, previous day
    expect(iso(nextSlot(six, at("2026-09-17T16:00:00Z")))).toBe("2026-09-17T21:30:00.000Z"); // 03:00 IST
  });
  test("hourly and every 12 hours", () => {
    expect(iso(mostRecentSlot({ ...ist, frequency: "HOURLY" }, at("2026-09-17T10:44:00Z")))).toBe("2026-09-17T10:30:00.000Z");
    expect(iso(mostRecentSlot({ ...ist, frequency: "EVERY_12_HOURS" }, at("2026-09-17T16:00:00Z")))).toBe(
      "2026-09-17T15:30:00.000Z",
    );
  });
});

describe("daylight-saving timezones", () => {
  test("09:00 New York is 13:00 UTC in summer and 14:00 UTC in winter", () => {
    expect(iso(mostRecentSlot(newYork, at("2026-07-01T20:00:00Z")))).toBe("2026-07-01T13:00:00.000Z");
    expect(iso(mostRecentSlot(newYork, at("2026-12-01T20:00:00Z")))).toBe("2026-12-01T14:00:00.000Z");
  });
  test("on the day the clocks change", () => {
    expect(iso(mostRecentSlot(newYork, at("2026-03-08T20:00:00Z")))).toBe("2026-03-08T13:00:00.000Z");
  });
});

describe("dueSlot", () => {
  test("a slot after the schedule was saved is due", () => {
    expect(iso(dueSlot(ist, at("2026-09-17T03:31:00Z"), at("2026-09-16T12:00:00Z")))).toBe("2026-09-17T03:30:00.000Z");
  });
  test("saving a schedule whose time already passed today does not make it due", () => {
    expect(dueSlot(ist, at("2026-09-17T09:31:00Z"), at("2026-09-17T09:30:00Z"))).toBeNull();
  });
  test("a slot missed during downtime is still due hours later (catch-up)", () => {
    expect(iso(dueSlot(ist, at("2026-09-17T11:30:00Z"), at("2026-09-10T00:00:00Z")))).toBe("2026-09-17T03:30:00.000Z");
  });
});

test("isValidTimezone", () => {
  expect(isValidTimezone("Asia/Kolkata")).toBe(true);
  expect(isValidTimezone("UTC")).toBe(true);
  expect(isValidTimezone("Mars/Olympus")).toBe(false);
  expect(isValidTimezone("")).toBe(false);
});
