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

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

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

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

/** Helper: returns a date far in the future (1 hour from now) for session mocks */
function futureExpiry(): string {
  return new Date(Date.now() + 60 * 60 * 1000).toISOString();
}

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

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

  return (
    <div>
      <div data-testid="auth-status">{isAuthenticated ? "Authenticated" : "Not Authenticated"}</div>
      <div data-testid="auth-error">{authError ?? "none"}</div>
      <div data-testid="session-expiring">{sessionExpiring ? "true" : "false"}</div>
      <div data-testid="session-minutes-remaining">{sessionMinutesRemaining}</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>
      <button onClick={refreshSession}>Refresh</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: futureExpiry() },
    });

    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> => {
    const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {
      // Intentionally empty - suppressing log output in tests
    });

    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();

    consoleErrorSpy.mockRestore();
  });

  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: futureExpiry() },
    });

    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 clear user and set authError to 'network' when signOut fails with a network error", async (): Promise<void> => {
    const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {
      // Intentionally empty - suppressing log output in tests
    });

    const mockUser: AuthUser = {
      id: "user-1",
      email: "test@example.com",
      name: "Test User",
    };

    // First: user is logged in
    vi.mocked(apiGet).mockResolvedValueOnce({
      user: mockUser,
      session: { id: "session-1", token: "token123", expiresAt: futureExpiry() },
    });

    // signOut request fails with a network error (TypeError with "fetch")
    vi.mocked(apiPost).mockRejectedValueOnce(new TypeError("Failed to fetch"));

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

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

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

    // User should be cleared (finally block runs even on error)
    await waitFor(() => {
      expect(screen.getByTestId("auth-status")).toHaveTextContent("Not Authenticated");
    });

    // authError should be set to "network" via classifyAuthError
    expect(screen.getByTestId("auth-error")).toHaveTextContent("network");

    // Verify the sign-out endpoint was still called
    expect(apiPost).toHaveBeenCalledWith("/auth/sign-out");

    consoleErrorSpy.mockRestore();
  });

  it("should clear user and set authError to 'backend' when signOut fails with a server error", async (): Promise<void> => {
    const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {
      // Intentionally empty - suppressing log output in tests
    });

    const mockUser: AuthUser = {
      id: "user-1",
      email: "test@example.com",
      name: "Test User",
    };

    // First: user is logged in
    vi.mocked(apiGet).mockResolvedValueOnce({
      user: mockUser,
      session: { id: "session-1", token: "token123", expiresAt: futureExpiry() },
    });

    // signOut request fails with a 500 Internal Server Error
    vi.mocked(apiPost).mockRejectedValueOnce(new Error("Internal Server Error"));

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

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

    // Click sign out — the apiPost will reject with server error
    const signOutButton = screen.getByRole("button", { name: "Sign Out" });
    signOutButton.click();

    // User should be cleared (finally block runs even on error)
    await waitFor(() => {
      expect(screen.getByTestId("auth-status")).toHaveTextContent("Not Authenticated");
    });

    // authError should be set to "backend" via classifyAuthError
    expect(screen.getByTestId("auth-error")).toHaveTextContent("backend");

    // Verify the sign-out endpoint was still called
    expect(apiPost).toHaveBeenCalledWith("/auth/sign-out");

    consoleErrorSpy.mockRestore();
  });

  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();
  });

  describe("auth error handling", (): void => {
    it("should set authError to null for normal 401 Unauthorized (not logged in)", async (): Promise<void> => {
      // 401 Unauthorized is a normal condition (user not logged in), not an error.
      // classifyAuthError should return null so no "having trouble" banner appears.
      vi.mocked(apiGet).mockRejectedValueOnce(new Error("Unauthorized"));

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

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

      // authError should be null (displayed as "none" by TestComponent)
      expect(screen.getByTestId("auth-error")).toHaveTextContent("none");
    });

    it("should set authError to null for 403 Forbidden", async (): Promise<void> => {
      // 403 Forbidden is also classified as invalid_credentials by parseAuthError
      vi.mocked(apiGet).mockRejectedValueOnce(new Error("Forbidden"));

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

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

      expect(screen.getByTestId("auth-error")).toHaveTextContent("none");
    });

    it("should set authError to null for session expired errors", async (): Promise<void> => {
      // "session expired" is a normal auth lifecycle event, not a backend error
      vi.mocked(apiGet).mockRejectedValueOnce(new Error("Session expired"));

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

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

      expect(screen.getByTestId("auth-error")).toHaveTextContent("none");
    });

    it("should set authError to 'backend' for truly unrecognised Error instances", async (): Promise<void> => {
      const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {
        // Intentionally empty
      });

      // An Error that doesn't match any known pattern (parseAuthError returns "unknown")
      // should fall through to the instanceof Error catch-all and return "backend"
      vi.mocked(apiGet).mockRejectedValueOnce(
        new Error("Something completely unexpected happened")
      );

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

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

      expect(screen.getByTestId("auth-error")).toHaveTextContent("backend");

      consoleErrorSpy.mockRestore();
    });

    it("should set authError to 'network' for fetch failures", async (): Promise<void> => {
      const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {
        // Intentionally empty
      });

      // Network error - backend is unreachable
      vi.mocked(apiGet).mockRejectedValueOnce(new TypeError("Failed to fetch"));

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

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

      // Should have a network error
      expect(screen.getByTestId("auth-error")).toHaveTextContent("network");

      consoleErrorSpy.mockRestore();
    });

    it("should always log auth errors (including production)", async (): Promise<void> => {
      const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {
        // Intentionally empty - we're testing that errors are logged
      });

      // Network error - backend is unreachable
      vi.mocked(apiGet).mockRejectedValueOnce(new TypeError("Failed to fetch"));

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

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

      // Should log error regardless of NODE_ENV
      expect(consoleErrorSpy).toHaveBeenCalledWith(
        expect.stringContaining("[Auth]"),
        expect.any(Error)
      );

      consoleErrorSpy.mockRestore();
    });

    it("should set authError to 'network' for connection refused", async (): Promise<void> => {
      const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {
        // Intentionally empty
      });

      // "Connection refused" includes "connection" which parseAuthError maps to network_error
      vi.mocked(apiGet).mockRejectedValueOnce(new Error("Connection refused"));

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

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

      consoleErrorSpy.mockRestore();
    });

    it("should set authError to 'backend' for server errors", async (): Promise<void> => {
      const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {
        // Intentionally empty
      });

      // Backend error - 500 Internal Server Error
      vi.mocked(apiGet).mockRejectedValueOnce(new Error("Internal Server Error"));

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

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

      // Should have a backend error
      expect(screen.getByTestId("auth-error")).toHaveTextContent("backend");

      consoleErrorSpy.mockRestore();
    });

    it("should set authError to 'backend' for service unavailable", async (): Promise<void> => {
      const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {
        // Intentionally empty
      });

      vi.mocked(apiGet).mockRejectedValueOnce(new Error("Service Unavailable"));

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

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

      consoleErrorSpy.mockRestore();
    });

    it("should persist authError across re-renders when no new session check occurs", async (): Promise<void> => {
      const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {
        // Intentionally empty
      });

      // First call fails with network error
      vi.mocked(apiGet).mockRejectedValueOnce(new TypeError("Failed to fetch"));

      const { rerender } = render(
        <AuthProvider>
          <TestComponent />
        </AuthProvider>
      );

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

      // Re-render does NOT trigger a new session check, so authError persists
      rerender(
        <AuthProvider>
          <TestComponent />
        </AuthProvider>
      );

      // authError should still be "network" — re-render alone does not clear it
      expect(screen.getByTestId("auth-error")).toHaveTextContent("network");

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

  describe("session expiry detection", (): void => {
    const mockUser: AuthUser = {
      id: "user-1",
      email: "test@example.com",
      name: "Test User",
    };

    beforeEach((): void => {
      // Reset all mocks to clear any unconsumed mockResolvedValueOnce queues
      // from previous tests (vi.clearAllMocks only clears calls/results, not implementations)
      vi.resetAllMocks();
    });

    afterEach((): void => {
      // Ensure no stale intervals leak between tests
      vi.clearAllTimers();
    });

    it("should set sessionExpiring to false when session has plenty of time remaining", async (): Promise<void> => {
      const farFuture = new Date(Date.now() + 60 * 60 * 1000).toISOString(); // 60 minutes

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

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

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

      expect(screen.getByTestId("session-expiring")).toHaveTextContent("false");
    });

    it("should set sessionExpiring to true when session is within 5 minutes of expiry", async (): Promise<void> => {
      const nearExpiry = new Date(Date.now() + 3 * 60 * 1000).toISOString(); // 3 minutes

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

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

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

      await waitFor(() => {
        expect(screen.getByTestId("session-expiring")).toHaveTextContent("true");
      });
    });

    it("should calculate sessionMinutesRemaining correctly", async (): Promise<void> => {
      const nearExpiry = new Date(Date.now() + 3 * 60 * 1000).toISOString(); // 3 minutes

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

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

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

      await waitFor(() => {
        expect(screen.getByTestId("session-minutes-remaining")).toHaveTextContent("3");
      });
    });

    it("should transition from not-expiring to expiring after interval fires", async (): Promise<void> => {
      vi.useFakeTimers();

      // Session expires 6 minutes from now - just outside the warning window
      const expiresAt = new Date(Date.now() + 6 * 60 * 1000).toISOString();

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

      await act(async () => {
        render(
          <AuthProvider>
            <TestComponent />
          </AuthProvider>
        );
        // Flush the resolved mock promise so checkSession completes
        await Promise.resolve();
        await Promise.resolve();
      });

      // Initially not expiring (6 minutes remaining)
      expect(screen.getByTestId("auth-status")).toHaveTextContent("Authenticated");
      expect(screen.getByTestId("session-expiring")).toHaveTextContent("false");

      // Advance 2 minutes - should now be within the 5-minute window (4 min remaining)
      await act(async () => {
        vi.advanceTimersByTime(2 * 60 * 1000);
        await Promise.resolve();
      });

      expect(screen.getByTestId("session-expiring")).toHaveTextContent("true");

      vi.useRealTimers();
    });

    it("should log out user and set session_expired when session expires via interval", async (): Promise<void> => {
      vi.useFakeTimers();

      // Session expires 30 seconds from now
      const almostExpired = new Date(Date.now() + 30 * 1000).toISOString();

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

      await act(async () => {
        render(
          <AuthProvider>
            <TestComponent />
          </AuthProvider>
        );
        await Promise.resolve();
        await Promise.resolve();
      });

      expect(screen.getByTestId("auth-status")).toHaveTextContent("Authenticated");

      // Advance past the expiry time (triggers the 60s interval)
      await act(async () => {
        vi.advanceTimersByTime(60 * 1000);
        await Promise.resolve();
      });

      expect(screen.getByTestId("auth-status")).toHaveTextContent("Not Authenticated");
      expect(screen.getByTestId("session-expiring")).toHaveTextContent("false");
      // Session expiry now sets explicit session_expired error state
      expect(screen.getByTestId("auth-error")).toHaveTextContent("session_expired");

      vi.useRealTimers();
    });

    it("should reset sessionExpiring after successful refreshSession", async (): Promise<void> => {
      // Session near expiry (3 minutes remaining)
      const nearExpiry = new Date(Date.now() + 3 * 60 * 1000).toISOString();

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

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

      await waitFor(() => {
        expect(screen.getByTestId("session-expiring")).toHaveTextContent("true");
      });

      // Set up a refreshed session response with a far-future expiry
      const refreshedExpiry = new Date(Date.now() + 60 * 60 * 1000).toISOString();
      vi.mocked(apiGet).mockResolvedValueOnce({
        user: mockUser,
        session: { id: "session-2", token: "token456", expiresAt: refreshedExpiry },
      });

      // Click refresh button
      const refreshButton = screen.getByRole("button", { name: "Refresh" });
      refreshButton.click();

      await waitFor(() => {
        expect(screen.getByTestId("session-expiring")).toHaveTextContent("false");
      });
    });
  });

  describe("workspace ID persistence", (): void => {
    // ---------------------------------------------------------------------------
    // localStorage mock for workspace persistence tests
    // ---------------------------------------------------------------------------
    interface MockLocalStorage {
      getItem: ReturnType<typeof vi.fn>;
      setItem: ReturnType<typeof vi.fn>;
      removeItem: ReturnType<typeof vi.fn>;
      clear: ReturnType<typeof vi.fn>;
      readonly length: number;
      key: ReturnType<typeof vi.fn>;
    }

    let localStorageMock: MockLocalStorage;

    beforeEach((): void => {
      let store: Record<string, string> = {};
      localStorageMock = {
        getItem: vi.fn((key: string): string | null => store[key] ?? null),
        setItem: vi.fn((key: string, value: string): void => {
          store[key] = value;
        }),
        removeItem: vi.fn((key: string): void => {
          store = Object.fromEntries(Object.entries(store).filter(([k]) => k !== key));
        }),
        clear: vi.fn((): void => {
          store = {};
        }),
        get length(): number {
          return Object.keys(store).length;
        },
        key: vi.fn((_index: number): string | null => null),
      };

      Object.defineProperty(window, "localStorage", {
        value: localStorageMock,
        writable: true,
        configurable: true,
      });

      vi.resetAllMocks();
    });

    afterEach((): void => {
      vi.restoreAllMocks();
    });

    it("should call fetchUserWorkspaces after successful session check", 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: futureExpiry() },
      });
      vi.mocked(fetchUserWorkspaces).mockResolvedValueOnce([
        {
          id: "ws-1",
          name: "My Workspace",
          ownerId: "user-1",
          role: WorkspaceMemberRole.OWNER,
          createdAt: "2026-01-01",
        },
      ]);

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

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

      expect(fetchUserWorkspaces).toHaveBeenCalledTimes(1);
    });

    it("should persist the first workspace ID to localStorage", 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: futureExpiry() },
      });
      vi.mocked(fetchUserWorkspaces).mockResolvedValueOnce([
        {
          id: "ws-abc-123",
          name: "My Workspace",
          ownerId: "user-1",
          role: WorkspaceMemberRole.OWNER,
          createdAt: "2026-01-01",
        },
        {
          id: "ws-def-456",
          name: "Second Workspace",
          ownerId: "other",
          role: WorkspaceMemberRole.MEMBER,
          createdAt: "2026-02-01",
        },
      ]);

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

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

      expect(localStorageMock.setItem).toHaveBeenCalledWith("mosaic-workspace-id", "ws-abc-123");
    });

    it("should not write localStorage when fetchUserWorkspaces returns empty array", 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: futureExpiry() },
      });
      vi.mocked(fetchUserWorkspaces).mockResolvedValueOnce([]);

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

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

      expect(localStorageMock.setItem).not.toHaveBeenCalledWith(
        "mosaic-workspace-id",
        expect.anything()
      );
    });

    it("should handle fetchUserWorkspaces failure gracefully — auth still succeeds", 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: futureExpiry() },
      });
      vi.mocked(fetchUserWorkspaces).mockRejectedValueOnce(new Error("Network error"));

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

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

      // Auth succeeded despite workspace fetch failure
      expect(screen.getByTestId("auth-error")).toHaveTextContent("none");
    });

    it("should remove workspace ID from localStorage 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: futureExpiry() },
      });
      vi.mocked(fetchUserWorkspaces).mockResolvedValueOnce([
        {
          id: "ws-1",
          name: "My Workspace",
          ownerId: "user-1",
          role: WorkspaceMemberRole.OWNER,
          createdAt: "2026-01-01",
        },
      ]);
      vi.mocked(apiPost).mockResolvedValueOnce({ success: true });

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

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

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

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

      expect(localStorageMock.removeItem).toHaveBeenCalledWith("mosaic-workspace-id");
    });
  });
});
