import { describe, it, expect, beforeEach, vi } from "vitest";
import { createRoot } from "solid-js";
import {
  useTheme,
  getSystemTheme,
  getStoredTheme,
  getResolvedTheme,
  applyThemeClass,
  updateMetaThemeColor,
  persistTheme,
} from "./theme";

function createLocalStorageMock() {
  const store = new Map<string, string>();
  return {
    getItem: (key: string) => store.get(key) ?? null,
    setItem: (key: string, value: string) => store.set(key, value),
    removeItem: (key: string) => store.delete(key),
    clear: () => store.clear(),
  };
}

function setupDOM() {
  document.documentElement.classList.remove("light", "dark");
  document
    .querySelectorAll('meta[name="theme-color"]')
    .forEach((el) => el.remove());
  const ls = createLocalStorageMock();
  vi.stubGlobal("localStorage", ls);
}

function createMatchMediaMock(matches: boolean) {
  return vi.fn().mockImplementation((query: string) => ({
    matches,
    media: query,
    onchange: null,
    addEventListener: vi.fn(),
    removeEventListener: vi.fn(),
    addListener: vi.fn(),
    removeListener: vi.fn(),
    dispatchEvent: vi.fn(),
  }));
}

function runWithRoot<T>(fn: () => T): T {
  return createRoot((dispose) => {
    const result = fn();
    return result;
  }) as T;
}

describe("getSystemTheme", () => {
  it("returns 'light' when prefers-color-scheme is light", () => {
    vi.stubGlobal("matchMedia", createMatchMediaMock(false));
    expect(getSystemTheme()).toBe("light");
  });

  it("returns 'dark' when prefers-color-scheme is dark", () => {
    vi.stubGlobal("matchMedia", createMatchMediaMock(true));
    expect(getSystemTheme()).toBe("dark");
  });
});

describe("getStoredTheme", () => {
  beforeEach(() => {
    setupDOM();
  });

  it("returns 'system' when nothing is stored", () => {
    expect(getStoredTheme()).toBe("system");
  });

  it("returns 'light' when stored", () => {
    localStorage.setItem("shieldai-theme", "light");
    expect(getStoredTheme()).toBe("light");
  });

  it("returns 'dark' when stored", () => {
    localStorage.setItem("shieldai-theme", "dark");
    expect(getStoredTheme()).toBe("dark");
  });

  it("returns 'system' for invalid value", () => {
    localStorage.setItem("shieldai-theme", "invalid");
    expect(getStoredTheme()).toBe("system");
  });
});

describe("getResolvedTheme", () => {
  it("resolves explicit theme directly", () => {
    vi.stubGlobal("matchMedia", createMatchMediaMock(true));
    expect(getResolvedTheme("light")).toBe("light");
    expect(getResolvedTheme("dark")).toBe("dark");
  });

  it("resolves 'system' to OS preference", () => {
    vi.stubGlobal("matchMedia", createMatchMediaMock(false));
    expect(getResolvedTheme("system")).toBe("light");
  });

  it("resolves 'system' to dark with dark OS", () => {
    vi.stubGlobal("matchMedia", createMatchMediaMock(true));
    expect(getResolvedTheme("system")).toBe("dark");
  });
});

describe("applyThemeClass", () => {
  beforeEach(() => {
    setupDOM();
  });

  it("adds 'light' class and removes 'dark'", () => {
    document.documentElement.classList.add("dark");
    applyThemeClass("light");
    expect(document.documentElement.classList.contains("light")).toBe(true);
    expect(document.documentElement.classList.contains("dark")).toBe(false);
  });

  it("adds 'dark' class and removes 'light'", () => {
    document.documentElement.classList.add("light");
    applyThemeClass("dark");
    expect(document.documentElement.classList.contains("dark")).toBe(true);
    expect(document.documentElement.classList.contains("light")).toBe(false);
  });
});

describe("updateMetaThemeColor", () => {
  beforeEach(() => {
    setupDOM();
  });

  it("creates a theme-color meta tag for light mode", () => {
    updateMetaThemeColor("light");
    const meta = document.querySelector('meta[name="theme-color"]');
    expect(meta).not.toBeNull();
    expect(meta!.getAttribute("content")).toBe("#fafbfc");
  });

  it("creates a theme-color meta tag for dark mode", () => {
    updateMetaThemeColor("dark");
    const meta = document.querySelector('meta[name="theme-color"]');
    expect(meta).not.toBeNull();
    expect(meta!.getAttribute("content")).toBe("#111827");
  });

  it("updates existing meta tag instead of creating new one", () => {
    updateMetaThemeColor("light");
    updateMetaThemeColor("dark");
    const metas = document.querySelectorAll('meta[name="theme-color"]');
    expect(metas.length).toBe(1);
    expect(metas[0].getAttribute("content")).toBe("#111827");
  });
});

describe("persistTheme", () => {
  beforeEach(() => {
    setupDOM();
  });

  it("writes theme to localStorage", () => {
    persistTheme("dark");
    expect(localStorage.getItem("shieldai-theme")).toBe("dark");
  });

  it("writes 'system' to localStorage", () => {
    persistTheme("system");
    expect(localStorage.getItem("shieldai-theme")).toBe("system");
  });
});

describe("useTheme", () => {
  beforeEach(() => {
    setupDOM();
  });

  describe("initial theme", () => {
    it("returns 'system' when no theme is stored", () => {
      vi.stubGlobal("matchMedia", createMatchMediaMock(false));
      runWithRoot(() => {
        const { theme } = useTheme();
        expect(theme()).toBe("system");
      });
    });

    it("returns 'light' from localStorage", () => {
      vi.stubGlobal("matchMedia", createMatchMediaMock(false));
      localStorage.setItem("shieldai-theme", "light");
      runWithRoot(() => {
        const { theme } = useTheme();
        expect(theme()).toBe("light");
      });
    });

    it("returns 'dark' from localStorage", () => {
      vi.stubGlobal("matchMedia", createMatchMediaMock(false));
      localStorage.setItem("shieldai-theme", "dark");
      runWithRoot(() => {
        const { theme } = useTheme();
        expect(theme()).toBe("dark");
      });
    });
  });

  describe("resolved theme", () => {
    it("resolves 'system' to 'light' with light OS preference", () => {
      vi.stubGlobal("matchMedia", createMatchMediaMock(false));
      runWithRoot(() => {
        const { resolved } = useTheme();
        expect(resolved()).toBe("light");
      });
    });

    it("resolves 'system' to 'dark' with dark OS preference", () => {
      vi.stubGlobal("matchMedia", createMatchMediaMock(true));
      runWithRoot(() => {
        const { resolved } = useTheme();
        expect(resolved()).toBe("dark");
      });
    });
  });

  describe("setTheme", () => {
    it("sets theme signal and persists to localStorage", () => {
      vi.stubGlobal("matchMedia", createMatchMediaMock(false));
      runWithRoot(() => {
        const { setTheme, theme } = useTheme();
        setTheme("dark");
        expect(theme()).toBe("dark");
        expect(localStorage.getItem("shieldai-theme")).toBe("dark");
      });
    });

    it("persists 'system' to localStorage", () => {
      vi.stubGlobal("matchMedia", createMatchMediaMock(false));
      runWithRoot(() => {
        const { setTheme } = useTheme();
        setTheme("system");
        expect(localStorage.getItem("shieldai-theme")).toBe("system");
      });
    });
  });

  describe("toggle", () => {
    it("toggles from light to dark", () => {
      vi.stubGlobal("matchMedia", createMatchMediaMock(false));
      localStorage.setItem("shieldai-theme", "light");
      runWithRoot(() => {
        const { toggle, resolved, theme } = useTheme();
        toggle();
        expect(resolved()).toBe("dark");
        expect(theme()).toBe("dark");
      });
    });

    it("toggles from dark to light", () => {
      vi.stubGlobal("matchMedia", createMatchMediaMock(true));
      localStorage.setItem("shieldai-theme", "dark");
      runWithRoot(() => {
        const { toggle, resolved, theme } = useTheme();
        toggle();
        expect(resolved()).toBe("light");
        expect(theme()).toBe("light");
      });
    });
  });

  describe("system theme listener", () => {
    it("registers matchMedia change listener", () => {
      const addEventListener = vi.fn();
      const removeEventListener = vi.fn();
      vi.stubGlobal(
        "matchMedia",
        vi.fn().mockReturnValue({
          matches: false,
          addEventListener,
          removeEventListener,
        }),
      );
      runWithRoot(() => {
        useTheme();
        expect(addEventListener).toHaveBeenCalledWith(
          "change",
          expect.any(Function),
        );
      });
    });
  });
});
