import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { createRoot, type Root } from "react-dom/client";
import { act } from "react-dom/test-utils";
import { MemoryRouter } from "react-router-dom";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import ClientsPage from "./ClientsPage";

const navigateMock = vi.fn();
const fetchClientsMock = vi.fn();
const fetchMeMock = vi.fn();
const fetchDeveloperRequestStatusMock = vi.fn();
const fetchMyTenantsMock = vi.fn();
const requestDeveloperAccessMock = vi.fn();

let authState = {
  user: {
    access_token: "access-token",
    profile: {
      role: "super_admin",
      tenant_id: "tenant-1",
      companyCode: "HANMAC",
      name: "Dev Admin",
      email: "dev@example.com",
      phone: "010-0000-0000",
    },
  },
};

vi.mock("react-oidc-context", () => ({
  useAuth: () => authState,
}));

vi.mock("react-router-dom", async () => {
  const actual =
    await vi.importActual<typeof import("react-router-dom")>(
      "react-router-dom",
    );
  return {
    ...actual,
    useNavigate: () => navigateMock,
  };
});

vi.mock("../../lib/devApi", () => ({
  fetchClients: () => fetchClientsMock(),
  fetchMe: () => fetchMeMock(),
  fetchDeveloperRequestStatus: () => fetchDeveloperRequestStatusMock(),
  fetchMyTenants: () => fetchMyTenantsMock(),
  requestDeveloperAccess: (...args: unknown[]) =>
    requestDeveloperAccessMock(...args),
}));

vi.mock("../../lib/i18n", () => ({
  t: (key: string, fallback?: string, vars?: Record<string, unknown>) => {
    let text = fallback ?? key;
    for (const [name, value] of Object.entries(vars ?? {})) {
      text = text.replaceAll(`{{${name}}}`, String(value));
    }
    return text;
  },
}));

const roots: Root[] = [];

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

beforeEach(() => {
  authState = {
    user: {
      access_token: "access-token",
      profile: {
        role: "super_admin",
        tenant_id: "tenant-1",
        companyCode: "HANMAC",
        name: "Dev Admin",
        email: "dev@example.com",
        phone: "010-0000-0000",
      },
    },
  };

  fetchClientsMock.mockResolvedValue({
    items: [],
    limit: 100,
    offset: 0,
  });
  fetchMeMock.mockResolvedValue({
    role: "super_admin",
    name: "Dev Admin",
    email: "dev@example.com",
    phone: "010-0000-0000",
  });
  fetchDeveloperRequestStatusMock.mockResolvedValue({ status: "none" });
  fetchMyTenantsMock.mockResolvedValue([
    {
      id: "tenant-1",
      name: "Hanmac",
      slug: "hanmac",
      type: "COMPANY",
      parentId: null,
      description: "",
      status: "active",
      memberCount: 10,
      createdAt: "2026-05-01T00:00:00Z",
      updatedAt: "2026-05-01T00:00:00Z",
    },
  ]);
});

function makeClients(count: number) {
  return Array.from({ length: count }, (_, index) => ({
    id: `client-${index + 1}`,
    name: `App ${index + 1}`,
    type: index % 2 === 0 ? "private" : "pkce",
    status: index % 2 === 0 ? "active" : "inactive",
    createdAt: `2026-05-${String(index + 1).padStart(2, "0")}T00:00:00Z`,
    redirectUris: [],
    scopes: [],
    metadata: {},
  }));
}

async function setInputValue(input: HTMLInputElement, value: string) {
  const descriptor = Object.getOwnPropertyDescriptor(
    HTMLInputElement.prototype,
    "value",
  );
  descriptor?.set?.call(input, value);
  input.dispatchEvent(new Event("input", { bubbles: true }));
  await new Promise((resolve) => setTimeout(resolve, 0));
}

async function renderPage() {
  const container = document.createElement("div");
  document.body.appendChild(container);
  const root = createRoot(container);
  roots.push(root);
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: { retry: false },
      mutations: { retry: false },
    },
  });

  await act(async () => {
    root.render(
      <QueryClientProvider client={queryClient}>
        <MemoryRouter>
          <ClientsPage />
        </MemoryRouter>
      </QueryClientProvider>,
    );
  });

  await act(async () => {
    await new Promise((resolve) => setTimeout(resolve, 0));
  });

  return container;
}

async function waitForTextContent(container: HTMLElement, text: string) {
  for (let attempt = 0; attempt < 20; attempt += 1) {
    if (container.textContent?.includes(text)) {
      return;
    }

    await act(async () => {
      await new Promise((resolve) => setTimeout(resolve, 0));
    });
  }

  throw new Error(`Expected container text to include: ${text}`);
}

describe("ClientsPage", () => {
  it("does not show the legacy tenant scope label for unrestricted clients", async () => {
    fetchClientsMock.mockResolvedValue({
      items: [
        {
          ...makeClients(1)[0],
          name: "Unrestricted App",
          metadata: {
            tenant_access_restricted: false,
            allowed_tenants: [],
          },
        },
      ],
      limit: 100,
      offset: 0,
    });

    const container = await renderPage();

    expect(container.textContent).toContain("Unrestricted App");
    expect(container.textContent).not.toContain("Tenant-scoped");
    expect(container.textContent).not.toContain("Tenant-limited");
  });

  it("shows Tenant-limited only when client tenant access is restricted", async () => {
    fetchClientsMock.mockResolvedValue({
      items: [
        {
          ...makeClients(1)[0],
          name: "Limited App",
          metadata: {
            tenant_access_restricted: true,
            allowed_tenants: ["tenant-1"],
          },
        },
      ],
      limit: 100,
      offset: 0,
    });

    const container = await renderPage();

    expect(container.textContent).toContain("Limited App");
    expect(container.textContent).toContain("Tenant-limited");
    expect(container.textContent).not.toContain("Tenant-scoped");
  });

  it("expands the list and applies search filters", async () => {
    fetchClientsMock.mockResolvedValue({
      items: makeClients(6),
      limit: 100,
      offset: 0,
    });

    const container = await renderPage();
    expect(container.textContent).toContain(
      "총 6개의 애플리케이션이 등록되어 있습니다.",
    );
    expect(container.textContent).toContain("App 6");
    expect(container.textContent).toContain("App 2");
    expect(container.textContent).not.toContain("App 1");

    const moreButton = Array.from(container.querySelectorAll("button")).find(
      (button) => button.textContent === "더보기",
    );
    expect(moreButton).toBeTruthy();

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

    expect(container.textContent).toContain("App 6");
    expect(container.textContent).toContain("접기");

    const advancedButton = Array.from(
      container.querySelectorAll("button"),
    ).find((button) => button.textContent === "Advanced Filters");
    expect(advancedButton).toBeTruthy();

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

    const searchInput = Array.from(container.querySelectorAll("input")).find(
      (input) =>
        input
          .getAttribute("placeholder")
          ?.includes("클라이언트 이름/ID로 검색"),
    ) as HTMLInputElement | undefined;
    if (!searchInput) {
      throw new Error("Expected search input to be rendered");
    }

    await act(async () => {
      await setInputValue(searchInput, "missing-client");
    });

    expect(container.textContent).toContain("조건에 맞는 연동 앱이 없습니다.");

    const resetButton = Array.from(container.querySelectorAll("button")).find(
      (button) => button.textContent === "초기화",
    );
    expect(resetButton).toBeTruthy();

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

    await act(async () => {
      await setInputValue(searchInput, "");
    });

    expect(container.textContent).toContain("App 1");
  });

  it("navigates to the developer request page from empty states", async () => {
    authState = {
      user: {
        access_token: "access-token",
        profile: {
          role: "user",
          tenant_id: "tenant-1",
          companyCode: "HANMAC",
          name: "Requester",
          email: "requester@example.com",
          phone: "010-1234-5678",
        },
      },
    };
    fetchClientsMock.mockResolvedValue({
      items: [],
      limit: 100,
      offset: 0,
    });
    fetchDeveloperRequestStatusMock.mockResolvedValue({ status: "none" });
    fetchMeMock.mockResolvedValue({
      role: "user",
      name: "Requester",
      email: "requester@example.com",
      phone: "010-1234-5678",
    });

    const container = await renderPage();
    expect(container.textContent).toContain("개발자 등록 신청하기");

    const requestButton = Array.from(container.querySelectorAll("button")).find(
      (button) => button.textContent === "개발자 등록 신청하기",
    );
    expect(requestButton).toBeTruthy();

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

    expect(navigateMock).toHaveBeenCalledWith("/developer-requests");
  });

  it("allows a user without tenant context to request developer access", async () => {
    authState = {
      user: {
        access_token: "access-token",
        profile: {
          role: "user",
          companyCode: "HANMAC",
          name: "Requester",
          email: "requester@example.com",
          phone: "010-1234-5678",
        },
      },
    };
    fetchMeMock.mockResolvedValue({
      role: "user",
      name: "Requester",
      email: "requester@example.com",
      phone: "010-1234-5678",
    });
    fetchDeveloperRequestStatusMock.mockResolvedValue({ status: "none" });

    const container = await renderPage();
    await waitForTextContent(container, "개발자 등록 신청하기");

    const requestButton = Array.from(container.querySelectorAll("button")).find(
      (button) => button.textContent === "개발자 등록 신청하기",
    );
    expect(requestButton).toBeTruthy();

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

    expect(navigateMock).toHaveBeenCalledWith("/developer-requests");
    expect(fetchDeveloperRequestStatusMock).toHaveBeenCalled();
  });

  it("shows the create app button for a super admin without tenant context", async () => {
    authState = {
      user: {
        access_token: "access-token",
        profile: {
          role: "super_admin",
          companyCode: "HANMAC",
          name: "Dev Admin",
          email: "dev@example.com",
          phone: "010-0000-0000",
        },
      },
    };
    fetchMeMock.mockResolvedValue({
      role: "super_admin",
      name: "Dev Admin",
      email: "dev@example.com",
      phone: "010-0000-0000",
    });

    const container = await renderPage();
    expect(container.textContent).toContain("연동 앱 추가");

    const createButton = Array.from(container.querySelectorAll("button")).find(
      (button) => button.textContent === "연동 앱 추가",
    );
    expect(createButton).toBeTruthy();

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

    expect(navigateMock).toHaveBeenCalledWith("/clients/new");
  });
});
