import { describe, it, expect } from "vitest";
import { isInOvernightWindow, getOvernightResumeUTC } from "../overnight-pause";

// Helper to build a Date at a specific local time in a timezone.
// We use a known UTC offset approach: format a UTC date, check what local time
// it produces, then shift until we hit the desired time.
function dateAtLocalTime(
  timezone: string,
  year: number,
  month: number, // 1-indexed
  day: number,
  hour: number,
  minute: number
): Date {
  // Use a binary-search-free approach: construct approximate UTC, then correct
  const approxUtc = Date.UTC(year, month - 1, day, hour, minute);
  const probe = new Date(approxUtc);
  const fmt = new Intl.DateTimeFormat("en-US", {
    timeZone: timezone,
    hour: "2-digit",
    minute: "2-digit",
    hour12: false,
  });
  const parts = fmt.formatToParts(probe);
  const localHour = parseInt(parts.find((p) => p.type === "hour")?.value ?? "0", 10);
  const localMin = parseInt(parts.find((p) => p.type === "minute")?.value ?? "0", 10);
  const h = localHour === 24 ? 0 : localHour;
  const diffMs = ((hour * 60 + minute) - (h * 60 + localMin)) * 60 * 1000;
  return new Date(approxUtc + diffMs);
}

// ─── isInOvernightWindow ──────────────────────────────────────────────────────

describe("isInOvernightWindow — midnight-spanning window (23:00–07:00)", () => {
  const tz = "America/New_York";
  const start = "23:00";
  const end = "07:00";

  it("returns true just after window starts (23:30)", () => {
    expect(isInOvernightWindow(tz, start, end, dateAtLocalTime(tz, 2025, 6, 15, 23, 30))).toBe(true);
  });

  it("returns true at exact window start (23:00)", () => {
    expect(isInOvernightWindow(tz, start, end, dateAtLocalTime(tz, 2025, 6, 15, 23, 0))).toBe(true);
  });

  it("returns true past midnight (01:00)", () => {
    expect(isInOvernightWindow(tz, start, end, dateAtLocalTime(tz, 2025, 6, 16, 1, 0))).toBe(true);
  });

  it("returns true just before window ends (06:59)", () => {
    expect(isInOvernightWindow(tz, start, end, dateAtLocalTime(tz, 2025, 6, 16, 6, 59))).toBe(true);
  });

  it("returns false exactly at window end (07:00)", () => {
    expect(isInOvernightWindow(tz, start, end, dateAtLocalTime(tz, 2025, 6, 16, 7, 0))).toBe(false);
  });

  it("returns false in the middle of the day (14:00)", () => {
    expect(isInOvernightWindow(tz, start, end, dateAtLocalTime(tz, 2025, 6, 15, 14, 0))).toBe(false);
  });

  it("returns false just before window starts (22:59)", () => {
    expect(isInOvernightWindow(tz, start, end, dateAtLocalTime(tz, 2025, 6, 15, 22, 59))).toBe(false);
  });
});

describe("isInOvernightWindow — same-day window (10:00–18:00)", () => {
  const tz = "America/Chicago";
  const start = "10:00";
  const end = "18:00";

  it("returns true at 14:00", () => {
    expect(isInOvernightWindow(tz, start, end, dateAtLocalTime(tz, 2025, 6, 15, 14, 0))).toBe(true);
  });

  it("returns true at exact start (10:00)", () => {
    expect(isInOvernightWindow(tz, start, end, dateAtLocalTime(tz, 2025, 6, 15, 10, 0))).toBe(true);
  });

  it("returns false at exact end (18:00)", () => {
    expect(isInOvernightWindow(tz, start, end, dateAtLocalTime(tz, 2025, 6, 15, 18, 0))).toBe(false);
  });

  it("returns false at 09:59", () => {
    expect(isInOvernightWindow(tz, start, end, dateAtLocalTime(tz, 2025, 6, 15, 9, 59))).toBe(false);
  });

  it("returns false at 20:00", () => {
    expect(isInOvernightWindow(tz, start, end, dateAtLocalTime(tz, 2025, 6, 15, 20, 0))).toBe(false);
  });
});

describe("isInOvernightWindow — different IANA timezones", () => {
  it("works for Europe/London at 00:30 in a midnight-spanning window", () => {
    const d = dateAtLocalTime("Europe/London", 2025, 6, 15, 0, 30);
    expect(isInOvernightWindow("Europe/London", "23:00", "07:00", d)).toBe(true);
  });

  it("works for Asia/Tokyo — 23:30 is in window", () => {
    const d = dateAtLocalTime("Asia/Tokyo", 2025, 6, 15, 23, 30);
    expect(isInOvernightWindow("Asia/Tokyo", "23:00", "07:00", d)).toBe(true);
  });

  it("works for Asia/Tokyo — 14:00 is not in window", () => {
    const d = dateAtLocalTime("Asia/Tokyo", 2025, 6, 15, 14, 0);
    expect(isInOvernightWindow("Asia/Tokyo", "23:00", "07:00", d)).toBe(false);
  });
});

// ─── getOvernightResumeUTC ────────────────────────────────────────────────────

describe("getOvernightResumeUTC", () => {
  it("returns a future date when called during the overnight window", () => {
    const tz = "America/New_York";
    // 2:00 AM ET — inside the 23:00–07:00 window
    const now = dateAtLocalTime(tz, 2025, 6, 16, 2, 0);
    const resume = getOvernightResumeUTC(tz, "07:00", now);
    expect(resume.getTime()).toBeGreaterThan(now.getTime());
  });

  it("returns a date that represents 07:00 in target timezone", () => {
    const tz = "America/Chicago";
    const now = dateAtLocalTime(tz, 2025, 6, 16, 1, 0);
    const resume = getOvernightResumeUTC(tz, "07:00", now);
    // Verify it shows as 07:00 in Chicago
    const fmt = new Intl.DateTimeFormat("en-US", {
      timeZone: tz,
      hour: "2-digit",
      minute: "2-digit",
      hour12: false,
    });
    const parts = fmt.formatToParts(resume);
    const h = parseInt(parts.find((p) => p.type === "hour")?.value ?? "0", 10);
    const m = parseInt(parts.find((p) => p.type === "minute")?.value ?? "0", 10);
    expect(h).toBe(7);
    expect(m).toBe(0);
  });

  it("returns tomorrow's end time when current time is after window end (daytime)", () => {
    const tz = "America/New_York";
    // 14:00 ET — outside the window, should return tomorrow 07:00
    const now = dateAtLocalTime(tz, 2025, 6, 16, 14, 0);
    const resume = getOvernightResumeUTC(tz, "07:00", now);
    expect(resume.getTime()).toBeGreaterThan(now.getTime());
  });
});
