import { act, renderHook } from "@testing-library/react";
import { afterEach, beforeEach, expect, test, vi } from "vitest";
import { useResourceListPage } from "./useResourcePages.js";

const mocks = vi.hoisted(() => ({
    batchDeleteResources: vi.fn(),
    can: vi.fn(),
    confirm: vi.fn(),
    deleteResource: vi.fn(),
    reload: vi.fn(),
    showToast: vi.fn(),
    usePaginatedQuery: vi.fn(),
}));

vi.mock("@/app/auth/AuthProvider.jsx", () => ({
    useAuth: () => ({ can: mocks.can }),
}));

vi.mock("@/shared/hooks/usePaginatedQuery.js", () => ({
    usePaginatedQuery: mocks.usePaginatedQuery,
}));

vi.mock("@/shared/ui/ConfirmProvider.jsx", () => ({
    useConfirm: () => mocks.confirm,
}));

vi.mock("@/shared/ui/ToastProvider.jsx", () => ({
    useToast: () => ({ showToast: mocks.showToast }),
}));

vi.mock("@/features/resources/api", async (importOriginal) => ({
    ...(await importOriginal()),
    batchDeleteResources: mocks.batchDeleteResources,
    deleteResource: mocks.deleteResource,
}));

beforeEach(() => {
    mocks.can.mockReturnValue(true);
    mocks.confirm.mockResolvedValue(true);
    mocks.batchDeleteResources.mockResolvedValue([]);
    mocks.deleteResource.mockResolvedValue({});
    mocks.reload.mockResolvedValue(undefined);
    mocks.usePaginatedQuery.mockReturnValue({
        data: resourcePage(),
        error: null,
        loading: false,
        reload: mocks.reload,
    });
});

afterEach(() => {
    vi.clearAllMocks();
});

test("deleting a resource hides only that row without reloading the list", async () => {
    const { result } = renderHook(() => useResourceListPage());

    let deletedResourceIds = [];
    await act(async () => {
        deletedResourceIds = await result.current.deleteResources([resourceFixture({ resourceId: 11 })]);
    });

    expect(deletedResourceIds).toEqual([11]);
    expect(mocks.deleteResource).toHaveBeenCalledWith(11);
    expect(mocks.reload).not.toHaveBeenCalled();
    expect(result.current.data.items.map((resource) => resource.resourceId)).toEqual([12]);
    expect(mocks.showToast).toHaveBeenCalledWith("资源已删除", "success");
});

test("batch resource delete hides successful rows without reloading the list", async () => {
    const { result } = renderHook(() => useResourceListPage());

    let deletedResourceIds = [];
    await act(async () => {
        deletedResourceIds = await result.current.deleteResources([
            resourceFixture({ resourceId: 11 }),
            resourceFixture({ resourceId: 12 }),
        ]);
    });

    expect(deletedResourceIds).toEqual([11, 12]);
    expect(mocks.batchDeleteResources).toHaveBeenCalledWith([11, 12]);
    expect(mocks.deleteResource).not.toHaveBeenCalled();
    expect(mocks.reload).not.toHaveBeenCalled();
    expect(result.current.data.items).toEqual([]);
    expect(mocks.showToast).toHaveBeenCalledWith("已删除 2 个资源", "success");
});

test("failed batch resource delete keeps all rows visible", async () => {
    mocks.usePaginatedQuery.mockReturnValue({
        data: {
            items: [
                resourceFixture({ resourceId: 11 }),
                resourceFixture({ resourceId: 12 }),
                resourceFixture({ resourceId: 13 }),
            ],
            page: 1,
            pageSize: 50,
            total: 3,
        },
        error: null,
        loading: false,
        reload: mocks.reload,
    });
    mocks.batchDeleteResources.mockRejectedValueOnce(new Error("delete failed"));
    const { result } = renderHook(() => useResourceListPage());

    let deletedResourceIds = [];
    await act(async () => {
        deletedResourceIds = await result.current.deleteResources([
            resourceFixture({ resourceId: 11 }),
            resourceFixture({ resourceId: 12 }),
            resourceFixture({ resourceId: 13 }),
        ]);
    });

    expect(deletedResourceIds).toEqual([]);
    expect(mocks.batchDeleteResources).toHaveBeenCalledWith([11, 12, 13]);
    expect(mocks.deleteResource).not.toHaveBeenCalled();
    expect(result.current.data.items.map((resource) => resource.resourceId)).toEqual([11, 12, 13]);
    expect(mocks.showToast).toHaveBeenCalledWith("delete failed", "error");
});

test("failed resource delete keeps the row visible", async () => {
    mocks.deleteResource.mockRejectedValueOnce(new Error("delete failed"));
    const { result } = renderHook(() => useResourceListPage());

    let deletedResourceIds = [];
    await act(async () => {
        deletedResourceIds = await result.current.deleteResources([resourceFixture({ resourceId: 11 })]);
    });

    expect(deletedResourceIds).toEqual([]);
    expect(mocks.reload).not.toHaveBeenCalled();
    expect(result.current.data.items.map((resource) => resource.resourceId)).toEqual([11, 12]);
    expect(mocks.showToast).toHaveBeenCalledWith("delete failed", "error");
});

function resourcePage() {
    return {
        items: [resourceFixture({ resourceId: 11, name: "Rose" }), resourceFixture({ resourceId: 12, name: "Ring" })],
        page: 1,
        pageSize: 50,
        total: 2,
    };
}

function resourceFixture(patch = {}) {
    return {
        coinPrice: 10,
        name: "Rose",
        resourceCode: "gift_rose",
        resourceId: 11,
        resourceType: "gift",
        status: "active",
        ...patch,
    };
}
