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

import { useJobPoll } from "./useJobPoll";

// 后端客户端是 hook 的副作用边界；状态机（job.ts）保留真实。
const get = vi.fn();
vi.mock("@/lib/api/client", () => ({ api: { GET: (...a: unknown[]) => get(...a) } }));

const POLL_INTERVAL_MS = 1500;

describe("useJobPoll", () => {
  beforeEach(() => {
    vi.useFakeTimers();
    get.mockReset();
  });
  afterEach(() => {
    vi.useRealTimers();
    vi.clearAllMocks();
  });

  it("初始为 polling、进度 0、无 job", () => {
    const { result } = renderHook(() => useJobPoll());
    expect(result.current.status).toBe("polling");
    expect(result.current.progress).toBe(0);
    expect(result.current.job).toBeNull();
  });

  it("轮询循环：running 持续轮询，done 终态后停止", async () => {
    // Arrange：首拍 running(50)，次拍 done(100)。
    get
      .mockResolvedValueOnce({
        data: { id: "j1", status: "running", progress: 50 },
        error: null,
      })
      .mockResolvedValueOnce({
        data: { id: "j1", status: "done", progress: 100, result: {} },
        error: null,
      });

    const { result } = renderHook(() => useJobPoll());

    // Act：启动轮询，首拍立即执行。
    await act(async () => {
      result.current.poll("j1");
      await vi.advanceTimersByTimeAsync(0);
    });
    expect(result.current.status).toBe("polling");
    expect(result.current.progress).toBe(50);

    // 推进一个轮询间隔触发次拍 → 终态 done。
    await act(async () => {
      await vi.advanceTimersByTimeAsync(POLL_INTERVAL_MS);
    });

    // Assert：done、进度 100；不再继续轮询（仅两次 GET）。
    expect(result.current.status).toBe("done");
    expect(result.current.progress).toBe(100);
    expect(get).toHaveBeenCalledTimes(2);

    await act(async () => {
      await vi.advanceTimersByTimeAsync(POLL_INTERVAL_MS * 3);
    });
    expect(get).toHaveBeenCalledTimes(2);
  });

  it("job failed：进入 error 终态并带后端错误文案", async () => {
    get.mockResolvedValue({
      data: { id: "j1", status: "failed", progress: 30, error: "配额耗尽" },
      error: null,
    });
    const { result } = renderHook(() => useJobPoll());
    await act(async () => {
      result.current.poll("j1");
      await vi.advanceTimersByTimeAsync(0);
    });
    expect(result.current.status).toBe("error");
    expect(result.current.error).toBe("配额耗尽");
  });

  it("后端返回 error 信封：dispatch 失败并显示通用文案", async () => {
    get.mockResolvedValue({ data: null, error: { detail: "500" } });
    const { result } = renderHook(() => useJobPoll());
    await act(async () => {
      result.current.poll("j1");
      await vi.advanceTimersByTimeAsync(0);
    });
    expect(result.current.status).toBe("error");
    expect(result.current.error).toBe("轮询任务状态失败");
  });

  it("请求抛异常：捕获并以异常消息进入 error", async () => {
    get.mockRejectedValue(new Error("network down"));
    const { result } = renderHook(() => useJobPoll());
    await act(async () => {
      result.current.poll("j1");
      await vi.advanceTimersByTimeAsync(0);
    });
    expect(result.current.status).toBe("error");
    expect(result.current.error).toBe("network down");
  });

  it("reset 停止当前轮询：后续不再发起 GET", async () => {
    get.mockResolvedValue({
      data: { id: "j1", status: "running", progress: 10 },
      error: null,
    });
    const { result } = renderHook(() => useJobPoll());
    await act(async () => {
      result.current.poll("j1");
      await vi.advanceTimersByTimeAsync(0);
    });
    const callsBefore = get.mock.calls.length;
    act(() => result.current.reset());
    await act(async () => {
      await vi.advanceTimersByTimeAsync(POLL_INTERVAL_MS * 2);
    });
    expect(get.mock.calls.length).toBe(callsBefore);
  });

  it("卸载清理：定时器被清除，不再继续轮询", async () => {
    get.mockResolvedValue({
      data: { id: "j1", status: "running", progress: 10 },
      error: null,
    });
    const { result, unmount } = renderHook(() => useJobPoll());
    await act(async () => {
      result.current.poll("j1");
      await vi.advanceTimersByTimeAsync(0);
    });
    const callsBefore = get.mock.calls.length;
    unmount();
    await act(async () => {
      await vi.advanceTimersByTimeAsync(POLL_INTERVAL_MS * 2);
    });
    expect(get.mock.calls.length).toBe(callsBefore);
  });
});
