import { fireEvent, screen, waitFor } from "@testing-library/react";
import { describe, expect, mock, test } from "bun:test";
import { createElement } from "react";
import { useLocation } from "react-router";

import type { Project } from "../../../src/shared/api";

import { App } from "../../../src/web/app";
import { ProjectFormModal } from "../../../src/web/features/projects/components/ProjectFormModal";
import { ProjectTable } from "../../../src/web/features/projects/components/ProjectTable";
import { installFetchMock, jsonResponse, mockMetaResponse, renderWithProviders } from "../test-utils";

const ACTIVE_PROJECT: Project = {
  createdAt: "2024-01-01T00:00:00.000Z",
  description: "活跃描述",
  id: "p1",
  name: "活跃项目",
  status: "active",
  updatedAt: "2024-01-01T00:00:00.000Z",
};

const ARCHIVED_PROJECT: Project = {
  createdAt: "2024-01-01T00:00:00.000Z",
  description: "归档描述",
  id: "p2",
  name: "归档项目",
  status: "archived",
  updatedAt: "2024-01-02T00:00:00.000Z",
};

async function clickLatestConfirmButton() {
  const confirmTexts = await screen.findAllByText(/确\s*定|OK|确认/);
  const lastText = confirmTexts[confirmTexts.length - 1]!;
  const button = lastText.closest("button") ?? lastText.closest("[role='button']") ?? lastText;
  fireEvent.click(button);
}

function createProjectFetchMock(initialProjects: Project[] = [ACTIVE_PROJECT, ARCHIVED_PROJECT]) {
  let projects = [...initialProjects];
  return installFetchMock((call) => {
    if (call.url.includes("/api/meta")) return mockMetaResponse();

    const url = new URL(call.url, "http://localhost");
    if (url.pathname === "/api/projects" && call.method === "GET") {
      const status = url.searchParams.get("status");
      const keyword = url.searchParams.get("keyword") ?? "";
      const items = projects.filter((project) => {
        const statusMatched = !status || project.status === status;
        const keywordMatched = !keyword || `${project.name}${project.description}`.includes(keyword);
        return statusMatched && keywordMatched;
      });
      return jsonResponse({ items, page: 1, pageSize: 20, total: items.length });
    }

    if (url.pathname === "/api/projects" && call.method === "POST") {
      const data = jsonBody(call.body) as { description?: string; name: string };
      const created: Project = {
        createdAt: "2024-01-03T00:00:00.000Z",
        description: data.description ?? "",
        id: "p-created",
        name: data.name,
        status: "active",
        updatedAt: "2024-01-03T00:00:00.000Z",
      };
      projects = [created, ...projects];
      return jsonResponse({ project: created }, { status: 201 });
    }

    const projectId = /^\/api\/projects\/([^/]+)(?:\/(archive|restore))?$/.exec(url.pathname);
    if (projectId) {
      const [, id, action] = projectId;
      const project = projects.find((item) => item.id === id);
      if (!project) return jsonResponse({ error: "项目不存在", status: 404 }, { status: 404 });

      if (call.method === "PATCH") {
        const data = jsonBody(call.body) as { description?: string; name?: string };
        const updated = { ...project, ...data, updatedAt: "2024-01-04T00:00:00.000Z" };
        projects = projects.map((item) => (item.id === id ? updated : item));
        return jsonResponse({ project: updated });
      }

      if (call.method === "POST" && action === "archive") {
        const archived = { ...project, status: "archived" as const };
        projects = projects.map((item) => (item.id === id ? archived : item));
        return jsonResponse({ project: archived });
      }

      if (call.method === "POST" && action === "restore") {
        const restored = { ...project, status: "active" as const };
        projects = projects.map((item) => (item.id === id ? restored : item));
        return jsonResponse({ project: restored });
      }

      if (call.method === "DELETE") {
        projects = projects.filter((item) => item.id !== id);
        return new Response(null, { status: 204 });
      }
    }

    return jsonResponse({ error: "Not Found" }, { status: 404 });
  });
}

function jsonBody(body: BodyInit | null | undefined): unknown {
  return JSON.parse(typeof body === "string" ? body : "{}");
}

function LocationProbe() {
  const location = useLocation();
  return createElement("span", null, `当前路径：${location.pathname}`);
}

describe("ProjectsPage", () => {
  test("渲染项目管理入口并展示项目列表", async () => {
    const calls = createProjectFetchMock();

    renderWithProviders(createElement(App), { initialRoute: "/projects" });

    await waitFor(() => {
      expect(screen.getByText("活跃项目")).not.toBeNull();
    });

    expect(screen.getByText("归档项目")).not.toBeNull();
    expect(screen.getByRole("button", { name: /新建项目/ })).not.toBeNull();
    expect(screen.getByPlaceholderText("搜索名称或描述")).not.toBeNull();
    expect(calls.filter((call) => !call.url.includes("/api/meta")).length).toBeGreaterThan(0);
  });

  test("搜索和状态筛选会更新请求参数与用户可见结果", async () => {
    const calls = createProjectFetchMock();

    renderWithProviders(createElement(App), { initialRoute: "/projects" });
    await waitFor(() => expect(screen.getByText("活跃项目")).not.toBeNull());

    const searchInput1 = screen.getByPlaceholderText("搜索名称或描述");
    fireEvent.change(searchInput1, { target: { value: "归档" } });
    fireEvent.keyDown(searchInput1, { key: "Enter" });
    await waitFor(() => expect(calls.some((call) => call.url.includes("keyword=%E5%BD%92%E6%A1%A3"))).toBe(true));

    const statusLabels = screen.getAllByText("状态");
    const selectLabel = statusLabels.find((el) => el.closest(".ant-select"));
    if (selectLabel) fireEvent.mouseDown(selectLabel);
    await waitFor(() => {
      const archivedOptions = screen.getAllByText("已归档");
      const dropdownOption = archivedOptions.find((el) => el.closest(".ant-select-item"));
      if (dropdownOption) fireEvent.click(dropdownOption);
    });
    await waitFor(() => expect(calls.some((call) => call.url.includes("status=archived"))).toBe(true));

    await waitFor(() => expect(screen.getByText("归档项目")).not.toBeNull());
  });

  test("清空搜索条件复位请求参数并重新展示全部项目", async () => {
    const calls = createProjectFetchMock();

    renderWithProviders(createElement(App), { initialRoute: "/projects" });
    await waitFor(() => expect(screen.getByText("活跃项目")).not.toBeNull());

    const searchInput2 = screen.getByPlaceholderText("搜索名称或描述");
    fireEvent.change(searchInput2, { target: { value: "归档" } });
    fireEvent.keyDown(searchInput2, { key: "Enter" });
    await waitFor(() => expect(calls.some((call) => call.url.includes("keyword=%E5%BD%92%E6%A1%A3"))).toBe(true));

    const searchInput3 = screen.getByPlaceholderText("搜索名称或描述");
    const clearButton = searchInput3.closest(".ant-input-search")?.querySelector(".ant-input-clear-icon");
    if (clearButton) fireEvent.click(clearButton);

    await waitFor(() => {
      const lastProjectCall = [...calls].reverse().find((call) => call.url.includes("/api/projects"));
      expect(lastProjectCall && !lastProjectCall.url.includes("keyword=")).toBe(true);
    });
    await waitFor(() => expect(screen.getByText("活跃项目")).not.toBeNull());
  });

  test("新建项目提交请求 body 并显示创建结果", async () => {
    const calls = createProjectFetchMock([]);

    renderWithProviders(createElement(App), { initialRoute: "/projects" });
    await waitFor(() => expect(screen.getByRole("button", { name: /新建项目/ })).not.toBeNull());

    fireEvent.click(screen.getByRole("button", { name: /新建项目/ }));
    await waitFor(() => expect(screen.getAllByText("新建项目").length).toBeGreaterThan(1));
    await waitFor(() => expect(screen.getByPlaceholderText("请输入项目名称")).not.toBeNull());
    fireEvent.change(screen.getByPlaceholderText("请输入项目名称"), { target: { value: "新增项目" } });
    fireEvent.change(screen.getByPlaceholderText("请输入项目描述"), { target: { value: "新增描述" } });
    await clickLatestConfirmButton();

    await waitFor(() => expect(screen.getByText("新增项目")).not.toBeNull());

    const createCall = calls.find((call) => call.url.endsWith("/api/projects") && call.method === "POST");
    expect(createCall).toBeDefined();
    expect(jsonBody(createCall?.body)).toEqual({ description: "新增描述", name: "新增项目" });
  });

  test("编辑项目表单只提交变更字段", async () => {
    const updateCalls: unknown[] = [];
    const onUpdate = mock((args: unknown) => {
      updateCalls.push(args);
      return Promise.resolve();
    });

    renderWithProviders(
      createElement(ProjectFormModal, {
        editingProject: ACTIVE_PROJECT,
        onCancel: () => undefined,
        onCreate: () => Promise.resolve(),
        onOpenChange: () => undefined,
        onUpdate,
        open: true,
        submitting: false,
      }),
    );

    await waitFor(() => expect(screen.getByPlaceholderText("请输入项目名称")).not.toBeNull());
    fireEvent.change(screen.getByPlaceholderText("请输入项目名称"), { target: { value: "编辑项目" } });
    await clickLatestConfirmButton();

    await waitFor(() => expect(onUpdate).toHaveBeenCalled());
    expect(updateCalls[0]).toEqual({ data: { name: "编辑项目" }, id: "p1" });
  });

  test("项目表单校验失败不会提交，接口失败时保留弹窗", async () => {
    const onCreate = mock(() => Promise.reject(new Error("创建失败")));
    const onOpenChange = mock(() => undefined);

    renderWithProviders(
      createElement(ProjectFormModal, {
        editingProject: null,
        onCancel: () => undefined,
        onCreate,
        onOpenChange,
        onUpdate: () => Promise.resolve(),
        open: true,
        submitting: false,
      }),
    );

    await waitFor(() => expect(screen.getByPlaceholderText("请输入项目名称")).not.toBeNull());
    await clickLatestConfirmButton();
    expect(onCreate).not.toHaveBeenCalled();

    fireEvent.change(screen.getByPlaceholderText("请输入项目名称"), { target: { value: "失败项目" } });
    await clickLatestConfirmButton();
    await waitFor(() => expect(onCreate).toHaveBeenCalled());
    expect(onOpenChange).not.toHaveBeenCalledWith(false);
    expect(screen.getByText("新建项目")).not.toBeNull();
  });

  test("项目表格操作触发导航和行级动作", async () => {
    const onArchive = mock(() => Promise.resolve());
    const onDelete = mock(() => Promise.resolve());
    const onRestore = mock(() => Promise.resolve());

    renderWithProviders(
      createElement(
        "div",
        null,
        createElement(LocationProbe),
        createElement(ProjectTable, {
          data: { items: [ACTIVE_PROJECT, ARCHIVED_PROJECT], page: 1, pageSize: 20, total: 2 },
          loading: false,
          onArchive,
          onChange: () => undefined,
          onDelete,
          onEdit: () => undefined,
          onRestore,
          page: 1,
          pageSize: 20,
        }),
      ),
    );

    fireEvent.click(screen.getByRole("button", { name: /工作台/ }));
    expect(screen.getByText("当前路径：/workbench/p1")).not.toBeNull();

    fireEvent.click(screen.getByRole("button", { name: /归档/ }));
    await waitFor(() => expect(screen.getByText("确认归档此项目？")).not.toBeNull());
    await clickLatestConfirmButton();
    await waitFor(() => expect(onArchive).toHaveBeenCalledWith("p1"));

    fireEvent.click(screen.getByRole("button", { name: /恢复/ }));
    await waitFor(() => expect(screen.getByText("确认恢复此项目？")).not.toBeNull());
    await clickLatestConfirmButton();
    await waitFor(() => expect(onRestore).toHaveBeenCalledWith("p2"));

    fireEvent.click(screen.getByRole("button", { name: /删除/ }));
    await waitFor(() => expect(screen.getByText("确认永久删除此项目？")).not.toBeNull());
    await clickLatestConfirmButton();
    await waitFor(() => expect(onDelete).toHaveBeenCalledWith("p2"));
  }, 15000);
});
