import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, act } from "@testing-library/react";
import { ThemeProvider, useTheme } from "./ThemeProvider";

function ThemeConsumer(): React.JSX.Element {
  const { theme, themeId, themeDefinition, resolvedTheme, setTheme, toggleTheme } = useTheme();
  return (
    <div>
      <span data-testid="theme">{theme}</span>
      <span data-testid="themeId">{themeId}</span>
      <span data-testid="themeName">{themeDefinition.name}</span>
      <span data-testid="resolved">{resolvedTheme}</span>
      <button
        onClick={() => {
          setTheme("light");
        }}
      >
        Set Light
      </button>
      <button
        onClick={() => {
          setTheme("dark");
        }}
      >
        Set Dark
      </button>
      <button
        onClick={() => {
          setTheme("nord");
        }}
      >
        Set Nord
      </button>
      <button
        onClick={() => {
          setTheme("dracula");
        }}
      >
        Set Dracula
      </button>
      <button
        onClick={() => {
          setTheme("system");
        }}
      >
        Set System
      </button>
      <button
        onClick={() => {
          toggleTheme();
        }}
      >
        Toggle
      </button>
    </div>
  );
}

describe("ThemeProvider", (): void => {
  let mockMatchMedia: ReturnType<typeof vi.fn>;

  beforeEach((): void => {
    localStorage.clear();
    document.documentElement.removeAttribute("data-theme");
    // Clear any inline style properties set by theme application
    document.documentElement.removeAttribute("style");

    mockMatchMedia = vi.fn().mockReturnValue({
      matches: false,
      addEventListener: vi.fn(),
      removeEventListener: vi.fn(),
    });
    Object.defineProperty(window, "matchMedia", {
      writable: true,
      value: mockMatchMedia,
    });
  });

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

  it("should use 'mosaic-theme' as storage key", (): void => {
    localStorage.setItem("mosaic-theme", "light");

    render(
      <ThemeProvider>
        <ThemeConsumer />
      </ThemeProvider>
    );

    expect(screen.getByTestId("theme")).toHaveTextContent("light");
    expect(screen.getByTestId("themeId")).toHaveTextContent("light");
  });

  it("should NOT read from old 'jarvis-theme' storage key", (): void => {
    localStorage.setItem("jarvis-theme", "light");

    render(
      <ThemeProvider>
        <ThemeConsumer />
      </ThemeProvider>
    );

    expect(screen.getByTestId("theme")).toHaveTextContent("system");
  });

  it("should store theme under 'mosaic-theme' key", (): void => {
    render(
      <ThemeProvider>
        <ThemeConsumer />
      </ThemeProvider>
    );

    act(() => {
      screen.getByText("Set Light").click();
    });

    expect(localStorage.getItem("mosaic-theme")).toBe("light");
    expect(localStorage.getItem("jarvis-theme")).toBeNull();
  });

  it("should render children", (): void => {
    render(
      <ThemeProvider>
        <div data-testid="child">Hello</div>
      </ThemeProvider>
    );

    expect(screen.getByTestId("child")).toBeInTheDocument();
  });

  it("should throw when useTheme is used outside provider", (): void => {
    const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {
      // Intentionally empty
    });

    expect(() => {
      render(<ThemeConsumer />);
    }).toThrow("useTheme must be used within a ThemeProvider");

    consoleSpy.mockRestore();
  });

  it("should resolve 'system' to dark when OS prefers dark", (): void => {
    mockMatchMedia.mockReturnValue({
      matches: true,
      addEventListener: vi.fn(),
      removeEventListener: vi.fn(),
    });

    render(
      <ThemeProvider>
        <ThemeConsumer />
      </ThemeProvider>
    );

    expect(screen.getByTestId("themeId")).toHaveTextContent("dark");
    expect(screen.getByTestId("resolved")).toHaveTextContent("dark");
  });

  it("should resolve 'system' to light when OS prefers light", (): void => {
    mockMatchMedia.mockReturnValue({
      matches: false,
      addEventListener: vi.fn(),
      removeEventListener: vi.fn(),
    });

    render(
      <ThemeProvider>
        <ThemeConsumer />
      </ThemeProvider>
    );

    expect(screen.getByTestId("themeId")).toHaveTextContent("light");
    expect(screen.getByTestId("resolved")).toHaveTextContent("light");
  });

  it("should support Nord theme", (): void => {
    localStorage.setItem("mosaic-theme", "nord");

    render(
      <ThemeProvider>
        <ThemeConsumer />
      </ThemeProvider>
    );

    expect(screen.getByTestId("theme")).toHaveTextContent("nord");
    expect(screen.getByTestId("themeId")).toHaveTextContent("nord");
    expect(screen.getByTestId("themeName")).toHaveTextContent("Nord");
    expect(screen.getByTestId("resolved")).toHaveTextContent("dark");
  });

  it("should support Dracula theme", (): void => {
    localStorage.setItem("mosaic-theme", "dracula");

    render(
      <ThemeProvider>
        <ThemeConsumer />
      </ThemeProvider>
    );

    expect(screen.getByTestId("themeId")).toHaveTextContent("dracula");
    expect(screen.getByTestId("resolved")).toHaveTextContent("dark");
  });

  it("should support Solarized Dark theme", (): void => {
    localStorage.setItem("mosaic-theme", "solarized-dark");

    render(
      <ThemeProvider>
        <ThemeConsumer />
      </ThemeProvider>
    );

    expect(screen.getByTestId("themeId")).toHaveTextContent("solarized-dark");
    expect(screen.getByTestId("resolved")).toHaveTextContent("dark");
  });

  it("should fall back to system for unknown theme IDs", (): void => {
    localStorage.setItem("mosaic-theme", "nonexistent-theme");

    render(
      <ThemeProvider>
        <ThemeConsumer />
      </ThemeProvider>
    );

    // Falls back to "system" because "nonexistent-theme" is not a valid theme ID
    expect(screen.getByTestId("theme")).toHaveTextContent("system");
  });

  it("should switch between themes via setTheme", (): void => {
    render(
      <ThemeProvider>
        <ThemeConsumer />
      </ThemeProvider>
    );

    act(() => {
      screen.getByText("Set Nord").click();
    });

    expect(screen.getByTestId("themeId")).toHaveTextContent("nord");
    expect(screen.getByTestId("themeName")).toHaveTextContent("Nord");
    expect(localStorage.getItem("mosaic-theme")).toBe("nord");

    act(() => {
      screen.getByText("Set Dracula").click();
    });

    expect(screen.getByTestId("themeId")).toHaveTextContent("dracula");
    expect(localStorage.getItem("mosaic-theme")).toBe("dracula");
  });

  it("should toggle between dark and light", (): void => {
    localStorage.setItem("mosaic-theme", "dark");

    render(
      <ThemeProvider>
        <ThemeConsumer />
      </ThemeProvider>
    );

    expect(screen.getByTestId("resolved")).toHaveTextContent("dark");

    act(() => {
      screen.getByText("Toggle").click();
    });

    expect(screen.getByTestId("themeId")).toHaveTextContent("light");
    expect(screen.getByTestId("resolved")).toHaveTextContent("light");
  });

  it("should toggle from a dark theme (nord) to light", (): void => {
    localStorage.setItem("mosaic-theme", "nord");

    render(
      <ThemeProvider>
        <ThemeConsumer />
      </ThemeProvider>
    );

    expect(screen.getByTestId("resolved")).toHaveTextContent("dark");

    act(() => {
      screen.getByText("Toggle").click();
    });

    expect(screen.getByTestId("themeId")).toHaveTextContent("light");
    expect(screen.getByTestId("resolved")).toHaveTextContent("light");
  });

  it("should apply CSS variables on theme change", (): void => {
    render(
      <ThemeProvider>
        <ThemeConsumer />
      </ThemeProvider>
    );

    act(() => {
      screen.getByText("Set Nord").click();
    });

    // Nord's bg-900 is #2e3440
    const bgValue = document.documentElement.style.getPropertyValue("--ms-bg-900");
    expect(bgValue).toBe("#2e3440");
  });

  it("should set data-theme attribute based on isDark", (): void => {
    render(
      <ThemeProvider>
        <ThemeConsumer />
      </ThemeProvider>
    );

    act(() => {
      screen.getByText("Set Nord").click();
    });

    expect(document.documentElement.getAttribute("data-theme")).toBe("dark");

    act(() => {
      screen.getByText("Set Light").click();
    });

    expect(document.documentElement.getAttribute("data-theme")).toBe("light");
  });

  it("should expose themeDefinition with full theme data", (): void => {
    localStorage.setItem("mosaic-theme", "dark");

    render(
      <ThemeProvider>
        <ThemeConsumer />
      </ThemeProvider>
    );

    expect(screen.getByTestId("themeName")).toHaveTextContent("Dark");
  });
});
