import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { MemoryRouter } from "react-router-dom";
import { afterEach, expect, test, vi } from "vitest";
import { AppProviders } from "@/app/providers.jsx";
import { queryClient } from "@/shared/query/client";
import { CoinAdjustmentPage } from "./CoinAdjustmentPage.jsx";

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

afterEach(() => {
    queryClient.clear();
    vi.unstubAllGlobals();
});

test("coin adjustment does not submit when remark is blank", async () => {
    const user = userEvent.setup();
    vi.stubGlobal("fetch", createCoinAdjustmentFetch());

    renderPage();
    await openReadyDialog(user);
    await user.type(screen.getByRole("textbox", { name: "用户 ID" }), "1001");
    await user.click(screen.getByRole("button", { name: "搜索" }));
    await waitFor(() => expect(screen.getByText("测试用户")).toBeInTheDocument());
    await user.type(screen.getByRole("textbox", { name: "备注" }), "   ");
    await user.type(screen.getByRole("textbox", { name: "发放数量" }), "500");
    await user.click(screen.getByRole("button", { name: "确认" }));

    await waitFor(() => expect(screen.getByText("请输入备注")).toBeInTheDocument());
    expect(coinAdjustmentPostBodies()).toHaveLength(0);
});

test("coin adjustment request body includes trimmed remark", async () => {
	const user = userEvent.setup();
	vi.stubGlobal("fetch", createCoinAdjustmentFetch());

    renderPage();
    await openReadyDialog(user);
    await user.type(screen.getByRole("textbox", { name: "用户 ID" }), "1001");
    await user.click(screen.getByRole("button", { name: "搜索" }));
    await waitFor(() => expect(screen.getByText("测试用户")).toBeInTheDocument());
    await user.type(screen.getByRole("textbox", { name: "备注" }), "  客服复核后补发  ");
    await user.type(screen.getByRole("textbox", { name: "发放数量" }), "500");
    await user.click(screen.getByRole("button", { name: "确认" }));

    await waitFor(() =>
        expect(coinAdjustmentPostBodies()).toEqual([
            expect.objectContaining({
                amount: 500,
                reason: "运营发放",
                remark: "客服复核后补发",
                targetUserId: "1001",
            }),
        ]),
	);
});

test("coin adjustment does not submit when remark exceeds 512 characters", async () => {
	const user = userEvent.setup();
	vi.stubGlobal("fetch", createCoinAdjustmentFetch());

	renderPage();
	await openReadyDialog(user);
	await user.type(screen.getByRole("textbox", { name: "用户 ID" }), "1001");
	await user.click(screen.getByRole("button", { name: "搜索" }));
	await waitFor(() => expect(screen.getByText("测试用户")).toBeInTheDocument());
	fireEvent.change(screen.getByRole("textbox", { name: "备注" }), { target: { value: "补".repeat(513) } });
	await user.type(screen.getByRole("textbox", { name: "发放数量" }), "500");
	await user.click(screen.getByRole("button", { name: "确认" }));

	await waitFor(() => expect(screen.getByText("备注不能超过 512 个字符")).toBeInTheDocument());
	expect(coinAdjustmentPostBodies()).toHaveLength(0);
});

test("coin adjustment list displays remark and falls back for empty remark", async () => {
	vi.stubGlobal(
		"fetch",
        createCoinAdjustmentFetch({
            items: [
                coinAdjustmentFixture({ entryId: 1, remark: "客服复核后补发", transactionId: "tx-remark" }),
                coinAdjustmentFixture({ entryId: 2, remark: "", transactionId: "tx-empty" }),
            ],
        }),
    );

    renderPage();

    await waitFor(() => expect(screen.getByText("客服复核后补发")).toBeInTheDocument());
    expect(screen.getByText("备注")).toBeInTheDocument();
    const rows = document.querySelectorAll(".admin-row:not(.admin-row--head)");
    expect(within(rows[1]).getByText("-")).toBeInTheDocument();
});

function renderPage() {
    return render(
        <AppProviders>
            <MemoryRouter>
                <CoinAdjustmentPage />
            </MemoryRouter>
        </AppProviders>,
    );
}

async function openReadyDialog(user) {
    await waitFor(() => expect(screen.getByText("当前无数据")).toBeInTheDocument());
    await user.click(screen.getByRole("button", { name: "金币增减" }));
    await waitFor(() => expect(screen.getByRole("dialog", { name: "金币增减" })).toBeInTheDocument());
}

function coinAdjustmentPostBodies() {
    return vi
        .mocked(fetch)
        .mock.calls.filter(([url, init]) => String(url).includes("/api/v1/admin/operations/coin-adjustments") && init?.method === "POST")
        .map(([, init]) => JSON.parse(String(init?.body)));
}

function createCoinAdjustmentFetch({ items = [] } = {}) {
    return vi.fn(async (input, init = {}) => {
        const url = String(input);
        const method = init.method || "GET";

        if (url.includes("/api/v1/admin/operations/coin-adjustments/target")) {
            return jsonResponse({
                code: 0,
                data: {
                    displayUserId: "1001",
                    userId: "1001",
                    username: "测试用户",
                },
            });
        }
        if (url.includes("/api/v1/admin/operations/coin-adjustments") && method === "POST") {
            return jsonResponse({
                code: 0,
                data: {
                    amount: 500,
                    availableDelta: 500,
                    balanceAfter: 1500,
                    transactionId: "tx-created",
                    user: { displayUserId: "1001", userId: "1001", username: "测试用户" },
                },
            });
        }
        if (url.includes("/api/v1/admin/operations/coin-adjustments")) {
            return jsonResponse({ code: 0, data: { items, page: 1, pageSize: 50, total: items.length } });
        }

        return jsonResponse({ code: 0, data: {} });
    });
}

function coinAdjustmentFixture(patch = {}) {
    return {
        amount: 500,
        availableAfter: 1500,
        availableDelta: 500,
        createdAtMs: 1762300800000,
        direction: "income",
        entryId: 1,
        operator: { name: "运营" },
        reason: "运营发放",
        remark: "",
        transactionId: "tx-1",
        user: { displayUserId: "1001", userId: "1001", username: "测试用户" },
        userId: "1001",
        ...patch,
    };
}

function jsonResponse(body) {
    return new Response(JSON.stringify(body), {
        headers: { "content-type": "application/json" },
        status: 200,
    });
}
