/**
 * Tests for Provider Order Validator
 *
 * Validates runtime checking of provider ordering through context tracking.
 */

import { describe, it, expect, beforeEach, vi } from "vitest";
import { render } from "@testing-library/react";
import {
  validateProviderPosition,
  validateProvidersExist,
  hasProvider,
  getCurrentProviders,
  createProviderTracker,
  useProviderValidation,
} from "../providerOrderValidator";

describe("ProviderOrderValidator", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  // -----------------------------------------------------------------------
  // Test 1: validateProviderPosition detects missing parent providers
  // -----------------------------------------------------------------------

  it("throws error when provider parent is missing", () => {
    function InvalidComponent(): React.JSX.Element {
      validateProviderPosition("AuthProvider", 6);
      return <div>Should not render</div>;
    }

    expect(() => {
      render(<InvalidComponent />);
    }).toThrow(/missing required parent providers/i);
  });

  it("allows ThemeProvider at position 0 without parent tracking", () => {
    function ValidThemeComponent(): React.JSX.Element {
      validateProviderPosition("ThemeProvider", 0);
      return <div data-testid="theme">Theme OK</div>;
    }

    const { getByTestId } = render(<ValidThemeComponent />);
    expect(getByTestId("theme")).toBeInTheDocument();
  });

  // -----------------------------------------------------------------------
  // Test 2: validateProvidersExist checks for required providers
  // -----------------------------------------------------------------------

  it("throws error when required providers are missing", () => {
    function NeedsProviders(): React.JSX.Element {
      validateProvidersExist(["AuthProvider", "NotificationProvider"]);
      return <div>Should not render</div>;
    }

    expect(() => {
      render(<NeedsProviders />);
    }).toThrow(/missing required providers/i);
  });

  it("allows validation when tracking context exists with all required providers", () => {
    function ValidatedComponent(): React.JSX.Element {
      validateProvidersExist(["ThemeProvider"]);
      return <div data-testid="valid">Valid</div>;
    }

    const composition = (
      <div>
        {createProviderTracker("ThemeProvider", 0, <ValidatedComponent />)}
      </div>
    );

    const { getByTestId } = render(composition);
    expect(getByTestId("valid")).toBeInTheDocument();
  });

  // -----------------------------------------------------------------------
  // Test 3: hasProvider checks provider presence
  // -----------------------------------------------------------------------

  it("returns true when provider is present", () => {
    let result = false;

    function CheckProvider(): React.JSX.Element {
      result = hasProvider("ThemeProvider");
      return <div>Check done</div>;
    }

    render(
      createProviderTracker("ThemeProvider", 0, <CheckProvider />)
    );

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

  it("returns false when provider is not present", () => {
    let result = true;

    function CheckProvider(): React.JSX.Element {
      result = hasProvider("TimezoneProvider");
      return <div>Check done</div>;
    }

    render(
      createProviderTracker("ThemeProvider", 0, <CheckProvider />)
    );

    expect(result).toBe(false);
  });

  // -----------------------------------------------------------------------
  // Test 4: getCurrentProviders returns providers in order
  // -----------------------------------------------------------------------

  it("returns empty array when no providers are tracked", () => {
    let providers: string[] = [];

    function CheckProviders(): React.JSX.Element {
      const result = getCurrentProviders();
      providers = result;
      return <div>Check done</div>;
    }

    render(<CheckProviders />);
    expect(providers).toEqual([]);
  });

  it("returns providers in correct order when tracked", () => {
    let providers: string[] = [];

    function CheckProviders(): React.JSX.Element {
      const result = getCurrentProviders();
      providers = result;
      return <div>Check done</div>;
    }

    const composition = (
      <div>
        {createProviderTracker("ThemeProvider", 0, 
          createProviderTracker("FluentProvider", 1,
            createProviderTracker("NotificationProvider", 2, <CheckProviders />)
          )
        )}
      </div>
    );

    render(composition);
    expect(providers).toEqual(["ThemeProvider", "FluentProvider", "NotificationProvider"]);
  });

  // -----------------------------------------------------------------------
  // Test 5: createProviderTracker validates positioning
  // -----------------------------------------------------------------------

  it("throws error for invalid position in createProviderTracker", () => {
    function Component(): React.JSX.Element {
      return <div>Invalid</div>;
    }

    // The error should be thrown during render, not construction
    expect(() => {
      // Using any to test invalid position despite type checking
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      render(createProviderTracker("ThemeProvider" as any, 999, <Component />));
    }).toThrow(/invalid position/i);
  });

  it("throws error when parent provider is missing in tracker", () => {
    function Component(): React.JSX.Element {
      return <div>Should not render</div>;
    }

    expect(() => {
      // NotificationProvider (position 2) requires FluentProvider (position 1)
      // but we're skipping FluentProvider
      render(
        <div>
          {createProviderTracker("ThemeProvider", 0,
            createProviderTracker("NotificationProvider", 2, <Component />)
          )}
        </div>
      );
    }).toThrow(/incorrect parent structure|missing/i);
  });

  // -----------------------------------------------------------------------
  // Test 6: useProviderValidation hook validates providers
  // -----------------------------------------------------------------------

  it("logs warning in development when required providers are missing", () => {
    const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});

    function ComponentNeedsAuth(): React.JSX.Element {
      useProviderValidation("ComponentNeedsAuth", ["AuthProvider"]);
      return <div>Checking</div>;
    }

    render(<ComponentNeedsAuth />);

    // In development, should warn about missing provider
    const hasWarning = warnSpy.mock.calls.some((call) =>
      String(call[0] || "").includes("ComponentNeedsAuth") ||
      String(call[0] || "").includes("missing")
    );

    // Only verify warning if NODE_ENV is development
    if (process.env.NODE_ENV === "development") {
      expect(hasWarning).toBe(true);
    }

    warnSpy.mockRestore();
  });

  it("does not warn when all required providers are present", () => {
    const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});

    function ComponentNeedsAuth(): React.JSX.Element {
      useProviderValidation("ComponentNeedsAuth", ["ThemeProvider"]);
      return <div data-testid="ok">OK</div>;
    }

    const composition = (
      <div>
        {createProviderTracker("ThemeProvider", 0,
          createProviderTracker("FluentProvider", 1, <ComponentNeedsAuth />)
        )}
      </div>
    );

    render(composition);

    // Should not warn about missing ThemeProvider
    const warnCalls = warnSpy.mock.calls;
    const hasThemeWarning = warnCalls.some((call) =>
      String(call[0] || "").includes("missing") && String(call[0] || "").includes("ThemeProvider")
    );

    expect(hasThemeWarning).toBe(false);

    warnSpy.mockRestore();
  });

  // -----------------------------------------------------------------------
  // Test 7: Nested provider tracking
  // -----------------------------------------------------------------------

  it("correctly tracks nested providers through multiple levels", () => {
    let capturedProviders: string[] = [];

    function InnerComponent(): React.JSX.Element {
      capturedProviders = getCurrentProviders();
      return <div data-testid="inner">Inner</div>;
    }

    const composition = (
      <div>
        {createProviderTracker("ThemeProvider", 0,
          createProviderTracker("FluentProvider", 1,
            createProviderTracker("NotificationProvider", 2,
              createProviderTracker("ErrorBoundary", 3,
                createProviderTracker("BrowserRouter", 4, <InnerComponent />)
              )
            )
          )
        )}
      </div>
    );

    const { getByTestId } = render(composition);
    expect(getByTestId("inner")).toBeInTheDocument();
    expect(capturedProviders).toEqual([
      "ThemeProvider",
      "FluentProvider",
      "NotificationProvider",
      "ErrorBoundary",
      "BrowserRouter",
    ]);
  });

  // -----------------------------------------------------------------------
  // Test 8: Validation errors are descriptive
  // -----------------------------------------------------------------------

  it("provides helpful error messages for missing providers", () => {
    let errorCaught = false;

    function Component(): React.JSX.Element {
      try {
        validateProvidersExist(["AuthProvider", "TimezoneProvider"]);
      } catch (error) {
        if (error instanceof Error) {
          expect(error.message).toMatch(/missing required providers/i);
          errorCaught = true;
        }
      }
      return <div>Error caught</div>;
    }

    render(<Component />);
    expect(errorCaught).toBe(true);
  });

  it("provides helpful error messages for incorrect positioning", () => {
    let errorCaught = false;

    function Component(): React.JSX.Element {
      try {
        validateProviderPosition("AuthProvider", 6);
      } catch (error) {
        if (error instanceof Error) {
          expect(error.message).toMatch(/missing required parent/i);
          errorCaught = true;
        }
      }
      return <div>Error caught</div>;
    }

    render(<Component />);
    expect(errorCaught).toBe(true);
  });

  // -----------------------------------------------------------------------
  // Test 9: Invalid position numbers are rejected
  // -----------------------------------------------------------------------

  it("throws error for negative position", () => {
    function Component(): React.JSX.Element {
      try {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        validateProviderPosition("InvalidProvider" as any, -1);
      } catch (error) {
        if (error instanceof Error) {
          expect(error.message).toMatch(/invalid expected position/i);
        }
      }
      return <div>Error caught</div>;
    }

    render(<Component />);
  });

  it("throws error for position beyond sequence length", () => {
    function Component(): React.JSX.Element {
      try {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        validateProviderPosition("InvalidProvider" as any, 999);
      } catch (error) {
        if (error instanceof Error) {
          expect(error.message).toMatch(/invalid expected position/i);
        }
      }
      return <div>Error caught</div>;
    }

    render(<Component />);
  });
});
