/**
 * Provider Composition Tests
 *
 * Validates that providers are nested in the correct order and that their
 * dependencies are satisfied. These tests ensure that the provider hierarchy
 * contract is maintained across refactors.
 *
 * Key invariants tested:
 * - All providers mount without crashing
 * - Providers are accessible from their descendant components
 * - Order-dependent initialization (auth, timezone) works correctly
 * - Theme persistence works across re-renders
 * - Notifications and errors propagate correctly
 */

import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import { type ReactElement } from "react";
import { MemoryRouter, Route, Routes } from "react-router-dom";
import { FluentProvider, webLightTheme } from "@fluentui/react-components";
import { AuthProvider } from "../AuthProvider";
import { ThemeProvider, useThemeMode } from "../ThemeProvider";
import { TimezoneProvider } from "../TimezoneProvider";
import { useTimezone } from "../../hooks/useTimezone";
import { NotificationProvider, useNotification } from "../../services/notificationService";
import * as clientModule from "../../api/client";

// ---------------------------------------------------------------------------
// Fixture: Test component that consumes all providers
// ---------------------------------------------------------------------------

function AllProvidersConsumer(): ReactElement {
  const { colorMode } = useThemeMode();
  const notification = useNotification();
  const timezone = useTimezone();

  return (
    <div>
      <div data-testid="theme">{colorMode}</div>
      <div data-testid="timezone">{timezone}</div>
      <button
        type="button"
        onClick={() => notification.success("Test notification")}
        data-testid="notify-btn"
      >
        Notify
      </button>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Test Suite: Provider Composition
// ---------------------------------------------------------------------------

describe("Provider Composition Contract", () => {
  beforeEach(() => {
    sessionStorage.clear();
    localStorage.clear();
    vi.clearAllMocks();
    vi.restoreAllMocks();

    // Mock window.matchMedia for theme preference
    Object.defineProperty(window, "matchMedia", {
      configurable: true,
      writable: true,
      value: vi.fn().mockReturnValue({
        matches: false,
        addEventListener: vi.fn(),
        removeEventListener: vi.fn(),
      }),
    });
  });

  afterEach(() => {
    vi.clearAllMocks();
  });

  // -----------------------------------------------------------------------
  // Test 1: ThemeProvider must be the outermost provider
  // -----------------------------------------------------------------------

  it("ThemeProvider supplies theme to AppContents for FluentProvider", () => {
    function InnerApp(): ReactElement {
      const { colorMode } = useThemeMode();
      return <div data-testid="color-output">{colorMode}</div>;
    }

    render(
      <ThemeProvider>
        <InnerApp />
      </ThemeProvider>,
    );

    const colorOutput = screen.getByTestId("color-output");
    expect(colorOutput.textContent).toMatch(/light|dark/);
  });

  it("FluentProvider must receive theme from useThemeMode", () => {
    function ConsumerComponent(): ReactElement {
      return <div data-testid="fluent-content">Fluent content rendered</div>;
    }

    const { container } = render(
      <ThemeProvider>
        <FluentProvider theme={webLightTheme}>
          <ConsumerComponent />
        </FluentProvider>
      </ThemeProvider>,
    );

    expect(screen.getByTestId("fluent-content")).toBeInTheDocument();
    // Verify FluentProvider rendered (check for Fluent-specific class)
    expect(container.querySelector("[class*='fui']")).toBeTruthy();
  });

  // -----------------------------------------------------------------------
  // Test 2: NotificationProvider must be after FluentProvider but before
  // error boundaries and content
  // -----------------------------------------------------------------------

  it("NotificationProvider makes useNotification available to children", () => {
    function NotificationConsumer(): ReactElement {
      const notification = useNotification();

      return (
        <button
          type="button"
          onClick={() => notification.success("Test")}
          data-testid="notify-btn"
        >
          Notify
        </button>
      );
    }

    render(
      <ThemeProvider>
        <FluentProvider theme={webLightTheme}>
          <NotificationProvider>
            <NotificationConsumer />
          </NotificationProvider>
        </FluentProvider>
      </ThemeProvider>,
    );

    expect(screen.getByTestId("notify-btn")).toBeInTheDocument();
  });

  it("throws error when useNotification is called outside NotificationProvider", () => {
    function BadComponent(): ReactElement {
      useNotification();
      return <div>This should not render</div>;
    }

    expect(() => {
      render(
        <ThemeProvider>
          <FluentProvider theme={webLightTheme}>
            <BadComponent />
          </FluentProvider>
        </ThemeProvider>,
      );
    }).toThrow("useNotification must be used within NotificationProvider");
  });

  // -----------------------------------------------------------------------
  // Test 3: AuthProvider must be inside BrowserRouter
  // -----------------------------------------------------------------------

  it("AuthProvider must be inside BrowserRouter (uses useNavigate internally)", () => {
    // This test verifies that AuthProvider can be placed inside BrowserRouter
    // by confirming it doesn't throw when nested correctly

    render(
      <MemoryRouter initialEntries={["/"]}>
        <FluentProvider theme={webLightTheme}>
          <AuthProvider>
            <Routes>
              <Route path="/" element={<div>Home</div>} />
            </Routes>
          </AuthProvider>
        </FluentProvider>
      </MemoryRouter>,
    );

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

  it("throws error when AuthProvider is outside BrowserRouter", () => {
    function RootComponent(): ReactElement {
      return (
        <AuthProvider>
          <div>Content</div>
        </AuthProvider>
      );
    }

    expect(() => {
      render(
        <FluentProvider theme={webLightTheme}>
          <RootComponent />
        </FluentProvider>,
      );
    }).toThrow(/useNavigate|useLocation|outside/i);
  });

  // -----------------------------------------------------------------------
  // Test 4: TimezoneProvider must be inside authenticated context
  // -----------------------------------------------------------------------

  it("TimezoneProvider provides timezone to consumers", () => {
    function TimezoneConsumer(): ReactElement {
      const timezone = useTimezone();
      return <div data-testid="tz-output">{timezone}</div>;
    }

    render(
      <TimezoneProvider>
        <TimezoneConsumer />
      </TimezoneProvider>,
    );

    const tzOutput = screen.getByTestId("tz-output");
    expect(tzOutput.textContent).toBeTruthy();
  });

  it("defaults to UTC when timezone data is loading or unavailable", async () => {
    function TimezoneDisplay(): ReactElement {
      const timezone = useTimezone();
      return <div data-testid="tz-display">{timezone}</div>;
    }

    render(
      <TimezoneProvider>
        <TimezoneDisplay />
      </TimezoneProvider>,
    );

    // Should default to UTC while loading
    const tzDisplay = screen.getByTestId("tz-display");
    expect(tzDisplay.textContent).toMatch(/UTC/);
  });

  // -----------------------------------------------------------------------
  // Test 5: Provider initialization order is respected
  // -----------------------------------------------------------------------

  it("renders all providers without errors when properly nested", () => {
    render(
      <ThemeProvider>
        <FluentProvider theme={webLightTheme}>
          <NotificationProvider>
            <MemoryRouter initialEntries={["/"]}>
              <AuthProvider>
                <TimezoneProvider>
                  <AllProvidersConsumer />
                </TimezoneProvider>
              </AuthProvider>
            </MemoryRouter>
          </NotificationProvider>
        </FluentProvider>
      </ThemeProvider>,
    );

    expect(screen.getByTestId("theme")).toBeInTheDocument();
    expect(screen.getByTestId("timezone")).toBeInTheDocument();
    expect(screen.getByTestId("notify-btn")).toBeInTheDocument();
  });

  // -----------------------------------------------------------------------
  // Test 6: Async provider initialization (AuthProvider)
  // -----------------------------------------------------------------------

  it("AuthProvider shows loading state while validating session", async () => {
    vi.spyOn(clientModule, "setUnauthorizedHandler").mockImplementation(
      (_handler: (() => void) | null) => {
        // Mock implementation
      },
    );

    sessionStorage.setItem("bangui_authenticated", "true");

    render(
      <MemoryRouter initialEntries={["/"]}>
        <FluentProvider theme={webLightTheme}>
          <AuthProvider>
            <Routes>
              <Route path="/" element={<div>Authenticated content</div>} />
            </Routes>
          </AuthProvider>
        </FluentProvider>
      </MemoryRouter>,
    );

    // AuthProvider should eventually render authenticated content
    await waitFor(() => {
      expect(screen.queryByText("Authenticated content")).toBeInTheDocument();
    });
  });

  // -----------------------------------------------------------------------
  // Test 7: Theme persistence across provider remounts
  // -----------------------------------------------------------------------

  it("persists theme selection across re-renders", () => {
    localStorage.setItem("bangui_theme", "dark");

    const { rerender } = render(
      <ThemeProvider>
        <div data-testid="theme-display">
          <ThemeDisplay />
        </div>
      </ThemeProvider>,
    );

    expect(screen.getByTestId("theme-color")).toHaveTextContent("dark");

    // Re-render should preserve theme
    rerender(
      <ThemeProvider>
        <div data-testid="theme-display">
          <ThemeDisplay />
        </div>
      </ThemeProvider>,
    );

    expect(screen.getByTestId("theme-color")).toHaveTextContent("dark");
  });

  // -----------------------------------------------------------------------
  // Test 8: Error boundary receives notifications
  // -----------------------------------------------------------------------

  it("NotificationProvider is accessible from error scenarios", () => {
    // This tests that NotificationProvider is positioned correctly
    // so that error boundaries and other error handlers can use it

    let serviceWorks = false;

    function CaptureNotification(): ReactElement {
      const notification = useNotification();
      // Just verify it's callable - don't call it as it mutates state
      serviceWorks = typeof notification.success === "function";
      return <div>Captured notification service</div>;
    }

    render(
      <ThemeProvider>
        <FluentProvider theme={webLightTheme}>
          <NotificationProvider>
            <CaptureNotification />
          </NotificationProvider>
        </FluentProvider>
      </ThemeProvider>,
    );

    expect(serviceWorks).toBe(true);
  });

  // -----------------------------------------------------------------------
  // Test 9: Full app provider stack renders without errors
  // -----------------------------------------------------------------------

  it("complete App provider stack mounts successfully", async () => {
    // Mock the session validation
    vi.spyOn(clientModule, "setUnauthorizedHandler").mockImplementation(
      (_handler: (() => void) | null) => {
        // Mock implementation
      },
    );

    // Note: This tests basic mounting; full App integration is tested in App.test.tsx
    render(
      <ThemeProvider>
        <FluentProvider theme={webLightTheme}>
          <NotificationProvider>
            <MemoryRouter initialEntries={["/"]}>
              <AuthProvider>
                <Routes>
                  <Route path="/" element={<div>Dashboard</div>} />
                </Routes>
              </AuthProvider>
            </MemoryRouter>
          </NotificationProvider>
        </FluentProvider>
      </ThemeProvider>,
    );

    await waitFor(() => {
      expect(screen.queryByText("Dashboard")).toBeInTheDocument();
    });
  });
});

// ---------------------------------------------------------------------------
// Helper Components
// ---------------------------------------------------------------------------

function ThemeDisplay(): ReactElement {
  const { colorMode } = useThemeMode();
  return <span data-testid="theme-color">{colorMode}</span>;
}
