import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";

// Mock database
vi.mock("@/lib/db", () => ({
  db: {
    maintenanceWindow: {
      findMany: vi.fn(),
      findFirst: vi.fn(),
      create: vi.fn(),
      delete: vi.fn(),
    },
    device: {
      findFirst: vi.fn(),
    },
  },
}));

// Mock logger
vi.mock("@/lib/logger", () => ({
  logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() },
}));

import { db } from "@/lib/db";
import {
  isInMaintenanceWindow,
  getUpcomingWindows,
  getActiveWindows,
  _matchesRecurring,
} from "@/lib/token/scheduling";

/* ── Helper: create a window record ──────────────────────────────────── */

function makeWindow(overrides: Record<string, unknown> = {}) {
  return {
    id: "win_1",
    deviceId: "dev_1",
    userId: "user_1",
    startTime: new Date("2026-02-23T10:00:00Z"),
    endTime: new Date("2026-02-23T14:00:00Z"),
    reason: "Planned upgrade",
    recurring: "none",
    createdAt: new Date(),
    updatedAt: new Date(),
    ...overrides,
  };
}

/* ── isInMaintenanceWindow ───────────────────────────────────────────── */

describe("isInMaintenanceWindow", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.useFakeTimers();
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  it("should return true when current time is within a one-shot window", async () => {
    vi.setSystemTime(new Date("2026-02-23T12:00:00Z")); // inside 10:00-14:00

    vi.mocked(db.maintenanceWindow.findMany).mockResolvedValue([
      makeWindow(),
    ] as any);

    const result = await isInMaintenanceWindow("dev_1");
    expect(result).toBe(true);
  });

  it("should return false when current time is outside all windows", async () => {
    vi.setSystemTime(new Date("2026-02-23T15:00:00Z")); // after 14:00

    vi.mocked(db.maintenanceWindow.findMany).mockResolvedValue([] as any);

    const result = await isInMaintenanceWindow("dev_1");
    expect(result).toBe(false);
  });

  it("should return true for a recurring daily window during matching time", async () => {
    // Wednesday 11:00 UTC — within 10:00-14:00 daily window
    vi.setSystemTime(new Date("2026-02-25T11:00:00Z"));

    vi.mocked(db.maintenanceWindow.findMany).mockResolvedValue([
      makeWindow({
        recurring: "daily",
        startTime: new Date("2026-02-23T10:00:00Z"), // original was a Monday
        endTime: new Date("2026-02-23T14:00:00Z"),
      }),
    ] as any);

    const result = await isInMaintenanceWindow("dev_1");
    expect(result).toBe(true);
  });

  it("should return false for a recurring daily window outside matching time", async () => {
    // Wednesday 16:00 UTC — outside 10:00-14:00 daily window
    vi.setSystemTime(new Date("2026-02-25T16:00:00Z"));

    vi.mocked(db.maintenanceWindow.findMany).mockResolvedValue([
      makeWindow({
        recurring: "daily",
        startTime: new Date("2026-02-23T10:00:00Z"),
        endTime: new Date("2026-02-23T14:00:00Z"),
      }),
    ] as any);

    const result = await isInMaintenanceWindow("dev_1");
    expect(result).toBe(false);
  });

  it("should return true for a recurring weekly window on matching day and time", async () => {
    // 2026-02-23 is a Monday. Check next Monday at 11:00.
    vi.setSystemTime(new Date("2026-03-02T11:00:00Z")); // Monday 11:00

    vi.mocked(db.maintenanceWindow.findMany).mockResolvedValue([
      makeWindow({
        recurring: "weekly",
        startTime: new Date("2026-02-23T10:00:00Z"), // Monday 10:00
        endTime: new Date("2026-02-23T14:00:00Z"),   // Monday 14:00
      }),
    ] as any);

    const result = await isInMaintenanceWindow("dev_1");
    expect(result).toBe(true);
  });

  it("should return false for a recurring weekly window on wrong day", async () => {
    // 2026-02-23 is a Monday. Check Tuesday at 11:00.
    vi.setSystemTime(new Date("2026-02-25T11:00:00Z")); // Tuesday (not Monday)

    vi.mocked(db.maintenanceWindow.findMany).mockResolvedValue([
      makeWindow({
        recurring: "weekly",
        startTime: new Date("2026-02-23T10:00:00Z"), // Monday
        endTime: new Date("2026-02-23T14:00:00Z"),
      }),
    ] as any);

    const result = await isInMaintenanceWindow("dev_1");
    expect(result).toBe(false);
  });

  it("should return false when no maintenance windows exist", async () => {
    vi.setSystemTime(new Date("2026-02-23T12:00:00Z"));

    vi.mocked(db.maintenanceWindow.findMany).mockResolvedValue([]);

    const result = await isInMaintenanceWindow("dev_1");
    expect(result).toBe(false);
  });
});

/* ── _matchesRecurring (unit tests for the helper) ───────────────────── */

describe("_matchesRecurring", () => {
  it("should return false for recurring=none", () => {
    const result = _matchesRecurring(
      new Date("2026-02-23T10:00:00Z"),
      new Date("2026-02-23T14:00:00Z"),
      "none",
      new Date("2026-02-23T12:00:00Z"),
    );
    expect(result).toBe(false);
  });

  it("should handle overnight daily windows (23:00-02:00)", () => {
    const start = new Date("2026-02-23T23:00:00Z");
    const end = new Date("2026-02-24T02:00:00Z");

    // At midnight — should be in window
    expect(_matchesRecurring(start, end, "daily", new Date("2026-02-25T00:30:00Z"))).toBe(true);

    // At 23:30 — should be in window
    expect(_matchesRecurring(start, end, "daily", new Date("2026-02-25T23:30:00Z"))).toBe(true);

    // At 15:00 — should NOT be in window
    expect(_matchesRecurring(start, end, "daily", new Date("2026-02-25T15:00:00Z"))).toBe(false);
  });

  it("should match weekly only on the correct day", () => {
    // Monday window
    const start = new Date("2026-02-23T10:00:00Z"); // Monday
    const end = new Date("2026-02-23T14:00:00Z");

    // Monday 12:00 — match
    expect(_matchesRecurring(start, end, "weekly", new Date("2026-03-02T12:00:00Z"))).toBe(true);

    // Wednesday 12:00 — no match
    expect(_matchesRecurring(start, end, "weekly", new Date("2026-02-25T12:00:00Z"))).toBe(false);
  });
});

/* ── getUpcomingWindows ──────────────────────────────────────────────── */

describe("getUpcomingWindows", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.useFakeTimers();
    vi.setSystemTime(new Date("2026-02-23T12:00:00Z"));
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  it("should return upcoming windows ordered by startTime", async () => {
    const windows = [
      makeWindow({ id: "win_1", startTime: new Date("2026-02-23T13:00:00Z") }),
      makeWindow({ id: "win_2", startTime: new Date("2026-02-24T10:00:00Z") }),
    ];

    vi.mocked(db.maintenanceWindow.findMany).mockResolvedValue(windows as any);

    const result = await getUpcomingWindows("user_1");

    expect(result).toHaveLength(2);
    expect(db.maintenanceWindow.findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({ userId: "user_1" }),
        orderBy: { startTime: "asc" },
      }),
    );
  });

  it("should filter by userId", async () => {
    vi.mocked(db.maintenanceWindow.findMany).mockResolvedValue([]);

    await getUpcomingWindows("user_42");

    expect(db.maintenanceWindow.findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({ userId: "user_42" }),
      }),
    );
  });

  it("should include recurring windows regardless of endTime", async () => {
    vi.mocked(db.maintenanceWindow.findMany).mockResolvedValue([
      makeWindow({ recurring: "daily" }),
    ] as any);

    const result = await getUpcomingWindows("user_1");
    expect(result).toHaveLength(1);
  });
});

/* ── getActiveWindows ────────────────────────────────────────────────── */

describe("getActiveWindows", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.useFakeTimers();
    vi.setSystemTime(new Date("2026-02-23T12:00:00Z"));
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  it("should return only currently active one-shot windows", async () => {
    vi.mocked(db.maintenanceWindow.findMany).mockResolvedValue([
      makeWindow({
        id: "active",
        startTime: new Date("2026-02-23T10:00:00Z"),
        endTime: new Date("2026-02-23T14:00:00Z"),
      }),
    ] as any);

    const result = await getActiveWindows("user_1");
    expect(result).toHaveLength(1);
    expect(result[0].id).toBe("active");
  });

  it("should filter out recurring windows not matching current time", async () => {
    // Current time: 12:00 UTC. Recurring window is 16:00-18:00 — not active now.
    vi.mocked(db.maintenanceWindow.findMany).mockResolvedValue([
      makeWindow({
        id: "not_now",
        recurring: "daily",
        startTime: new Date("2026-02-20T16:00:00Z"),
        endTime: new Date("2026-02-20T18:00:00Z"),
      }),
    ] as any);

    const result = await getActiveWindows("user_1");
    expect(result).toHaveLength(0);
  });

  it("should include recurring windows that match current time", async () => {
    // Current time: 12:00 UTC. Daily window 10:00-14:00 — active now.
    vi.mocked(db.maintenanceWindow.findMany).mockResolvedValue([
      makeWindow({
        id: "active_recurring",
        recurring: "daily",
        startTime: new Date("2026-02-20T10:00:00Z"),
        endTime: new Date("2026-02-20T14:00:00Z"),
      }),
    ] as any);

    const result = await getActiveWindows("user_1");
    expect(result).toHaveLength(1);
    expect(result[0].id).toBe("active_recurring");
  });
});

/* ── POST validation (tested via scheduling lib + route patterns) ───── */

describe("POST validation rules", () => {
  it("startTime must be before endTime", () => {
    const start = new Date("2026-02-23T14:00:00Z");
    const end = new Date("2026-02-23T10:00:00Z");
    expect(start >= end).toBe(true); // This would fail validation
  });

  it("endTime must be in the future for non-recurring", () => {
    const pastEnd = new Date("2026-02-20T10:00:00Z");
    const now = new Date("2026-02-23T12:00:00Z");
    expect(pastEnd <= now).toBe(true); // This would fail validation
  });

  it("should accept valid window parameters", () => {
    const start = new Date("2026-02-24T10:00:00Z");
    const end = new Date("2026-02-24T14:00:00Z");
    const now = new Date("2026-02-23T12:00:00Z");

    expect(start < end).toBe(true);
    expect(end > now).toBe(true);
  });

  it("recurring type must be valid", () => {
    const valid = ["none", "daily", "weekly"];
    expect(valid.includes("none")).toBe(true);
    expect(valid.includes("daily")).toBe(true);
    expect(valid.includes("weekly")).toBe(true);
    expect(valid.includes("monthly")).toBe(false);
  });
});
