import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import { AuthProvider, useAuth } from "./auth-context";
import type { AuthUser } from "@mosaic/shared";

// Mock the API client
vi.mock("../api/client", () => ({
  apiGet: vi.fn(),
  apiPost: vi.fn(),
}));

const { apiGet, apiPost } = await import("../api/client");

// Test component that uses the auth context
function TestComponent(): React.JSX.Element {
  const { user, isLoading, isAuthenticated, signOut } = useAuth();

  if (isLoading) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      <div data-testid="auth-status">{isAuthenticated ? "Authenticated" : "Not Authenticated"}</div>
      {user && (
        <div>
          <div data-testid="user-email">{user.email}</div>
          <div data-testid="user-name">{user.name}</div>
        </div>
      )}
      <button onClick={signOut}>Sign Out</button>
    </div>
  );
}

describe("AuthContext", (): void => {
  beforeEach((): void => {
    vi.clearAllMocks();
  });

  it("should provide loading state initially", (): void => {
    vi.mocked(apiGet).mockImplementation(
      () =>
        new Promise(() => {
          // Never resolves - intentionally empty for testing loading state
        })
    );

    render(
      <AuthProvider>
        <TestComponent />
      </AuthProvider>
    );

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

  it("should provide authenticated user when session exists", async (): Promise<void> => {
    const mockUser: AuthUser = {
      id: "user-1",
      email: "test@example.com",
      name: "Test User",
    };

    vi.mocked(apiGet).mockResolvedValueOnce({
      user: mockUser,
      session: { id: "session-1", token: "token123", expiresAt: new Date() },
    });

    render(
      <AuthProvider>
        <TestComponent />
      </AuthProvider>
    );

    await waitFor(() => {
      expect(screen.getByTestId("auth-status")).toHaveTextContent("Authenticated");
    });

    expect(screen.getByTestId("user-email")).toHaveTextContent("test@example.com");
    expect(screen.getByTestId("user-name")).toHaveTextContent("Test User");
  });

  it("should handle unauthenticated state when session check fails", async (): Promise<void> => {
    vi.mocked(apiGet).mockRejectedValueOnce(new Error("Unauthorized"));

    render(
      <AuthProvider>
        <TestComponent />
      </AuthProvider>
    );

    await waitFor(() => {
      expect(screen.getByTestId("auth-status")).toHaveTextContent("Not Authenticated");
    });

    expect(screen.queryByTestId("user-email")).not.toBeInTheDocument();
  });

  it("should clear user on sign out", async (): Promise<void> => {
    const mockUser: AuthUser = {
      id: "user-1",
      email: "test@example.com",
      name: "Test User",
    };

    vi.mocked(apiGet).mockResolvedValueOnce({
      user: mockUser,
      session: { id: "session-1", token: "token123", expiresAt: new Date() },
    });

    vi.mocked(apiPost).mockResolvedValueOnce({ success: true });

    render(
      <AuthProvider>
        <TestComponent />
      </AuthProvider>
    );

    // Wait for authenticated state
    await waitFor(() => {
      expect(screen.getByTestId("auth-status")).toHaveTextContent("Authenticated");
    });

    // Click sign out
    const signOutButton = screen.getByRole("button", { name: "Sign Out" });
    signOutButton.click();

    await waitFor(() => {
      expect(screen.getByTestId("auth-status")).toHaveTextContent("Not Authenticated");
    });

    expect(apiPost).toHaveBeenCalledWith("/auth/sign-out");
  });

  it("should throw error when useAuth is used outside AuthProvider", (): void => {
    // Suppress console.error for this test
    const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {
      // Intentionally empty - suppressing React error boundary output
    });

    expect(() => {
      render(<TestComponent />);
    }).toThrow("useAuth must be used within AuthProvider");

    consoleErrorSpy.mockRestore();
  });
});
