// ABOUTME: Unit tests for the Calendar page component.
// ABOUTME: Tests calendar rendering, month navigation, ICS subscription, and token regeneration.
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";

// Mock next/navigation
const mockPush = vi.fn();
vi.mock("next/navigation", () => ({
  useRouter: () => ({
    push: mockPush,
  }),
}));

// Mock fetch
const mockFetch = vi.fn();
global.fetch = mockFetch;

import CalendarPage from "./page";

describe("CalendarPage", () => {
  const mockUser = {
    id: "user123",
    email: "test@example.com",
    cycleLength: 28,
    lastPeriodDate: "2026-01-01",
    calendarToken: "abc123def456",
    garminConnected: false,
    activeOverrides: [],
  };

  beforeEach(() => {
    vi.clearAllMocks();
    mockFetch.mockResolvedValue({
      ok: true,
      json: () => Promise.resolve(mockUser),
    });
  });

  describe("rendering", () => {
    it("renders the calendar heading", async () => {
      render(<CalendarPage />);

      await waitFor(() => {
        expect(
          screen.getByRole("heading", { name: /^calendar$/i, level: 1 }),
        ).toBeInTheDocument();
      });
    });

    it("renders the MonthView component with current month", async () => {
      render(<CalendarPage />);

      const today = new Date();
      const expectedMonth = today.toLocaleDateString("en-US", {
        month: "long",
        year: "numeric",
      });

      await waitFor(() => {
        expect(screen.getByText(expectedMonth)).toBeInTheDocument();
      });
    });

    it("renders day-of-week headers from MonthView", async () => {
      render(<CalendarPage />);

      await waitFor(() => {
        expect(screen.getByText("Sun")).toBeInTheDocument();
        expect(screen.getByText("Mon")).toBeInTheDocument();
      });
    });

    it("renders calendar days", async () => {
      render(<CalendarPage />);

      await waitFor(() => {
        // January has 31 days
        expect(screen.getByText("15")).toBeInTheDocument();
        expect(screen.getByText("31")).toBeInTheDocument();
      });
    });

    it("renders back link to dashboard", async () => {
      render(<CalendarPage />);

      await waitFor(() => {
        expect(screen.getByRole("link", { name: /back/i })).toHaveAttribute(
          "href",
          "/",
        );
      });
    });
  });

  describe("data loading", () => {
    it("fetches user data on mount", async () => {
      render(<CalendarPage />);

      await waitFor(() => {
        expect(mockFetch).toHaveBeenCalledWith("/api/user");
      });
    });

    it("shows loading state while fetching", async () => {
      let resolveUser: (value: unknown) => void = () => {};
      const userPromise = new Promise((resolve) => {
        resolveUser = resolve;
      });
      mockFetch.mockReturnValue({
        ok: true,
        json: () => userPromise,
      });

      render(<CalendarPage />);

      expect(screen.getByText(/loading/i)).toBeInTheDocument();

      resolveUser(mockUser);

      await waitFor(() => {
        expect(screen.queryByText(/loading/i)).not.toBeInTheDocument();
      });
    });

    it("shows error if fetching fails", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        json: () => Promise.resolve({ error: "Failed to fetch user" }),
      });

      render(<CalendarPage />);

      await waitFor(() => {
        expect(screen.getByRole("alert")).toBeInTheDocument();
      });
    });
  });

  describe("month navigation", () => {
    it("navigates to previous month when clicking previous button", async () => {
      render(<CalendarPage />);

      const today = new Date();
      const currentMonth = today.toLocaleDateString("en-US", {
        month: "long",
        year: "numeric",
      });
      const prevMonth = new Date(today.getFullYear(), today.getMonth() - 1);
      const prevMonthStr = prevMonth.toLocaleDateString("en-US", {
        month: "long",
        year: "numeric",
      });

      await waitFor(() => {
        expect(screen.getByText(currentMonth)).toBeInTheDocument();
      });

      const prevButton = screen.getByRole("button", {
        name: /previous month/i,
      });
      fireEvent.click(prevButton);

      expect(screen.getByText(prevMonthStr)).toBeInTheDocument();
    });

    it("navigates to next month when clicking next button", async () => {
      render(<CalendarPage />);

      const today = new Date();
      const currentMonth = today.toLocaleDateString("en-US", {
        month: "long",
        year: "numeric",
      });
      const nextMonth = new Date(today.getFullYear(), today.getMonth() + 1);
      const nextMonthStr = nextMonth.toLocaleDateString("en-US", {
        month: "long",
        year: "numeric",
      });

      await waitFor(() => {
        expect(screen.getByText(currentMonth)).toBeInTheDocument();
      });

      const nextButton = screen.getByRole("button", { name: /next month/i });
      fireEvent.click(nextButton);

      expect(screen.getByText(nextMonthStr)).toBeInTheDocument();
    });

    it("returns to current month when clicking Today button", async () => {
      render(<CalendarPage />);

      const today = new Date();
      const currentMonth = today.toLocaleDateString("en-US", {
        month: "long",
        year: "numeric",
      });
      const nextMonth = new Date(today.getFullYear(), today.getMonth() + 1);
      const nextMonthStr = nextMonth.toLocaleDateString("en-US", {
        month: "long",
        year: "numeric",
      });

      await waitFor(() => {
        expect(screen.getByText(currentMonth)).toBeInTheDocument();
      });

      // Navigate away first
      const nextButton = screen.getByRole("button", { name: /next month/i });
      fireEvent.click(nextButton);
      expect(screen.getByText(nextMonthStr)).toBeInTheDocument();

      // Click Today to return
      const todayButton = screen.getByRole("button", { name: /^today$/i });
      fireEvent.click(todayButton);

      expect(screen.getByText(currentMonth)).toBeInTheDocument();
    });
  });

  describe("ICS subscription", () => {
    it("renders ICS subscription section", async () => {
      render(<CalendarPage />);

      await waitFor(() => {
        expect(screen.getByText(/calendar subscription/i)).toBeInTheDocument();
      });
    });

    it("displays calendar subscription URL when token exists", async () => {
      render(<CalendarPage />);

      await waitFor(() => {
        // The URL input should contain the token
        const urlInput = screen.getByRole("textbox") as HTMLInputElement;
        expect(urlInput.value).toContain("abc123def456.ics");
      });
    });

    it("shows instructions for subscribing", async () => {
      render(<CalendarPage />);

      await waitFor(() => {
        expect(
          screen.getByText(/subscribe to this calendar/i),
        ).toBeInTheDocument();
      });
    });

    it("renders copy URL button", async () => {
      render(<CalendarPage />);

      await waitFor(() => {
        expect(
          screen.getByRole("button", { name: /copy/i }),
        ).toBeInTheDocument();
      });
    });

    it("renders regenerate token button", async () => {
      render(<CalendarPage />);

      await waitFor(() => {
        expect(
          screen.getByRole("button", { name: /regenerate/i }),
        ).toBeInTheDocument();
      });
    });
  });

  describe("token regeneration", () => {
    it("calls regenerate API when clicking regenerate button", async () => {
      const confirmSpy = vi.spyOn(window, "confirm").mockReturnValue(true);

      mockFetch
        .mockResolvedValueOnce({
          ok: true,
          json: () => Promise.resolve(mockUser),
        })
        .mockResolvedValueOnce({
          ok: true,
          json: () =>
            Promise.resolve({
              token: "newtoken123",
              url: "http://localhost/api/calendar/user123/newtoken123.ics",
            }),
        });

      render(<CalendarPage />);

      await waitFor(() => {
        expect(
          screen.getByRole("button", { name: /regenerate/i }),
        ).toBeInTheDocument();
      });

      const regenerateButton = screen.getByRole("button", {
        name: /regenerate/i,
      });
      fireEvent.click(regenerateButton);

      await waitFor(() => {
        expect(mockFetch).toHaveBeenCalledWith(
          "/api/calendar/regenerate-token",
          {
            method: "POST",
          },
        );
      });

      confirmSpy.mockRestore();
    });

    it("updates displayed URL after regeneration", async () => {
      const confirmSpy = vi.spyOn(window, "confirm").mockReturnValue(true);

      mockFetch
        .mockResolvedValueOnce({
          ok: true,
          json: () => Promise.resolve(mockUser),
        })
        .mockResolvedValueOnce({
          ok: true,
          json: () =>
            Promise.resolve({
              token: "newtoken123",
              url: "http://localhost/api/calendar/user123/newtoken123.ics",
            }),
        });

      render(<CalendarPage />);

      await waitFor(() => {
        const urlInput = screen.getByRole("textbox") as HTMLInputElement;
        expect(urlInput.value).toContain("abc123def456.ics");
      });

      const regenerateButton = screen.getByRole("button", {
        name: /regenerate/i,
      });
      fireEvent.click(regenerateButton);

      await waitFor(() => {
        const urlInput = screen.getByRole("textbox") as HTMLInputElement;
        expect(urlInput.value).toContain("newtoken123.ics");
      });

      confirmSpy.mockRestore();
    });

    it("shows confirmation dialog before regenerating", async () => {
      // Mock window.confirm
      const confirmSpy = vi.spyOn(window, "confirm").mockReturnValue(true);

      mockFetch
        .mockResolvedValueOnce({
          ok: true,
          json: () => Promise.resolve(mockUser),
        })
        .mockResolvedValueOnce({
          ok: true,
          json: () =>
            Promise.resolve({
              token: "newtoken123",
              url: "http://localhost/api/calendar/user123/newtoken123.ics",
            }),
        });

      render(<CalendarPage />);

      await waitFor(() => {
        expect(
          screen.getByRole("button", { name: /regenerate/i }),
        ).toBeInTheDocument();
      });

      const regenerateButton = screen.getByRole("button", {
        name: /regenerate/i,
      });
      fireEvent.click(regenerateButton);

      expect(confirmSpy).toHaveBeenCalled();
      confirmSpy.mockRestore();
    });

    it("does not regenerate if user cancels confirmation", async () => {
      const confirmSpy = vi.spyOn(window, "confirm").mockReturnValue(false);

      render(<CalendarPage />);

      await waitFor(() => {
        expect(
          screen.getByRole("button", { name: /regenerate/i }),
        ).toBeInTheDocument();
      });

      const regenerateButton = screen.getByRole("button", {
        name: /regenerate/i,
      });
      fireEvent.click(regenerateButton);

      // Should not call the regenerate API
      expect(mockFetch).toHaveBeenCalledTimes(1); // Only initial fetch
      confirmSpy.mockRestore();
    });
  });

  describe("phase legend", () => {
    it("displays phase legend from MonthView", async () => {
      render(<CalendarPage />);

      await waitFor(() => {
        expect(screen.getByText(/menstrual/i)).toBeInTheDocument();
        expect(screen.getByText(/follicular/i)).toBeInTheDocument();
        expect(screen.getByText(/ovulation/i)).toBeInTheDocument();
        expect(screen.getByText(/early luteal/i)).toBeInTheDocument();
        expect(screen.getByText(/late luteal/i)).toBeInTheDocument();
      });
    });
  });

  describe("no calendar token", () => {
    it("shows generate button when no token exists", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ ...mockUser, calendarToken: null }),
      });

      render(<CalendarPage />);

      await waitFor(() => {
        expect(
          screen.getByRole("button", { name: /generate calendar url/i }),
        ).toBeInTheDocument();
      });
    });

    it("does not show URL when no token exists", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ ...mockUser, calendarToken: null }),
      });

      render(<CalendarPage />);

      await waitFor(() => {
        expect(screen.getByText(/calendar subscription/i)).toBeInTheDocument();
      });

      expect(screen.queryByText(/\.ics/i)).not.toBeInTheDocument();
    });
  });
});
