import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render } from "solid-js/web";
import type { JSX } from "solid-js";

import Button from "./Button";
import Card from "./Card";
import Input from "./Input";
import Badge from "./Badge";
import Modal from "./Modal";
import { ToastProvider, useToast } from "./Toast";

function mount(comp: () => JSX.Element): HTMLDivElement {
  const container = document.createElement("div");
  document.body.appendChild(container);
  render(() => comp(), container);
  return container;
}

beforeEach(() => {
  document.body.innerHTML = "";
  if (!globalThis.crypto) {
    Object.defineProperty(globalThis, "crypto", { value: {} });
  }
  (globalThis.crypto as unknown as Record<string, unknown>).randomUUID = vi.fn(
    () => "test-uuid-1234",
  );
});

afterEach(() => {
  document.body.innerHTML = "";
});

describe("Button", () => {
  it("renders with default props", () => {
    mount(() => <Button>Click me</Button>);
    const btn = document.querySelector("button")!;
    expect(btn.textContent).toContain("Click me");
    expect(btn.getAttribute("type")).toBe("button");
    expect(btn.className).toContain("gradient-primary");
  });

  it("applies primary variant by default", () => {
    mount(() => <Button>Primary</Button>);
    const btn = document.querySelector("button")!;
    expect(btn.className).toContain("gradient-primary");
    expect(btn.className).toContain("text-white");
  });

  it("applies secondary variant classes", () => {
    mount(() => <Button variant="secondary">Secondary</Button>);
    const btn = document.querySelector("button")!;
    expect(btn.className).toContain("bg-transparent");
    expect(btn.className).toContain("border");
  });

  it("applies ghost variant classes", () => {
    mount(() => <Button variant="ghost">Ghost</Button>);
    const btn = document.querySelector("button")!;
    expect(btn.className).toContain("bg-transparent");
  });

  it("applies danger variant classes", () => {
    mount(() => <Button variant="danger">Danger</Button>);
    const btn = document.querySelector("button")!;
    expect(btn.className).toContain("bg-[var(--color-danger)]");
  });

  it("applies size classes", () => {
    mount(() => <Button size="sm">Small</Button>);
    const btn = document.querySelector("button")!;
    expect(btn.className).toContain("px-3");
  });

  it("applies lg size classes", () => {
    mount(() => <Button size="lg">Large</Button>);
    const btn = document.querySelector("button")!;
    expect(btn.className).toContain("px-6");
  });

  it("disables button when disabled prop is true", () => {
    mount(() => <Button disabled>Disabled</Button>);
    expect(document.querySelector("button")!.disabled).toBe(true);
  });

  it("shows spinner when loading", () => {
    mount(() => <Button loading>Loading</Button>);
    const btn = document.querySelector("button")!;
    expect(btn.disabled).toBe(true);
    expect(btn.querySelector("svg")).toBeTruthy();
  });

  it("fires onClick handler", () => {
    const onClick = vi.fn();
    mount(() => <Button onClick={onClick}>Click</Button>);
    document.querySelector("button")!.click();
    expect(onClick).toHaveBeenCalledTimes(1);
  });

  it("does not fire onClick when disabled", () => {
    const onClick = vi.fn();
    mount(() => <Button onClick={onClick} disabled>Click</Button>);
    document.querySelector("button")!.click();
    expect(onClick).not.toHaveBeenCalled();
  });

  it("merges custom class", () => {
    mount(() => <Button class="my-custom">Styled</Button>);
    expect(document.querySelector("button")!.className).toContain("my-custom");
  });
});

describe("Card", () => {
  it("renders children", () => {
    mount(() => <Card><p>Hello World</p></Card>);
    expect(document.body.textContent).toContain("Hello World");
    expect(document.querySelector(".gradient-card")).toBeTruthy();
  });

  it("renders header slot", () => {
    mount(() => <Card header={<span>Header Text</span>}><p>Body</p></Card>);
    expect(document.body.textContent).toContain("Header Text");
    expect(document.body.textContent).toContain("Body");
  });

  it("renders footer slot", () => {
    mount(() => <Card footer={<span>Footer Text</span>}><p>Body</p></Card>);
    expect(document.body.textContent).toContain("Footer Text");
    expect(document.body.textContent).toContain("Body");
  });

  it("merges custom class", () => {
    mount(() => <Card class="my-card">X</Card>);
    expect(document.body.querySelector(".my-card")).toBeTruthy();
  });
});

describe("Input", () => {
  it("renders with label", () => {
    mount(() => <Input label="Email" />);
    expect(document.body.textContent).toContain("Email");
    expect(document.querySelector("label")).toBeTruthy();
  });

  it("renders error message and applies error border", () => {
    mount(() => <Input label="Email" error="Invalid email" />);
    expect(document.body.textContent).toContain("Invalid email");
    const input = document.querySelector("input")!;
    expect(input.className).toContain("border-[var(--color-error)]");
  });

  it("renders helper text when no error", () => {
    mount(() => (
      <Input label="Email" helperText="We will never share your email." />
    ));
    expect(document.body.textContent).toContain(
      "We will never share your email.",
    );
  });

  it("hides helper text when error is present", () => {
    mount(() => (
      <Input label="Email" error="Invalid" helperText="Helper" />
    ));
    expect(document.body.textContent).toContain("Invalid");
    expect(document.body.textContent).not.toContain("Helper");
  });

  it("renders input with correct type", () => {
    mount(() => <Input type="password" />);
    expect(document.querySelector("input")!.getAttribute("type")).toBe(
      "password",
    );
  });

  it("shows required indicator", () => {
    mount(() => <Input label="Name" required />);
    expect(document.body.textContent).toContain("*");
  });

  it("uses id prop when provided", () => {
    mount(() => <Input id="my-input" label="Label" />);
    expect(document.querySelector("input")!.id).toBe("my-input");
    expect(document.querySelector("label")!.getAttribute("for")).toBe(
      "my-input",
    );
  });

  it("uses name as id fallback", () => {
    mount(() => <Input name="email" label="Email" />);
    expect(document.querySelector("input")!.id).toBe("email");
  });
});

describe("Badge", () => {
  it("renders children", () => {
    mount(() => <Badge>Active</Badge>);
    const badge = document.querySelector("span")!;
    expect(badge.textContent).toContain("Active");
    expect(badge.className).toContain("rounded-full");
  });

  it("applies success variant", () => {
    mount(() => <Badge variant="success">OK</Badge>);
    expect(document.querySelector("span")!.className).toContain(
      "bg-[var(--color-success-bg)]",
    );
  });

  it("applies error variant", () => {
    mount(() => <Badge variant="error">Fail</Badge>);
    expect(document.querySelector("span")!.className).toContain(
      "bg-[var(--color-error-bg)]",
    );
  });

  it("applies warning variant", () => {
    mount(() => <Badge variant="warning">Warn</Badge>);
    expect(document.querySelector("span")!.className).toContain(
      "bg-[var(--color-warning-bg)]",
    );
  });

  it("applies info variant", () => {
    mount(() => <Badge variant="info">Info</Badge>);
    expect(document.querySelector("span")!.className).toContain(
      "bg-[var(--color-info-bg)]",
    );
  });

  it("defaults to default variant", () => {
    mount(() => <Badge>Default</Badge>);
    expect(document.querySelector("span")!.className).toContain(
      "bg-[var(--color-bg-secondary)]",
    );
  });

  it("merges custom class", () => {
    mount(() => <Badge class="my-badge">X</Badge>);
    expect(document.querySelector("span")!.className).toContain("my-badge");
  });
});

describe("Modal", () => {
  beforeEach(() => {
    vi.useFakeTimers({ shouldAdvanceTime: true });
  });

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

  it("does not render when closed", () => {
    mount(() => (
      <Modal isOpen={false} onClose={() => {}}>
        <p>Secret</p>
      </Modal>
    ));
    vi.advanceTimersByTime(32);
    expect(document.body.textContent).not.toContain("Secret");
  });

  it("renders when open", () => {
    mount(() => (
      <Modal isOpen={true} onClose={() => {}}>
        <p>Visible</p>
      </Modal>
    ));
    vi.advanceTimersByTime(32);
    expect(document.body.textContent).toContain("Visible");
  });

  it("renders title", () => {
    mount(() => (
      <Modal isOpen={true} onClose={() => {}} title="My Title">
        <p>Content</p>
      </Modal>
    ));
    vi.advanceTimersByTime(32);
    expect(document.body.textContent).toContain("My Title");
  });

  it("calls onClose when Escape is pressed", () => {
    const onClose = vi.fn();
    mount(() => (
      <Modal isOpen={true} onClose={onClose}>
        <p>Content</p>
      </Modal>
    ));
    vi.advanceTimersByTime(32);

    const overlay = document.body.querySelector("[role='dialog']")!
      .parentElement!;
    overlay.dispatchEvent(
      new KeyboardEvent("keydown", { key: "Escape", bubbles: true }),
    );
    expect(onClose).toHaveBeenCalled();
  });

  it("calls onClose when backdrop is clicked", () => {
    const onClose = vi.fn();
    mount(() => (
      <Modal isOpen={true} onClose={onClose}>
        <p>Content</p>
      </Modal>
    ));
    vi.advanceTimersByTime(32);

    const dialog = document.body.querySelector("[role='dialog']")!;
    const overlay = dialog.parentElement!;
    overlay.dispatchEvent(new MouseEvent("click", { bubbles: true }));
    expect(onClose).toHaveBeenCalled();
  });

  it("does not call onClose when clicking inside modal content", () => {
    const onClose = vi.fn();
    mount(() => (
      <Modal isOpen={true} onClose={onClose}>
        <p>Content</p>
      </Modal>
    ));
    vi.advanceTimersByTime(32);

    const dialog = document.body.querySelector("[role='dialog']")!;
    dialog.dispatchEvent(new MouseEvent("click", { bubbles: true }));
    expect(onClose).not.toHaveBeenCalled();
  });

  it("sets aria attributes for accessibility", () => {
    mount(() => (
      <Modal isOpen={true} onClose={() => {}} title="Accessible">
        <p>Content</p>
      </Modal>
    ));
    vi.advanceTimersByTime(32);
    const dialog = document.body.querySelector("[role='dialog']")!;
    expect(dialog.getAttribute("aria-modal")).toBe("true");
    expect(dialog.getAttribute("aria-label")).toBe("Accessible");
  });

  it("renders with Portal to document.body", () => {
    const container = document.createElement("div");
    document.body.appendChild(container);
    render(
      () => (
        <Modal isOpen={true} onClose={() => {}}>
          <p>Portal content</p>
        </Modal>
      ),
      container,
    );
    vi.advanceTimersByTime(32);

    expect(document.body.textContent).toContain("Portal content");
    container.remove();
  });
});

describe("Toast", () => {
  beforeEach(() => {
    vi.useFakeTimers({ shouldAdvanceTime: true });
  });

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

  it("provides context to children", () => {
    let captured: { showToast: unknown; dismissToast: unknown } | undefined;
    const Child = () => {
      captured = useToast();
      return <div>child</div>;
    };

    mount(() => (
      <ToastProvider>
        <Child />
      </ToastProvider>
    ));

    expect(captured).toBeDefined();
    expect(typeof captured!.showToast).toBe("function");
    expect(typeof captured!.dismissToast).toBe("function");
  });

  it("throws when useToast is used outside provider", () => {
    expect(() => {
      const Bad = () => {
        useToast();
        return <div />;
      };
      mount(() => <Bad />);
    }).toThrow("useToast must be used within a ToastProvider");
  });

  it("shows a toast", () => {
    let show!: (msg: string) => string;
    const Child = () => {
      const toast = useToast();
      show = (msg) => toast.showToast(msg);
      return <div>child</div>;
    };

    mount(() => (
      <ToastProvider>
        <Child />
      </ToastProvider>
    ));

    show("Hello Toast");
    vi.advanceTimersByTime(0);
    expect(document.body.textContent).toContain("Hello Toast");
  });

  it("dismisses a toast manually", () => {
    let show!: (msg: string) => string;
    let dismiss!: (id: string) => void;
    const Child = () => {
      const toast = useToast();
      show = (msg) => toast.showToast(msg);
      dismiss = (id) => toast.dismissToast(id);
      return <div>child</div>;
    };

    mount(() => (
      <ToastProvider>
        <Child />
      </ToastProvider>
    ));

    const id = show("Temp");
    vi.advanceTimersByTime(0);
    expect(document.body.textContent).toContain("Temp");

    dismiss(id);
    vi.advanceTimersByTime(0);
    expect(document.body.textContent).not.toContain("Temp");
  });

  it("auto-dismisses toast after duration", () => {
    let show!: (msg: string, variant?: string, duration?: number) => string;
    const Child = () => {
      const toast = useToast();
      show = (msg, variant, duration) =>
        toast.showToast(msg, variant as "info", duration);
      return <div>child</div>;
    };

    mount(() => (
      <ToastProvider>
        <Child />
      </ToastProvider>
    ));

    show("Auto", "info", 1000);
    vi.advanceTimersByTime(0);
    expect(document.body.textContent).toContain("Auto");

    vi.advanceTimersByTime(1000);
    expect(document.body.textContent).not.toContain("Auto");
  });

  it("shows multiple toasts", () => {
    let show!: (msg: string) => string;
    const Child = () => {
      const toast = useToast();
      show = (msg) => toast.showToast(msg);
      return <div>{toast.toasts().length}</div>;
    };

    mount(() => (
      <ToastProvider>
        <Child />
      </ToastProvider>
    ));

    show("First");
    show("Second");
    vi.advanceTimersByTime(0);

    expect(document.body.textContent).toContain("First");
    expect(document.body.textContent).toContain("Second");
  });

  it("renders with correct variant styles", () => {
    let show!: (msg: string, variant: string) => string;
    const Child = () => {
      const toast = useToast();
      show = (msg, variant) => toast.showToast(msg, variant as "error");
      return <div>child</div>;
    };

    mount(() => (
      <ToastProvider>
        <Child />
      </ToastProvider>
    ));

    show("Error!", "error");
    vi.advanceTimersByTime(0);

    const toastEl = document.body.querySelector("[role='alert']")!;
    expect(toastEl.className).toContain("bg-[var(--color-error-bg)]");
  });
});
