// @vitest-environment happy-dom
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render } from "@testing-library/react";
import { cleanup } from "@testing-library/react";

vi.mock("./useAuth", () => ({
  useAuth: vi.fn(),
}));

import { useAuth } from "./useAuth";
import { RoleGate } from "./RoleGate";

function mockUser(role: "admin" | "user" | "viewer" | null) {
  (useAuth as unknown as ReturnType<typeof vi.fn>).mockReturnValue({
    user: role ? { id: 1, username: "x", role } : null,
    status: role ? "authenticated" : "unauthenticated",
  });
}

describe("RoleGate", () => {
  beforeEach(() => {
    vi.resetAllMocks();
    cleanup();
  });

  it("renders children when role is allowed", () => {
    mockUser("admin");
    const { container } = render(
      <RoleGate allow={["admin", "user"]}>
        <button>Do thing</button>
      </RoleGate>
    );
    expect(container.querySelector("button")).not.toBeNull();
    expect(container.textContent).toContain("Do thing");
  });

  it("renders disabled (via span wrap) when role is NOT allowed", () => {
    mockUser("viewer");
    const { container } = render(
      <RoleGate allow={["admin", "user"]}>
        <button>Do thing</button>
      </RoleGate>
    );
    const buttons = container.querySelectorAll("button");
    expect(buttons.length).toBeGreaterThan(0);
    // The single child element is wrapped in a span that carries the
    // disabled visual signal — opacity-50 plus pointer-events-none so
    // the inner control can't be clicked.
    const wrapper = container.querySelector("span");
    expect(wrapper).not.toBeNull();
    expect(wrapper?.className).toContain("opacity-50");
    expect(wrapper?.className).toContain("pointer-events-none");
    // The wrapper also carries a native `title` attribute that
    // explains why the affordance is disabled.
    expect(wrapper?.getAttribute("title")).toMatch(/role \(viewer\) cannot perform this action/);
  });

  it("renders fallback when provided and role not allowed", () => {
    mockUser("viewer");
    const { container } = render(
      <RoleGate allow={["admin"]} fallback={<span>NO</span>}>
        <button>Do thing</button>
      </RoleGate>
    );
    expect(container.textContent).toContain("NO");
    // Fallback path does NOT wrap children — original button is absent.
    expect(container.querySelector("button")).toBeNull();
  });

  it("renders nothing when user is null", () => {
    mockUser(null);
    const { container } = render(
      <RoleGate allow={["admin", "user"]}>
        <button>Do thing</button>
      </RoleGate>
    );
    expect(container.querySelector("button")).toBeNull();
    expect(container.textContent).not.toContain("Do thing");
  });
});
