import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import CallbackPage from "./page";

// Mock next/navigation
const mockPush = vi.fn();
const mockSearchParams = new Map<string, string>();

vi.mock("next/navigation", () => ({
  useRouter: (): { push: typeof mockPush } => ({
    push: mockPush,
  }),
  useSearchParams: (): { get: (key: string) => string | undefined } => ({
    get: (key: string): string | undefined => mockSearchParams.get(key),
  }),
}));

// Mock auth context
vi.mock("@/lib/auth/auth-context", () => ({
  useAuth: vi.fn(() => ({
    refreshSession: vi.fn(),
  })),
}));

const { useAuth } = await import("@/lib/auth/auth-context");

describe("CallbackPage", (): void => {
  beforeEach((): void => {
    mockPush.mockClear();
    mockSearchParams.clear();
    vi.mocked(useAuth).mockReturnValue({
      refreshSession: vi.fn(),
      user: null,
      isLoading: false,
      isAuthenticated: false,
      authError: null,
      sessionExpiring: false,
      sessionMinutesRemaining: 0,
      signOut: vi.fn(),
    });
  });

  it("should render processing message", (): void => {
    render(<CallbackPage />);
    expect(screen.getByText(/completing authentication/i)).toBeInTheDocument();
  });

  it("should redirect to tasks page on success", async (): Promise<void> => {
    const mockRefreshSession = vi.fn().mockResolvedValue(undefined);
    vi.mocked(useAuth).mockReturnValue({
      refreshSession: mockRefreshSession,
      user: null,
      isLoading: false,
      isAuthenticated: false,
      authError: null,
      sessionExpiring: false,
      sessionMinutesRemaining: 0,
      signOut: vi.fn(),
    });

    render(<CallbackPage />);

    await waitFor(() => {
      expect(mockRefreshSession).toHaveBeenCalled();
      expect(mockPush).toHaveBeenCalledWith("/tasks");
    });
  });

  it("should redirect to login on error parameter", async (): Promise<void> => {
    mockSearchParams.set("error", "access_denied");
    mockSearchParams.set("error_description", "User cancelled");

    render(<CallbackPage />);

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

  it("should sanitize unknown error codes to prevent open redirect", async (): Promise<void> => {
    // Malicious error parameter that could be used for XSS or redirect attacks
    mockSearchParams.set("error", "<script>alert('xss')</script>");

    render(<CallbackPage />);

    await waitFor(() => {
      // Should replace unknown error with generic authentication_error
      expect(mockPush).toHaveBeenCalledWith("/login?error=authentication_error");
    });
  });

  it("should sanitize URL-like error codes to prevent open redirect", async (): Promise<void> => {
    // Attacker tries to inject a URL-like value
    mockSearchParams.set("error", "https://evil.com/phishing");

    render(<CallbackPage />);

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

  it("should allow valid OAuth 2.0 error codes", async (): Promise<void> => {
    const validErrors = [
      "access_denied",
      "invalid_request",
      "unauthorized_client",
      "server_error",
      "login_required",
      "consent_required",
    ];

    for (const errorCode of validErrors) {
      mockPush.mockClear();
      mockSearchParams.clear();
      mockSearchParams.set("error", errorCode);

      const { unmount } = render(<CallbackPage />);

      await waitFor(() => {
        expect(mockPush).toHaveBeenCalledWith(`/login?error=${errorCode}`);
      });

      unmount();
    }
  });

  it("should encode special characters in error parameter", async (): Promise<void> => {
    // Even valid errors should be encoded in the URL
    mockSearchParams.set("error", "session_failed");

    render(<CallbackPage />);

    await waitFor(() => {
      // session_failed doesn't need encoding but the function should still call encodeURIComponent
      expect(mockPush).toHaveBeenCalledWith("/login?error=session_failed");
    });
  });

  it("should handle refresh session errors gracefully", async (): Promise<void> => {
    const mockRefreshSession = vi.fn().mockRejectedValue(new Error("Session error"));
    vi.mocked(useAuth).mockReturnValue({
      refreshSession: mockRefreshSession,
      user: null,
      isLoading: false,
      isAuthenticated: false,
      authError: null,
      sessionExpiring: false,
      sessionMinutesRemaining: 0,
      signOut: vi.fn(),
    });

    render(<CallbackPage />);

    await waitFor(() => {
      expect(mockRefreshSession).toHaveBeenCalled();
      expect(mockPush).toHaveBeenCalledWith("/login?error=session_failed");
    });
  });
});
