import { render, screen, fireEvent, waitFor, cleanup } from "@testing-library/react";
import { expect, test, vi, beforeEach, afterEach, describe } from "vitest";
import Home from "./page";

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

// Default mock values
let mockUser: { id: number; email: string } | null = { id: 1, email: "test@example.com" };
let mockIsLoading = false;
const mockLogout = vi.fn();

vi.mock("./auth-context", () => ({
  useAuth: () => ({
    user: mockUser,
    isLoading: mockIsLoading,
    logout: mockLogout,
  }),
}));

beforeEach(() => {
  vi.clearAllMocks();
  // Reset to authenticated state
  mockUser = { id: 1, email: "test@example.com" };
  mockIsLoading = false;
});

afterEach(() => {
  cleanup();
});

describe("Home - Authenticated", () => {
  test("renders loading state when isLoading is true", () => {
    mockIsLoading = true;
    vi.spyOn(global, "fetch").mockImplementation(() => new Promise(() => {}));

    render(<Home />);
    expect(screen.getByText("Loading...")).toBeDefined();
  });

  test("renders user email in header", async () => {
    vi.spyOn(global, "fetch").mockResolvedValue({
      json: () => Promise.resolve({ value: 42 }),
    } as Response);

    render(<Home />);
    expect(screen.getByText("test@example.com")).toBeDefined();
  });

  test("renders sign out button", async () => {
    vi.spyOn(global, "fetch").mockResolvedValue({
      json: () => Promise.resolve({ value: 42 }),
    } as Response);

    render(<Home />);
    expect(screen.getByText("Sign out")).toBeDefined();
  });

  test("clicking sign out calls logout and redirects", async () => {
    vi.spyOn(global, "fetch").mockResolvedValue({
      json: () => Promise.resolve({ value: 42 }),
    } as Response);

    render(<Home />);
    fireEvent.click(screen.getByText("Sign out"));
    
    await waitFor(() => {
      expect(mockLogout).toHaveBeenCalled();
      expect(mockPush).toHaveBeenCalledWith("/login");
    });
  });

  test("renders counter value after fetch", async () => {
    vi.spyOn(global, "fetch").mockResolvedValue({
      json: () => Promise.resolve({ value: 42 }),
    } as Response);

    render(<Home />);
    await waitFor(() => {
      expect(screen.getByText("42")).toBeDefined();
    });
  });

  test("fetches counter with credentials", async () => {
    const fetchSpy = vi.spyOn(global, "fetch").mockResolvedValue({
      json: () => Promise.resolve({ value: 0 }),
    } as Response);

    render(<Home />);

    await waitFor(() => {
      expect(fetchSpy).toHaveBeenCalledWith(
        "http://localhost:8000/api/counter",
        expect.objectContaining({
          credentials: "include",
        })
      );
    });
  });

  test("renders increment button", async () => {
    vi.spyOn(global, "fetch").mockResolvedValue({
      json: () => Promise.resolve({ value: 0 }),
    } as Response);

    render(<Home />);
    expect(screen.getByText("Increment")).toBeDefined();
  });

  test("clicking increment button calls API with credentials", async () => {
    const fetchSpy = vi
      .spyOn(global, "fetch")
      .mockResolvedValueOnce({ json: () => Promise.resolve({ value: 0 }) } as Response)
      .mockResolvedValueOnce({ json: () => Promise.resolve({ value: 1 }) } as Response);

    render(<Home />);
    await waitFor(() => expect(screen.getByText("0")).toBeDefined());

    fireEvent.click(screen.getByText("Increment"));

    await waitFor(() => {
      expect(fetchSpy).toHaveBeenCalledWith(
        "http://localhost:8000/api/counter/increment",
        expect.objectContaining({
          method: "POST",
          credentials: "include",
        })
      );
    });
  });

  test("clicking increment updates displayed count", async () => {
    vi.spyOn(global, "fetch")
      .mockResolvedValueOnce({ json: () => Promise.resolve({ value: 0 }) } as Response)
      .mockResolvedValueOnce({ json: () => Promise.resolve({ value: 1 }) } as Response);

    render(<Home />);
    await waitFor(() => expect(screen.getByText("0")).toBeDefined());

    fireEvent.click(screen.getByText("Increment"));
    await waitFor(() => expect(screen.getByText("1")).toBeDefined());
  });
});

describe("Home - Unauthenticated", () => {
  test("redirects to login when not authenticated", async () => {
    mockUser = null;

    render(<Home />);

    await waitFor(() => {
      expect(mockPush).toHaveBeenCalledWith("/login");
    });
  });

  test("returns null when not authenticated", () => {
    mockUser = null;

    const { container } = render(<Home />);
    // Should render nothing (just redirects)
    expect(container.querySelector("main")).toBeNull();
  });

  test("does not fetch counter when no user", () => {
    mockUser = null;
    const fetchSpy = vi.spyOn(global, "fetch");

    render(<Home />);

    expect(fetchSpy).not.toHaveBeenCalled();
  });
});

describe("Home - Loading State", () => {
  test("does not redirect while loading", () => {
    mockIsLoading = true;
    mockUser = null;

    render(<Home />);

    expect(mockPush).not.toHaveBeenCalled();
  });

  test("shows loading indicator while loading", () => {
    mockIsLoading = true;

    render(<Home />);

    expect(screen.getByText("Loading...")).toBeDefined();
  });
});
