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

const mocks = vi.hoisted(() => ({
    can: vi.fn(),
    getVipConfig: vi.fn(),
    getVipProgram: vi.fn(),
    grantResource: vi.fn(),
    grantResourceGroup: vi.fn(),
    grantVip: vi.fn(),
    grantVipTrialCard: vi.fn(),
    listGiftTypes: vi.fn(),
    listGifts: vi.fn(),
    listResourceGroups: vi.fn(),
    listResources: vi.fn(),
    lookupResourceGrantTarget: 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: () => vi.fn(),
}));

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

vi.mock("@/features/resources/api", async (importOriginal) => ({
    ...(await importOriginal()),
    grantResource: mocks.grantResource,
    grantResourceGroup: mocks.grantResourceGroup,
    listGiftTypes: mocks.listGiftTypes,
    listGifts: mocks.listGifts,
    listResourceGroups: mocks.listResourceGroups,
    listResources: mocks.listResources,
    lookupResourceGrantTarget: mocks.lookupResourceGrantTarget,
}));

vi.mock("@/features/vip-config/api", () => ({
    getVipConfig: mocks.getVipConfig,
    getVipProgram: mocks.getVipProgram,
    grantVip: mocks.grantVip,
    grantVipTrialCard: mocks.grantVipTrialCard,
}));

beforeEach(() => {
    mocks.can.mockReturnValue(true);
    mocks.getVipConfig.mockResolvedValue({ levels: [{ level: 5, name: "VIP5", status: "active" }] });
    mocks.getVipProgram.mockResolvedValue({ programType: "legacy_timed", trialCardEnabled: false });
    mocks.grantResource.mockResolvedValue({});
    mocks.grantResourceGroup.mockResolvedValue({});
    mocks.grantVip.mockResolvedValue({ vip: { level: 5, name: "VIP5" } });
    mocks.grantVipTrialCard.mockResolvedValue({});
    mocks.listGiftTypes.mockResolvedValue([]);
    mocks.listGifts.mockResolvedValue({ items: [], page: 1, pageSize: 100, total: 0 });
    mocks.listResourceGroups.mockResolvedValue({ items: [], page: 1, pageSize: 100, total: 0 });
    mocks.listResources.mockResolvedValue({ items: [], page: 1, pageSize: 100, total: 0 });
    mocks.lookupResourceGrantTarget.mockResolvedValue({ userId: "318705991371722752" });
    mocks.reload.mockResolvedValue(undefined);
    mocks.usePaginatedQuery.mockReturnValue({
        data: { items: [], page: 1, pageSize: 50, total: 0 },
        error: null,
        loading: false,
        reload: mocks.reload,
    });
});

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

test("vip grant resolves target user before calling vip grant api", async () => {
    const { result } = renderHook(() => useResourceGrantListPage());

    await act(async () => {
        result.current.setForm(grantFormFixture({ subjectType: "vip", vipLevel: "5" }));
    });

    await act(async () => {
        await result.current.submitGrant({ preventDefault: vi.fn() });
    });

    expect(mocks.lookupResourceGrantTarget).toHaveBeenCalledWith("163000");
    expect(mocks.grantVip).toHaveBeenCalledWith({
        level: 5,
        reason: "manual",
        targetUserId: "318705991371722752",
    });
    expect(mocks.lookupResourceGrantTarget.mock.invocationCallOrder[0]).toBeLessThan(
        mocks.grantVip.mock.invocationCallOrder[0],
    );
    expect(mocks.grantResource).not.toHaveBeenCalled();
    expect(mocks.grantResourceGroup).not.toHaveBeenCalled();
});

test("resource grant branch still calls resource grant api", async () => {
    const { result } = renderHook(() => useResourceGrantListPage());

    await act(async () => {
        result.current.setForm(
            grantFormFixture({
                durationDays: "7",
                quantity: "2",
                resourceIds: ["11"],
                subjectType: "resource",
            }),
        );
    });

    await act(async () => {
        await result.current.submitGrant({ preventDefault: vi.fn() });
    });

    expect(mocks.lookupResourceGrantTarget).toHaveBeenCalledWith("163000");
    expect(mocks.grantResource).toHaveBeenCalledWith(
        expect.objectContaining({
            durationMs: 7 * 24 * 60 * 60 * 1000,
            quantity: 2,
            reason: "manual",
            resourceId: 11,
            targetUserId: "318705991371722752",
        }),
    );
    expect(mocks.grantResource.mock.calls[0][0].commandId).toMatch(/^resource-grant-11-0-/);
    expect(mocks.lookupResourceGrantTarget.mock.invocationCallOrder[0]).toBeLessThan(
        mocks.grantResource.mock.invocationCallOrder[0],
    );
    expect(mocks.grantVip).not.toHaveBeenCalled();
    expect(mocks.grantResourceGroup).not.toHaveBeenCalled();
});

test("resource group grant branch still calls resource group grant api", async () => {
    const { result } = renderHook(() => useResourceGrantListPage());

    await act(async () => {
        result.current.setForm(grantFormFixture({ groupId: "22", subjectType: "resource_group" }));
    });

    await act(async () => {
        await result.current.submitGrant({ preventDefault: vi.fn() });
    });

    expect(mocks.lookupResourceGrantTarget).toHaveBeenCalledWith("163000");
    expect(mocks.grantResourceGroup).toHaveBeenCalledWith(
        expect.objectContaining({
            groupId: 22,
            reason: "manual",
            targetUserId: "318705991371722752",
        }),
    );
    expect(mocks.grantResourceGroup.mock.calls[0][0].commandId).toMatch(/^resource-group-grant-/);
    expect(mocks.lookupResourceGrantTarget.mock.invocationCallOrder[0]).toBeLessThan(
        mocks.grantResourceGroup.mock.invocationCallOrder[0],
    );
    expect(mocks.grantVip).not.toHaveBeenCalled();
    expect(mocks.grantResource).not.toHaveBeenCalled();
});

test("create dialog loads only vip program and levels when user only has vip grant permission", async () => {
    mocks.can.mockImplementation((permission) => permission === "vip-config:grant");
    const { result } = renderHook(() => useResourceGrantListPage());

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

    await waitFor(() => expect(mocks.getVipConfig).toHaveBeenCalledTimes(1));
    expect(result.current.form.subjectType).toBe("vip");
    expect(mocks.getVipProgram).toHaveBeenCalledTimes(1);
    expect(mocks.listResources).not.toHaveBeenCalled();
    expect(mocks.listGifts).not.toHaveBeenCalled();
    expect(mocks.listGiftTypes).not.toHaveBeenCalled();
    expect(mocks.listResourceGroups).not.toHaveBeenCalled();
});

test("create dialog skips vip config when user lacks vip grant permission", async () => {
    mocks.can.mockImplementation((permission) => permission !== "vip-config:grant");
    const { result } = renderHook(() => useResourceGrantListPage());

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

    await waitFor(() => expect(mocks.listResources).toHaveBeenCalled());
    expect(result.current.form.subjectType).toBe("resource");
    expect(mocks.getVipConfig).not.toHaveBeenCalled();
    expect(mocks.getVipProgram).not.toHaveBeenCalled();
});

test("fami vip trial card grant resolves resource by level and sends duration", async () => {
    mocks.getVipProgram.mockResolvedValue({ programType: "tiered_privilege_v1", trialCardEnabled: true });
    const { result } = renderHook(() => useResourceGrantListPage());

    await act(async () => {
        result.current.openCreateGrant();
    });
    await waitFor(() => expect(result.current.vipProgram.programType).toBe("tiered_privilege_v1"));
    await act(async () => {
        result.current.setForm(
            grantFormFixture({
                durationDays: "20",
                subjectType: "vip_trial_card",
                vipLevel: "5",
            }),
        );
    });
    await act(async () => {
        await result.current.submitGrant({ preventDefault: vi.fn() });
    });

    expect(mocks.grantVipTrialCard).toHaveBeenCalledWith(
        expect.objectContaining({
            durationMs: 20 * 24 * 60 * 60 * 1000,
            level: 5,
            reason: "manual",
            targetUserId: "318705991371722752",
        }),
    );
    expect(mocks.grantVip).not.toHaveBeenCalled();
});

function grantFormFixture(patch = {}) {
    return {
        durationDays: "7",
        groupId: "",
        quantity: "1",
        reason: "manual",
        resourceId: "",
        resourceIds: [],
        subjectType: "resource",
        targetUserId: "163000",
        vipLevel: "",
        ...patch,
    };
}
