import { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, describe, expect, it, vi } from "vitest";
import { CopyButton } from "./copy-button";

const roots: Root[] = [];

afterEach(() => {
  for (const root of roots.splice(0)) {
    act(() => {
      root.unmount();
    });
  }
  vi.restoreAllMocks();
  delete (navigator as Navigator & { clipboard?: unknown }).clipboard;
  Object.defineProperty(window, "isSecureContext", {
    value: false,
    configurable: true,
  });
  document.body.innerHTML = "";
});

function renderCopyButton(value: string, onCopy = vi.fn()) {
  const container = document.createElement("div");
  document.body.appendChild(container);
  const root = createRoot(container);
  roots.push(root);

  act(() => {
    root.render(<CopyButton value={value} onCopy={onCopy} />);
  });

  return { container, onCopy };
}

describe("CopyButton", () => {
  it("copies with the clipboard API when secure context is available", async () => {
    const writeText = vi.fn().mockResolvedValue(undefined);
    Object.defineProperty(navigator, "clipboard", {
      value: { writeText },
      configurable: true,
    });
    Object.defineProperty(window, "isSecureContext", {
      value: true,
      configurable: true,
    });

    const { container, onCopy } = renderCopyButton("client-secret");
    const button = container.querySelector("button");
    expect(button).not.toBeNull();

    await act(async () => {
      button?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
    });

    expect(writeText).toHaveBeenCalledWith("client-secret");
    expect(onCopy).toHaveBeenCalledTimes(1);
  });

  it("falls back to execCommand when clipboard API is unavailable", async () => {
    const execCommand = vi.fn(() => true);
    Object.defineProperty(document, "execCommand", {
      value: execCommand,
      configurable: true,
    });
    Object.defineProperty(window, "isSecureContext", {
      value: false,
      configurable: true,
    });

    const { container, onCopy } = renderCopyButton("client-secret");
    const button = container.querySelector("button");
    expect(button).not.toBeNull();

    await act(async () => {
      button?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
    });

    expect(execCommand).toHaveBeenCalledWith("copy");
    expect(onCopy).toHaveBeenCalledTimes(1);
  });

  it("keeps running when the fallback copy flow fails", async () => {
    const execCommand = vi.fn(() => false);
    const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
    Object.defineProperty(document, "execCommand", {
      value: execCommand,
      configurable: true,
    });
    Object.defineProperty(window, "isSecureContext", {
      value: false,
      configurable: true,
    });

    const { container, onCopy } = renderCopyButton("client-secret");
    const button = container.querySelector("button");
    expect(button).not.toBeNull();

    await act(async () => {
      button?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
    });

    expect(execCommand).toHaveBeenCalledWith("copy");
    expect(onCopy).not.toHaveBeenCalled();
    expect(errorSpy).toHaveBeenCalled();
  });
});
