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

const exportUsersCSVMock = vi.hoisted(() => vi.fn());
const updateUserMock = vi.hoisted(() => vi.fn());
const bulkUpdateUsersMock = vi.hoisted(() => vi.fn());
const fetchUsersMock = vi.hoisted(() => vi.fn());

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

vi.mock("../../../lib/adminApi", () => ({
  fetchTenant: vi.fn(async () => ({
    id: "tenant-team-id",
    name: "기술기획팀",
    slug: "tech-planning",
  })),
  fetchUsers: fetchUsersMock,
  bulkUpdateUsers: bulkUpdateUsersMock,
  exportUsersCSV: exportUsersCSVMock,
  updateUser: updateUserMock,
}));

function renderTenantUsersPage() {
  const queryClient = new QueryClient({
    defaultOptions: { queries: { retry: false } },
  });
  const result = render(
    <QueryClientProvider client={queryClient}>
      <MemoryRouter initialEntries={["/tenants/tenant-team-id/users"]}>
        <Routes>
          <Route
            path="/tenants/:tenantId/users"
            element={<TenantUsersPage />}
          />
        </Routes>
      </MemoryRouter>
    </QueryClientProvider>,
  );

  return { ...result, queryClient };
}

describe("TenantUsersPage export", () => {
  beforeEach(() => {
    exportUsersCSVMock.mockReset();
    updateUserMock.mockReset();
    bulkUpdateUsersMock.mockReset();
    fetchUsersMock.mockReset();
    fetchUsersMock.mockResolvedValue({
      items: [
        {
          id: "user-1",
          name: "Alice",
          email: "alice@example.com",
          role: "user",
          status: "active",
        },
      ],
      total: 1,
    });
    exportUsersCSVMock.mockResolvedValue({
      blob: new Blob(["email,name\nalice@example.com,Alice\n"], {
        type: "text/csv",
      }),
      filename: "users_export_20260609.csv",
    });
    updateUserMock.mockResolvedValue({});
    vi.spyOn(window.URL, "createObjectURL").mockReturnValue(
      "blob:tenant-users-export",
    );
    vi.spyOn(window.URL, "revokeObjectURL").mockImplementation(() => {});
    bulkUpdateUsersMock.mockResolvedValue({ results: [] });
  });

  it("exports only the currently opened tenant users by tenant slug", async () => {
    renderTenantUsersPage();

    await screen.findByText("Alice");

    fireEvent.click(screen.getByTestId("tenant-users-export-menu-item"));

    await waitFor(() => {
      expect(exportUsersCSVMock).toHaveBeenCalledWith(
        "",
        "tech-planning",
        false,
      );
    });
  });

  it("queues searched users and adds all queued users to the tenant at once", async () => {
    fetchUsersMock
      .mockResolvedValueOnce({ items: [], total: 0 })
      .mockResolvedValueOnce({
        items: [
          {
            id: "user-2",
            name: "Bob",
            email: "bob@example.com",
            role: "user",
            status: "active",
          },
          {
            id: "user-3",
            name: "Carol",
            email: "carol@example.com",
            role: "user",
            status: "active",
          },
        ],
        total: 2,
      })
      .mockResolvedValue({ items: [], total: 0 });
    updateUserMock.mockResolvedValue({});

    renderTenantUsersPage();

    const addButton = await screen.findByTestId(
      "tenant-member-add-existing-btn",
    );
    await waitFor(() => expect(addButton).not.toBeDisabled());
    fireEvent.click(addButton);
    fireEvent.change(screen.getByTestId("tenant-member-search-input"), {
      target: { value: "bo" },
    });

    fireEvent.click(await screen.findByText("Bob"));
    fireEvent.click(await screen.findByText("Carol"));

    expect(screen.getByTestId("tenant-member-add-queue")).toHaveTextContent(
      "Bob",
    );
    expect(screen.getByTestId("tenant-member-add-queue")).toHaveTextContent(
      "Carol",
    );

    fireEvent.click(screen.getByTestId("tenant-member-add-submit-btn"));

    await waitFor(() => {
      expect(bulkUpdateUsersMock).toHaveBeenCalledWith({
        userIds: ["user-2", "user-3"],
        tenantSlug: "tech-planning",
        isAddTenant: true,
      });
    });
    expect(updateUserMock).not.toHaveBeenCalledWith(
      expect.any(String),
      expect.objectContaining({ isAddTenant: true }),
    );
  });

  it("queues orgfront multi picker users and adds them with one bulk request", async () => {
    fetchUsersMock
      .mockResolvedValueOnce({
        items: [
          {
            id: "existing-user",
            name: "Existing",
            email: "existing@example.com",
            role: "user",
            status: "active",
          },
        ],
        total: 1,
      })
      .mockResolvedValue({ items: [], total: 0 });

    renderTenantUsersPage();

    const addButton = await screen.findByTestId(
      "tenant-member-add-existing-btn",
    );
    await waitFor(() => expect(addButton).not.toBeDisabled());
    fireEvent.click(addButton);

    const picker = await screen.findByTitle("조직도에서 구성원 선택");
    expect(decodeURIComponent(picker.getAttribute("src") ?? "")).toContain(
      "/embed/picker?mode=multiple&select=user",
    );

    fireEvent(
      window,
      new MessageEvent("message", {
        data: {
          type: "orgfront:picker:confirm",
          payload: {
            mode: "multiple",
            selections: [
              { type: "tenant", id: "team-1", name: "플랫폼팀" },
              {
                type: "user",
                id: "picked-user-1",
                name: "Picked One",
                email: "picked1@example.com",
              },
              {
                type: "user",
                id: "picked-user-2",
                name: "Picked Two",
              },
              {
                type: "user",
                id: "existing-user",
                name: "Existing",
                email: "existing@example.com",
              },
            ],
          },
        },
      }),
    );

    expect(screen.getByTestId("tenant-member-add-queue")).toHaveTextContent(
      "Picked One",
    );
    expect(screen.getByTestId("tenant-member-add-queue")).toHaveTextContent(
      "Picked Two",
    );
    expect(screen.getByTestId("tenant-member-add-queue")).not.toHaveTextContent(
      "Existing",
    );

    fireEvent.click(screen.getByTestId("tenant-member-add-submit-btn"));

    await waitFor(() => {
      expect(bulkUpdateUsersMock).toHaveBeenCalledWith({
        userIds: ["picked-user-1", "picked-user-2"],
        tenantSlug: "tech-planning",
        isAddTenant: true,
      });
    });
  });

  it("removes a member from the tenant and invalidates the user detail cache", async () => {
    const confirmSpy = vi.spyOn(window, "confirm").mockReturnValue(true);
    const { queryClient } = renderTenantUsersPage();
    queryClient.setQueryData(["user", "user-1"], {
      id: "user-1",
      name: "Alice",
    });

    await screen.findByText("Alice");

    fireEvent.click(screen.getByTestId("tenant-member-remove-user-1"));

    await waitFor(() => {
      expect(updateUserMock).toHaveBeenCalledWith("user-1", {
        tenantSlug: "tech-planning",
        isRemoveTenant: true,
      });
    });
    expect(queryClient.getQueryState(["user", "user-1"])?.isInvalidated).toBe(
      true,
    );
    confirmSpy.mockRestore();
  });
});
