import { act } from "react-dom/test-utils";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { CommonAuditLog } from "../../audit";
import { AuditLogTable } from "./AuditLogTable";

const roots: Root[] = [];

afterEach(() => {
  for (const root of roots.splice(0)) {
    act(() => {
      root.unmount();
    });
  }
  vi.restoreAllMocks();
  document.body.innerHTML = "";
});

function renderTable(props: Parameters<typeof AuditLogTable>[0]) {
  const container = document.createElement("div");
  document.body.appendChild(container);
  const root = createRoot(container);
  roots.push(root);

  act(() => {
    root.render(<AuditLogTable {...props} />);
  });

  return { container };
}

const logs: CommonAuditLog[] = [
  {
    event_id: "evt-1",
    timestamp: "2026-05-28T06:07:18.000Z",
    user_id: "user-1",
    event_type: "CLIENT_UPDATE",
    status: "success",
    ip_address: "127.0.0.1",
    user_agent: "Vitest",
    device_id: "device-1",
    details: JSON.stringify({
      request_id: "req-1",
      method: "POST",
      path: "/api/v1/clients",
      latency_ms: 120,
      tenant_id: "tenant-1",
      actor_id: "user-1",
      action: "업데이트",
      target_id: "client-a",
      before: { status: "inactive" },
      after: { status: "active" },
    }),
  },
];

describe("AuditLogTable", () => {
  it("renders loading and empty states", () => {
    const { container: loadingContainer } = renderTable({
      logs: [],
      t: (key, fallback) => fallback ?? key,
      loading: true,
      hasNextPage: false,
      isFetchingNextPage: false,
      onLoadMore: vi.fn(),
    });

    expect(loadingContainer.textContent).toContain("Loading audit logs...");

    const { container: emptyContainer } = renderTable({
      logs: [],
      t: (key, fallback) => fallback ?? key,
      loading: false,
      hasNextPage: false,
      isFetchingNextPage: false,
      onLoadMore: vi.fn(),
    });

    expect(emptyContainer.textContent).toContain("No audit logs found.");
    expect(emptyContainer.textContent).toContain("End of audit feed");
  });

  it("renders rows, expands details, copies fields, and loads more", async () => {
    const writeText = vi.fn().mockResolvedValue(undefined);
    Object.defineProperty(navigator, "clipboard", {
      value: { writeText },
      configurable: true,
    });

    const onLoadMore = vi.fn();
    const { container } = renderTable({
      logs,
      t: (key, fallback, vars) => {
        let text = fallback ?? key;
        for (const [name, value] of Object.entries(vars ?? {})) {
          text = text.replaceAll(`{{${name}}}`, String(value));
        }
        return text;
      },
      loading: false,
      hasNextPage: true,
      isFetchingNextPage: false,
      onLoadMore,
    });

    expect(container.textContent).toContain("user-1");
    expect(container.textContent).toContain("업데이트");
    expect(container.textContent).toContain("client-a");
    expect(container.textContent).toContain("success");

    const buttons = Array.from(container.querySelectorAll("button"));
    const actorCopyButton = buttons.find(
      (button) => button.getAttribute("aria-label") === "Copy User ID",
    );
    const targetCopyButton = buttons.find(
      (button) => button.getAttribute("aria-label") === "Copy Client ID",
    );
    const expandButton = buttons.find(
      (button) => !button.getAttribute("aria-label") && !button.textContent,
    );
    const loadMoreButton = buttons.find(
      (button) => button.textContent === "Load more",
    );

    expect(actorCopyButton).toBeTruthy();
    expect(targetCopyButton).toBeTruthy();
    expect(expandButton).toBeTruthy();
    expect(loadMoreButton).toBeTruthy();

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

    expect(writeText).toHaveBeenCalledWith("user-1");
    expect(writeText).toHaveBeenCalledWith("client-a");
    expect(container.textContent).toContain("Request ID · req-1");
    expect(container.textContent).toContain("Actor");
    expect(container.textContent).toContain("Result");

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

    expect(onLoadMore).toHaveBeenCalledTimes(1);
  });
});
