// @vitest-environment jsdom
import { act, renderHook } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import type { RuleView } from "@/lib/api/types";
import { useRules } from "./useRules";

const post = vi.fn();
const del = vi.fn();
const toast = vi.fn();
vi.mock("@/lib/api/client", () => ({
  api: { POST: (...a: unknown[]) => post(...a), DELETE: (...a: unknown[]) => del(...a) },
}));
vi.mock("@/components/Toast", () => ({ useToast: () => toast }));

const seed: RuleView[] = [{ id: "r0", level: "global", content: "已有规则" }];

describe("useRules", () => {
  beforeEach(() => {
    post.mockReset();
    del.mockReset();
    toast.mockReset();
  });
  afterEach(() => vi.clearAllMocks());

  it("初始 items = 传入值，adding=false、deletingId=null", () => {
    const { result } = renderHook(() => useRules(seed));
    expect(result.current.items).toEqual(seed);
    expect(result.current.adding).toBe(false);
    expect(result.current.deletingId).toBeNull();
  });

  it("add 空白正文：弹错、不请求、返回 false", async () => {
    const { result } = renderHook(() => useRules(seed));
    let ok = true;
    await act(async () => {
      ok = await result.current.add("p1", "global", "   ");
    });
    expect(ok).toBe(false);
    expect(post).not.toHaveBeenCalled();
    expect(toast).toHaveBeenCalledWith("请填写规则正文。", "error");
  });

  it("add 成功：用服务端权威行替换乐观行、弹成功、返回 true", async () => {
    const authoritative: RuleView = { id: "r1", level: "global", content: "新规则" };
    post.mockResolvedValue({ data: authoritative, error: null });

    const { result } = renderHook(() => useRules(seed));
    let ok = false;
    await act(async () => {
      ok = await result.current.add("p1", "global", "新规则");
    });

    expect(ok).toBe(true);
    expect(result.current.items).toEqual([...seed, authoritative]);
    expect(result.current.adding).toBe(false);
    expect(toast).toHaveBeenCalledWith("已新增规则", "success");
  });

  it("add 后端 error：回滚到快照、弹错、返回 false", async () => {
    post.mockResolvedValue({ data: null, error: { detail: "boom" } });
    const { result } = renderHook(() => useRules(seed));
    let ok = true;
    await act(async () => {
      ok = await result.current.add("p1", "global", "新规则");
    });
    expect(ok).toBe(false);
    expect(result.current.items).toEqual(seed);
    expect(toast).toHaveBeenCalledWith("新增规则失败，请稍后重试。", "error");
  });

  it("remove 成功：乐观移除、弹成功、返回 true", async () => {
    del.mockResolvedValue({ error: null });
    const { result } = renderHook(() => useRules(seed));
    let ok = false;
    await act(async () => {
      ok = await result.current.remove("p1", "r0");
    });
    expect(ok).toBe(true);
    expect(result.current.items).toEqual([]);
    expect(toast).toHaveBeenCalledWith("已删除规则", "success");
  });

  it("add 进行中：adding=true 而 deletingId 仍为 null（互不冻结）", async () => {
    let resolvePost: (v: { data: RuleView; error: null }) => void = () => {};
    post.mockReturnValue(
      new Promise((res) => {
        resolvePost = res;
      }),
    );
    const { result } = renderHook(() => useRules(seed));
    let done: Promise<boolean> = Promise.resolve(false);
    act(() => {
      done = result.current.add("p1", "global", "新规则");
    });
    expect(result.current.adding).toBe(true);
    expect(result.current.deletingId).toBeNull();
    await act(async () => {
      resolvePost({ data: { id: "r1", level: "global", content: "新规则" }, error: null });
      await done;
    });
    expect(result.current.adding).toBe(false);
  });

  it("remove 进行中：deletingId=目标 id 而 adding 仍为 false（只冻结该行）", async () => {
    let resolveDel: (v: { error: null }) => void = () => {};
    del.mockReturnValue(
      new Promise((res) => {
        resolveDel = res;
      }),
    );
    const { result } = renderHook(() => useRules(seed));
    let done: Promise<boolean> = Promise.resolve(false);
    act(() => {
      done = result.current.remove("p1", "r0");
    });
    expect(result.current.deletingId).toBe("r0");
    expect(result.current.adding).toBe(false);
    await act(async () => {
      resolveDel({ error: null });
      await done;
    });
    expect(result.current.deletingId).toBeNull();
  });

  it("remove 后端 error：回滚、弹错、返回 false", async () => {
    del.mockResolvedValue({ error: { detail: "boom" } });
    const { result } = renderHook(() => useRules(seed));
    let ok = true;
    await act(async () => {
      ok = await result.current.remove("p1", "r0");
    });
    expect(ok).toBe(false);
    expect(result.current.items).toEqual(seed);
    expect(toast).toHaveBeenCalledWith("删除规则失败，请稍后重试。", "error");
  });
});
