/**
 * useApi / usePaginatedApi / useApiMutation Hooks 单元测试
 *
 * 覆盖：加载/成功/错误状态、分页参数、mutation trigger
 */

import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook, act, waitFor } from "@testing-library/react";
import useSWR from "swr";
import {
  useApi,
  usePaginatedApi,
  useApiMutation,
  swrFetcher,
} from "@/lib/hooks/use-api";
import type { PaginatedResponse } from "@/lib/hooks/use-api";

// ── Mock fetchWithAuth ────────────────────────────────────────────────────────

vi.mock("@/lib/api/client", () => ({
  fetchWithAuth: vi.fn(),
}));

import { fetchWithAuth } from "@/lib/api/client";
const mockFetchWithAuth = vi.mocked(fetchWithAuth);

// ── swrFetcher ────────────────────────────────────────────────────────────────

describe("swrFetcher", () => {
  it("应调用 fetchWithAuth 并返回结果", async () => {
    mockFetchWithAuth.mockResolvedValueOnce({ data: "test" });

    const result = await swrFetcher("/api/test");
    expect(result).toEqual({ data: "test" });
    expect(mockFetchWithAuth).toHaveBeenCalledWith("/api/test");
  });
});

// ── useApi ────────────────────────────────────────────────────────────────────

describe("useApi", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    // 清除 SWR 缓存，避免测试间数据残留
    useSWR.clearCache?.();
  });

  it("url 为 null 时应暂停请求", () => {
    const { result } = renderHook(() => useApi(null));

    expect(result.current.isLoading).toBe(false);
    expect(result.current.data).toBeUndefined();
    expect(mockFetchWithAuth).not.toHaveBeenCalled();
  });

  it("请求成功应返回数据", async () => {
    const mockData = { items: [{ id: "1", name: "品牌" }], total: 1 };
    mockFetchWithAuth.mockResolvedValueOnce(mockData);

    const { result } = renderHook(() => useApi("/api/brands-success"));

    await waitFor(() => {
      expect(result.current.data).toEqual(mockData);
      expect(result.current.error).toBeUndefined();
      expect(result.current.isLoading).toBe(false);
    });
  });

  it("请求失败应返回错误", async () => {
    mockFetchWithAuth.mockRejectedValueOnce(new Error("网络错误"));

    const { result } = renderHook(() => useApi("/api/brands-error"));

    await waitFor(() => {
      expect(result.current.error).toBeInstanceOf(Error);
      expect(result.current.error?.message).toBe("网络错误");
    });
  });

  it("refresh 应触发重新获取", async () => {
    mockFetchWithAuth.mockResolvedValue({ items: [] });

    const { result } = renderHook(() => useApi("/api/brands-refresh"));

    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
    });

    const callCount = mockFetchWithAuth.mock.calls.length;
    act(() => {
      result.current.refresh();
    });

    await waitFor(() => {
      expect(mockFetchWithAuth.mock.calls.length).toBeGreaterThan(callCount);
    });
  });
});

// ── usePaginatedApi ───────────────────────────────────────────────────────────

describe("usePaginatedApi", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    useSWR.clearCache?.();
  });

  it("应正确构造分页 URL", async () => {
    const mockResponse: PaginatedResponse<{ id: string }> = {
      items: [{ id: "1" }],
      total: 10,
    };
    mockFetchWithAuth.mockResolvedValueOnce(mockResponse);

    renderHook(() =>
      usePaginatedApi("/api/brands-page1", { page: 1, pageSize: 10 })
    );

    await waitFor(() => {
      // page=1, pageSize=10 → offset=0, limit=10
      expect(mockFetchWithAuth).toHaveBeenCalledWith(
        "/api/brands-page1?limit=10&offset=0"
      );
    });
  });

  it("第2页应正确计算 offset", async () => {
    const mockResponse: PaginatedResponse<{ id: string }> = {
      items: [],
      total: 20,
    };
    mockFetchWithAuth.mockResolvedValueOnce(mockResponse);

    renderHook(() =>
      usePaginatedApi("/api/brands-page2", { page: 2, pageSize: 5 })
    );

    await waitFor(() => {
      // page=2, pageSize=5 → offset=5, limit=5
      expect(mockFetchWithAuth).toHaveBeenCalledWith(
        "/api/brands-page2?limit=5&offset=5"
      );
    });
  });

  it("baseUrl 为 null 时应暂停请求", () => {
    const { result } = renderHook(() =>
      usePaginatedApi(null, { page: 1, pageSize: 10 })
    );

    expect(result.current.isLoading).toBe(false);
    expect(result.current.data).toBeUndefined();
  });

  it("应返回 total 和分页信息", async () => {
    const mockResponse: PaginatedResponse<{ id: string }> = {
      items: [{ id: "1" }, { id: "2" }],
      total: 25,
    };
    mockFetchWithAuth.mockResolvedValueOnce(mockResponse);

    const { result } = renderHook(() =>
      usePaginatedApi("/api/brands-total", { page: 1, pageSize: 10 })
    );

    await waitFor(() => {
      expect(result.current.data).toEqual([{ id: "1" }, { id: "2" }]);
      expect(result.current.total).toBe(25);
      expect(result.current.page).toBe(1);
      expect(result.current.pageSize).toBe(10);
    });
  });

  it("setPage 应更新页码", async () => {
    mockFetchWithAuth.mockResolvedValue({
      items: [],
      total: 30,
    });

    const { result } = renderHook(() =>
      usePaginatedApi("/api/brands-setpage", { page: 1, pageSize: 10 })
    );

    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
    });

    act(() => {
      result.current.setPage(3);
    });

    await waitFor(() => {
      expect(result.current.page).toBe(3);
    });
  });

  it("数据未加载时 total 应为 0", () => {
    // 用 null URL 确保无请求发出，total 为 0
    const { result } = renderHook(() =>
      usePaginatedApi(null, { page: 1, pageSize: 10 })
    );

    expect(result.current.total).toBe(0);
  });
});

// ── useApiMutation ────────────────────────────────────────────────────────────

describe("useApiMutation", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    useSWR.clearCache?.();
  });

  it("trigger 成功应返回数据", async () => {
    const mockResult = { id: "1", name: "新品牌" };
    mockFetchWithAuth.mockResolvedValueOnce(mockResult);

    const { result } = renderHook(() =>
      useApiMutation("/api/brands", "POST")
    );

    let response: unknown;
    await act(async () => {
      response = await result.current.trigger({ name: "新品牌" });
    });

    expect(response).toEqual(mockResult);
    expect(result.current.isMutating).toBe(false);
    expect(result.current.error).toBeUndefined();
  });

  it("trigger 失败应返回 null 并设置 error", async () => {
    mockFetchWithAuth.mockRejectedValueOnce(new Error("服务器错误"));

    const { result } = renderHook(() =>
      useApiMutation("/api/brands", "POST")
    );

    let response: unknown;
    await act(async () => {
      response = await result.current.trigger({ name: "失败" });
    });

    expect(response).toBeNull();
    expect(result.current.error).toBeInstanceOf(Error);
    expect(result.current.error?.message).toBe("服务器错误");
    expect(result.current.isMutating).toBe(false);
  });

  it("trigger 过程中 isMutating 应为 true", async () => {
    let resolveMutation: (value: unknown) => void;
    mockFetchWithAuth.mockReturnValueOnce(
      new Promise((resolve) => {
        resolveMutation = resolve;
      })
    );

    const { result } = renderHook(() =>
      useApiMutation("/api/brands", "PUT")
    );

    act(() => {
      result.current.trigger({ name: "更新" });
    });

    await waitFor(() => {
      expect(result.current.isMutating).toBe(true);
    });

    resolveMutation!({ id: "1" });

    await waitFor(() => {
      expect(result.current.isMutating).toBe(false);
    });
  });

  it("reset 应清除错误状态", async () => {
    mockFetchWithAuth.mockRejectedValueOnce(new Error("出错了"));

    const { result } = renderHook(() =>
      useApiMutation("/api/brands", "DELETE")
    );

    await act(async () => {
      await result.current.trigger();
    });

    expect(result.current.error).toBeDefined();

    act(() => {
      result.current.reset();
    });

    expect(result.current.error).toBeUndefined();
  });

  it("应正确传递 method 和 body", async () => {
    mockFetchWithAuth.mockResolvedValueOnce({ success: true });

    const { result } = renderHook(() =>
      useApiMutation("/api/brands/1", "PUT")
    );

    await act(async () => {
      await result.current.trigger({ name: "更新品牌" });
    });

    expect(mockFetchWithAuth).toHaveBeenCalledWith("/api/brands/1", {
      method: "PUT",
      body: JSON.stringify({ name: "更新品牌" }),
    });
  });
});
