import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { fireEvent, render, screen } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createI18nMock } from "../../test/i18nMock";
import UserListPage from "./UserListPage";

const selectRenderCounter = vi.hoisted(() => ({ count: 0 }));

const users = Array.from({ length: 200 }, (_, index) => ({
  id: `user-${index}`,
  name: `User ${index}`,
  email: `user${index}@example.com`,
  phone: `010-${String(index).padStart(4, "0")}-0000`,
  role: "user",
  status: "active",
  tenantSlug: "hanmac",
  tenant: { id: "tenant-1", name: "한맥", slug: "hanmac" },
  metadata: {},
  createdAt: "2026-05-01T00:00:00Z",
  updatedAt: "2026-05-01T00:00:00Z",
}));

const fetchUsersMock = vi.hoisted(() => vi.fn());
const fetchAllTenantsMock = vi.hoisted(() => vi.fn());
const searchRenderBudgetMs =
  process.env.npm_lifecycle_event === "test:coverage" ? 500 : 300;

vi.mock("../../lib/i18n", () => createI18nMock());

vi.mock("../../lib/adminApi", () => ({
  fetchMe: vi.fn(async () => ({
    id: "admin-user",
    role: "super_admin",
    name: "Admin",
    email: "admin@example.com",
  })),
  fetchAllTenants: fetchAllTenantsMock,
  fetchTenant: vi.fn(async () => ({
    id: "tenant-1",
    name: "한맥",
    slug: "hanmac",
    config: { userSchema: [] },
  })),
  fetchUsers: fetchUsersMock,
  bulkCreateUsers: vi.fn(),
  bulkDeleteUsers: vi.fn(),
  bulkUpdateUsers: vi.fn(),
  deleteUser: vi.fn(),
  exportUsersCSV: vi.fn(),
  updateUser: vi.fn(),
}));

vi.mock("../../components/ui/select", () => ({
  Select: ({ children }: { children: React.ReactNode }) => (
    <div>{children}</div>
  ),
  SelectTrigger: ({
    children,
    ...props
  }: React.ButtonHTMLAttributes<HTMLButtonElement>) => {
    selectRenderCounter.count += 1;
    return (
      <button type="button" {...props}>
        {children}
      </button>
    );
  },
  SelectValue: () => <span />,
  SelectContent: ({ children }: { children: React.ReactNode }) => (
    <div>{children}</div>
  ),
  SelectItem: ({
    children,
    value: _value,
  }: {
    children: React.ReactNode;
    value: string;
  }) => <div>{children}</div>,
}));

function renderUserListPage() {
  const queryClient = new QueryClient({
    defaultOptions: { queries: { retry: false } },
  });

  return render(
    <QueryClientProvider client={queryClient}>
      <MemoryRouter>
        <UserListPage />
      </MemoryRouter>
    </QueryClientProvider>,
  );
}

function createDeferred<T>() {
  let resolve: (value: T) => void = () => {};
  const promise = new Promise<T>((promiseResolve) => {
    resolve = promiseResolve;
  });

  return { promise, resolve };
}

describe("UserListPage search rendering", () => {
  beforeEach(() => {
    selectRenderCounter.count = 0;
    fetchUsersMock.mockReset();
    fetchAllTenantsMock.mockReset();
    fetchAllTenantsMock.mockResolvedValue({
      items: [{ id: "tenant-1", name: "한맥", slug: "hanmac" }],
      total: 1,
    });
    fetchUsersMock.mockImplementation(
      async (_limit: number, _offset: number, search?: string) => {
        const normalizedSearch = search?.trim().toLowerCase();
        const items = normalizedSearch
          ? users.filter((user) =>
              `${user.name} ${user.email}`
                .toLowerCase()
                .includes(normalizedSearch),
            )
          : users;
        return { items, total: items.length };
      },
    );
  });

  it("does not rerender user table controls while typing a draft search", async () => {
    renderUserListPage();

    await screen.findByText("User 0");
    const searchInput = screen.getByPlaceholderText("이름 또는 이메일 검색");
    const renderCountBeforeTyping = selectRenderCounter.count;

    fireEvent.change(searchInput, { target: { value: "u" } });

    expect(searchInput).toHaveValue("u");
    expect(selectRenderCounter.count).toBe(renderCountBeforeTyping);
  });

  it("keeps rendered row controls below the full 200-user result set", async () => {
    renderUserListPage();

    await screen.findByText("User 0");

    expect(screen.getAllByTestId(/^user-status-select-/).length).toBeLessThan(
      200,
    );
  });

  it("renders compact vertically centered user table headers", async () => {
    renderUserListPage();

    await screen.findByText("User 0");
    const nameHeader = screen.getByRole("columnheader", { name: /이름/ });
    const content = nameHeader.firstElementChild;

    expect(nameHeader).toHaveClass("h-9", "py-1", "align-middle", "text-xs");
    expect(content).toHaveClass("flex", "h-full", "items-center");
  });

  it("does not render private additional tenant appointments in the tenant column", async () => {
    fetchUsersMock.mockResolvedValueOnce({
      items: [
        {
          ...users[0],
          name: "Additional Tenant User",
          metadata: {
            additionalAppointments: [
              {
                tenantId: "tenant-2",
                tenantSlug: "private-team",
                tenantName: "비공개 팀",
                isPrimary: false,
              },
            ],
          },
        },
      ],
      total: 1,
    });

    renderUserListPage();

    expect(
      await screen.findByText("Additional Tenant User"),
    ).toBeInTheDocument();
    expect(screen.getAllByText("한맥").length).toBeGreaterThanOrEqual(1);
    expect(screen.queryByText("비공개 팀")).not.toBeInTheDocument();
  });

  it("excludes private tenants when choosing the representative tenant for the user list", async () => {
    fetchAllTenantsMock.mockResolvedValueOnce({
      items: [
        {
          id: "tenant-private",
          name: "비공개 팀",
          slug: "private-team",
          config: { visibility: "private" },
        },
        {
          id: "tenant-public",
          name: "공개 팀",
          slug: "public-team",
          config: { visibility: "public" },
        },
      ],
      total: 2,
    });
    fetchUsersMock.mockResolvedValueOnce({
      items: [
        {
          ...users[0],
          name: "Private Primary User",
          tenantSlug: "private-team",
          tenant: {
            id: "tenant-private",
            name: "비공개 팀",
            slug: "private-team",
            config: { visibility: "private" },
          },
          joinedTenants: [
            {
              id: "tenant-public",
              name: "공개 팀",
              slug: "public-team",
              config: { visibility: "public" },
            },
          ],
          metadata: {
            primaryTenantId: "tenant-private",
            primaryTenantSlug: "private-team",
            primaryTenantName: "비공개 팀",
          },
        },
      ],
      total: 1,
    });

    renderUserListPage();

    expect(await screen.findByText("Private Primary User")).toBeInTheDocument();
    expect(screen.getByText("공개 팀")).toBeInTheDocument();
    expect(screen.queryByText("비공개 팀")).not.toBeInTheDocument();
  });

  it("centers the initial loading message across the user table", async () => {
    const deferred = createDeferred<{ items: typeof users; total: number }>();
    fetchUsersMock.mockReturnValueOnce(deferred.promise);

    renderUserListPage();

    const loadingCell = await screen.findByTestId("user-table-loading-cell");
    expect(loadingCell).toHaveClass(
      "flex",
      "items-center",
      "justify-center",
      "text-center",
    );
    expect(loadingCell).toHaveStyle({ gridColumn: "1 / -1" });

    deferred.resolve({ items: users, total: users.length });
  });

  it("renders a 200-user search result update within 200ms after search submit", async () => {
    renderUserListPage();

    await screen.findByText("User 0");
    const searchInput = screen.getByPlaceholderText("이름 또는 이메일 검색");
    const startedAt = performance.now();

    fireEvent.change(searchInput, { target: { value: "user 19" } });
    fireEvent.keyDown(searchInput, { key: "Enter" });

    expect(await screen.findByText("User 19")).toBeInTheDocument();
    expect(screen.queryByText("User 0")).not.toBeInTheDocument();
    expect(performance.now() - startedAt).toBeLessThan(searchRenderBudgetMs);
  });

  it("keeps rendered form fields identifiable for browser autofill diagnostics", async () => {
    const { container } = renderUserListPage();

    await screen.findByText("User 0");
    const anonymousFields = Array.from(
      container.querySelectorAll("input, select, textarea"),
    ).filter(
      (field) =>
        !field.getAttribute("id")?.trim() &&
        !field.getAttribute("name")?.trim(),
    );

    expect(anonymousFields).toHaveLength(0);
  });
});
