import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import { beforeEach, expect, test, vi } from "vitest";
import { ToastProvider } from "@/shared/ui/ToastProvider.jsx";
import { fetchLuckyGiftDraws, fetchOpsDashboard, saveLuckyGiftConfig } from "./api.js";
import { initialOpsCenterView, OpsCenterApp } from "./OpsCenterApp.jsx";

vi.mock("./api.js", () => ({
    DEFAULT_POOL_ID: "default",
    fetchLuckyGiftDraws: vi.fn(),
    fetchOpsDashboard: vi.fn(),
    saveLuckyGiftConfig: vi.fn(),
}));

beforeEach(() => {
    vi.clearAllMocks();
    window.history.replaceState({}, "", "/");
    saveLuckyGiftConfig.mockResolvedValue({ app_code: "lalu", pool_id: "lucky" });
    fetchLuckyGiftDraws.mockResolvedValue({
        items: [
            {
                app_code: "lalu",
                created_at_ms: 1767000000000,
                draw_id: "draw-1",
                effective_reward_coins: 100,
                external_user_id: "ext-1",
                gift_id: "gift-9",
                multiplier_ppm: 2_000_000,
                pool_id: "super_lucky",
                reward_status: "granted",
                rule_version: 2,
            },
        ],
        page: 1,
        pageSize: 20,
        total: 1,
    });
    fetchOpsDashboard.mockResolvedValue({
        apps: [
            { app_code: "lalu", app_name: "Lalu", source: "registry", status: "active", updated_at_ms: 1767000000000 },
            { app_code: "yumi", app_name: "Yumi", source: "fixed", status: "active" },
        ],
        appSummaries: [
            {
                actual_rtp_ppm: 900000,
                app_code: "lalu",
                failed_draws: 0,
                granted_draws: 3,
                pending_draws: 0,
                total_draws: 3,
                total_reward_coins: 900,
                total_spent_coins: 1000,
                unique_rooms: 2,
                unique_users: 2,
            },
        ],
        luckyGifts: [
            {
                ...mockDynamicConfig(),
                app_code: "lalu",
                enabled: true,
                is_default: false,
                pool_id: "lucky",
                rule_version: 3,
            },
            {
                ...mockFixedConfig(92),
                app_code: "lalu",
                enabled: false,
                is_default: false,
                pool_id: "super_lucky",
                rule_version: 2,
            },
            {
                ...mockDynamicConfig(),
                app_code: "yumi",
                enabled: false,
                is_default: true,
                pool_id: "default",
                rule_version: 0,
            },
        ],
        poolBalances: [
            {
                app_code: "lalu",
                available_balance: 37950017,
                balance: 38559017,
                materialized: true,
                pool_id: "lucky",
                reserve_floor: 609000,
                updated_at_ms: 1767000000000,
            },
            {
                app_code: "yumi",
                available_balance: 361500,
                balance: 382500,
                materialized: false,
                pool_id: "default",
                reserve_floor: 21000,
            },
        ],
        summary: {
            activeApps: 2,
            configuredPools: 2,
            enabledPools: 1,
            failedDraws: 0,
            grantedDraws: 3,
            pendingDraws: 0,
            poolAvailableTotal: 38311517,
            poolBalanceTotal: 38941517,
            poolReserveTotal: 630000,
            totalDraws: 3,
            totalRewardCoins: 900,
            totalSpentCoins: 1000,
        },
    });
});

function renderApp() {
    return render(
        <ToastProvider>
            <OpsCenterApp />
        </ToastProvider>,
    );
}

test("renders overview with aggregated kpis and pool balances", async () => {
    renderApp();

    expect(await screen.findByRole("heading", { name: "运营配置中心" })).toBeTruthy();
    expect(screen.getByRole("navigation", { name: "运营中心导航" })).toBeTruthy();
    ["数据总览", "幸运礼物配置", "抽奖记录", "应用列表"].forEach((label) => {
        expect(screen.getByRole("button", { name: new RegExp(label) })).toBeTruthy();
    });

    expect(await screen.findByText("运营应用")).toBeTruthy();
    // 抽奖次数 KPI 必须是跨应用聚合值，而不是第一个应用的汇总（KPI 卡和汇总表列头各出现一次）。
    expect(screen.getAllByText("抽奖次数").length).toBeGreaterThan(0);
    expect(screen.getAllByText("38,941,517").length).toBeGreaterThan(0);
    expect(screen.getByText("奖池实时水位")).toBeTruthy();
    expect(screen.getByText("已入账")).toBeTruthy();
    expect(screen.getByText("默认水位")).toBeTruthy();
    expect(fetchOpsDashboard).toHaveBeenCalledTimes(1);
});

test("opens the requested config view from the legacy admin document redirect", async () => {
    expect(initialOpsCenterView("?view=configs")).toBe("configs");
    expect(initialOpsCenterView("?view=unknown")).toBe("overview");
    window.history.replaceState({}, "", "/ops-center/?view=configs");

    renderApp();

    expect(await screen.findByRole("button", { name: "添加配置" })).toBeTruthy();
    expect(screen.getByText("lucky")).toBeTruthy();
});

test("lists every pool config including published non-default pools", async () => {
    renderApp();
    await screen.findByText("运营应用");

    fireEvent.click(screen.getByRole("button", { name: /幸运礼物配置/ }));

    // 核心回归：lalu 的 lucky / super_lucky 已发布奖池必须出现在配置列表里。
    expect(await screen.findByText("lucky")).toBeTruthy();
    expect(screen.getByText("super_lucky")).toBeTruthy();
    expect(screen.getByText("v3")).toBeTruthy();
    expect(screen.getByText("v2")).toBeTruthy();
    expect(screen.getByText("已启用")).toBeTruthy();
    expect(screen.getByText("已停用")).toBeTruthy();
    expect(screen.getByText("默认草稿")).toBeTruthy();
});

test("creates a disabled dynamic draft without copying another app monetary limits", async () => {
    renderApp();
    await screen.findByText("运营应用");
    fireEvent.click(screen.getByRole("button", { name: /幸运礼物配置/ }));
    fireEvent.click(await screen.findByRole("button", { name: "添加配置" }));

    const dialog = await screen.findByRole("dialog");
    expect(within(dialog).getByText("添加幸运礼物配置")).toBeTruthy();
    expect(within(dialog).getByLabelText("策略版本").textContent).toContain("dynamic_v3");
    expect(within(dialog).getByLabelText(/初始奖池金币/).value).toBe("1000000");
    // 第一条 lalu 已发布配置的单次上限为 100 万；新建跨 App 配置必须归零并保持停用。
    expect(within(dialog).getByLabelText(/单次返奖上限/).value).toBe("0");
    expect(within(dialog).getByText("发布后保持停用")).toBeTruthy();
});

test("opens config dialog from a published pool row and saves a new version", async () => {
    renderApp();
    await screen.findByText("运营应用");

    fireEvent.click(screen.getByRole("button", { name: /幸运礼物配置/ }));
    await screen.findByText("lucky");

    fireEvent.click(screen.getAllByRole("button", { name: "新增版本" })[0]);
    const dialog = await screen.findByRole("dialog");
    expect(within(dialog).getByText("新增幸运礼物版本")).toBeTruthy();
    expect(within(dialog).getByLabelText("策略版本").textContent).toContain("dynamic_v3");
    expect(within(dialog).getByLabelText(/初始奖池金币/).value).toBe("1000000");
    expect(within(dialog).getByLabelText(/大奖倍率/).value).toBe("200, 500, 1000");
    expect(within(dialog).getAllByLabelText("概率 %")[0].readOnly).toBe(true);

    fireEvent.change(within(dialog).getByLabelText(/目标 RTP \(%\)/), { target: { value: "88" } });
    fireEvent.click(within(dialog).getByRole("button", { name: "保存配置" }));

    await waitFor(() => {
        expect(saveLuckyGiftConfig).toHaveBeenCalledWith(
            expect.objectContaining({
                app_code: "lalu",
                anchor_rate_percent: 1,
                jackpot_multipliers: [200, 500, 1000],
                pool_id: "lucky",
                profit_rate_percent: 1,
                stages: expect.arrayContaining([expect.objectContaining({ stage: "novice" })]),
                strategy_version: "dynamic_v3",
                target_rtp_percent: 88,
            }),
        );
    });
});

test("blocks an invalid enabled dynamic rule before calling the save API", async () => {
    renderApp();
    await screen.findByText("运营应用");
    fireEvent.click(screen.getByRole("button", { name: /幸运礼物配置/ }));
    await screen.findByText("lucky");
    fireEvent.click(screen.getAllByRole("button", { name: "新增版本" })[0]);

    const dialog = await screen.findByRole("dialog");
    fireEvent.change(within(dialog).getByLabelText(/初始奖池金币/), { target: { value: "0" } });
    fireEvent.click(within(dialog).getByRole("button", { name: "保存配置" }));

    expect(await within(dialog).findByRole("alert")).toHaveTextContent("初始奖池必须大于 0");
    expect(saveLuckyGiftConfig).not.toHaveBeenCalled();

    // 奖档编辑同样必须清掉上一次提交错误，避免用户修正后仍看到已经过期的阻断原因。
    fireEvent.change(within(dialog).getAllByLabelText("倍率")[0], { target: { value: "0.1" } });
    expect(within(dialog).queryByRole("alert")).toBeNull();
});

function mockDynamicConfig() {
    return {
        anchor_daily_payout_cap: 6_000_000,
        anchor_rate_percent: 1,
        control_band_percent: 3,
        device_daily_payout_cap: 4_000_000,
        effective_from_ms: 0,
        gift_price_reference: 100,
        high_water_nonzero_factor_percent: 130,
        high_watermark_coins: 20_000_000,
        initial_pool_coins: 1_000_000,
        jackpot_global_rtp_max_percent: 98,
        jackpot_multipliers: [200, 500, 1000],
        jackpot_spend_threshold_coins: 50_000,
        jackpot_user_72h_rtp_max_percent: 96,
        jackpot_user_day_rtp_max_percent: 96,
        loss_streak_guarantee: 5,
        low_water_nonzero_factor_percent: 70,
        low_watermark_coins: 10_000_000,
        max_jackpot_hits_per_user_day: 5,
        max_single_payout: 1_000_000,
        normal_max_equivalent_draws: 20_000,
        novice_max_equivalent_draws: 2_000,
        pool_rate_percent: 98,
        profit_rate_percent: 1,
        recharge_boost_factor_percent: 110,
        recharge_boost_window_ms: 300_000,
        room_hourly_payout_cap: 5_000_000,
        settlement_window_wager: 1_000_000,
        stages: mockStages(98),
        strategy_version: "dynamic_v3",
        target_rtp_percent: 98,
        user_daily_payout_cap: 3_000_000,
        user_hourly_payout_cap: 2_000_000,
    };
}

function mockFixedConfig(targetRTP) {
    return {
        control_band_percent: 1,
        gift_price_reference: 100,
        normal_max_equivalent_draws: 20_000,
        novice_max_equivalent_draws: 2_000,
        pool_rate_percent: targetRTP + 1,
        settlement_window_wager: 1_000_000,
        stages: mockStages(targetRTP),
        strategy_version: "fixed_v2",
        target_rtp_percent: targetRTP,
    };
}

function mockStages(targetRTP) {
    return ["novice", "normal", "advanced"].map((stage, index) => ({
        min_recharge_30d_coins: index === 0 ? 0 : index,
        min_recharge_7d_coins: index === 2 ? 1 : 0,
        stage,
        tiers: [
            {
                enabled: true,
                high_water_only: false,
                multiplier: 0,
                probability_percent: 100 - targetRTP,
                tier_id: `${stage}_none`,
            },
            {
                enabled: true,
                high_water_only: false,
                multiplier: 1,
                probability_percent: targetRTP,
                tier_id: `${stage}_1x`,
            },
        ],
    }));
}

test("loads draw records for the first app with server pagination", async () => {
    renderApp();
    await screen.findByText("运营应用");

    fireEvent.click(screen.getByRole("button", { name: /抽奖记录/ }));

    await waitFor(() => {
        expect(fetchLuckyGiftDraws).toHaveBeenCalledWith(
            expect.objectContaining({ appCode: "lalu", page: 1, poolId: "", status: "" }),
        );
    });
    expect(await screen.findByText("ext-1")).toBeTruthy();
    expect(screen.getByText("已发放")).toBeTruthy();
    expect(screen.getByText("2x")).toBeTruthy();
});

test("shows app registry and fixed integrations in apps view", async () => {
    renderApp();
    await screen.findByText("运营应用");

    fireEvent.click(screen.getByRole("button", { name: /应用列表/ }));

    expect(await screen.findByText("Lalu")).toBeTruthy();
    expect(screen.getByText("主数据")).toBeTruthy();
    expect(screen.getByText("外部接入")).toBeTruthy();
});
