import { describe, expect, test } from "vitest";
import {
    applyDynamicStrategyDefaults,
    applyGiftHostDiamondRatio,
    configToForm,
    formToConfig,
    giftHostDiamondRatioForPool,
    validateLuckyGiftForm,
} from "./configForm.js";

describe("ops center lucky gift dynamic_v3 form", () => {
    test("loads safe dynamic defaults without inventing app-specific monetary caps", () => {
        const form = configToForm({ app_code: "lalu", pool_id: "default", strategy_version: "dynamic_v3" });

        expect(form).toMatchObject({
            anchor_rate_percent: "1",
            enabled: false,
            initial_pool_coins: "0",
            jackpot_multipliers: "200, 500, 1000",
            max_single_payout: "0",
            pool_rate_percent: "98",
            profit_rate_percent: "1",
            strategy_version: "dynamic_v3",
            target_rtp_percent: "98",
        });
        expect(form.stages.map((stage) => [stage.min_recharge_7d_coins, stage.min_recharge_30d_coins])).toEqual([
            ["0", "0"],
            ["0", "0"],
            ["0", "0"],
        ]);
        expect(form.stages.map((stage) => stage.tiers.map((tier) => Number(tier.probabilityPercent)))).toEqual([
            [5, 4, 86, 5],
            [5, 4, 86, 5],
            [5, 4, 86, 5],
        ]);
    });

    test("round-trips every dynamic strategy field and canonical stage keys", () => {
        const config = completeDynamicConfig();
        // 使用空原对象可证明每个字段都真正经过 formToConfig 写回；若沿用 config 作为 spread 基底，
        // 漏掉的动态字段也会被旧值掩盖，测试无法发现发布 payload 的字段丢失。
        const output = formToConfig({}, configToForm(config));

        expect(output).toEqual(config);
        expect(output.stages[1]).toMatchObject({
            min_recharge_30d_coins: 20_000,
            min_recharge_7d_coins: 10_000,
            stage: "normal",
        });
        expect(output.stages[1].tiers[0]).toEqual({
            enabled: true,
            high_water_only: false,
            multiplier: 0,
            probability_percent: 5,
            tier_id: "normal_none",
        });
    });

    test("drops legacy dynamic initial seed when loading and publishing a new version", () => {
        const legacy = completeDynamicConfig();
        legacy.initial_pool_coins = 1_000_000;
        legacy.stages[0].min_recharge_7d_coins = 12_345;
        legacy.stages[0].min_recharge_30d_coins = 67_890;

        const form = configToForm(legacy);
        expect(form.initial_pool_coins).toBe("0");
        expect(form.stages[0]).toMatchObject({ min_recharge_7d_coins: "0", min_recharge_30d_coins: "0" });
        const output = formToConfig(legacy, form);
        expect(output.initial_pool_coins).toBe(0);
        expect(output.stages[0]).toMatchObject({ min_recharge_7d_coins: 0, min_recharge_30d_coins: 0 });
    });

    test("allows disabled drafts but blocks enabled dynamic rules until app monetary limits are filled", () => {
        const draft = configToForm({ app_code: "lalu", pool_id: "default", strategy_version: "dynamic_v3" });
        expect(validateLuckyGiftForm(draft)).toEqual([]);

        const enabled = { ...draft, enabled: true };
        const errors = validateLuckyGiftForm(enabled);

        expect(errors).toContain("启用前普通阶段至少一个最低充值门槛必须大于 0");
        expect(errors).toContain("高阶充值门槛必须逐维不低于正常阶段且至少一项更高");
        expect(errors).toContain("启用前必须填写用户日累计消费触发门槛（金币）");
        expect(errors).toContain("启用前必须填写单次返奖上限");
        expect(errors).toContain("启用前必须填写主播每日上限");
    });

    test("keeps fixed_v2 readable and does not apply dynamic-only validation", () => {
        const form = configToForm({
            app_code: "yumi",
            control_band_percent: 1,
            enabled: true,
            gift_price_reference: 100,
            normal_max_equivalent_draws: 20_000,
            novice_max_equivalent_draws: 2_000,
            pool_id: "legacy",
            pool_rate_percent: 96,
            settlement_window_wager: 1_000_000,
            stages: fixedStages(),
            strategy_version: "fixed_v2",
            target_rtp_percent: 95,
        });

        expect(form.strategy_version).toBe("fixed_v2");
        expect(validateLuckyGiftForm(form)).toEqual([]);
    });

    test("switching a legacy form to dynamic creates a disabled safe draft", () => {
        const legacy = configToForm({
            app_code: "yumi",
            control_band_percent: 1,
            enabled: true,
            gift_price_reference: 100,
            normal_max_equivalent_draws: 20_000,
            novice_max_equivalent_draws: 2_000,
            pool_id: "legacy",
            pool_rate_percent: 96,
            settlement_window_wager: 1_000_000,
            stages: fixedStages(),
            strategy_version: "fixed_v2",
            target_rtp_percent: 95,
        });
        const dynamic = applyDynamicStrategyDefaults(legacy, { anchorRatePercent: 10 });

        expect(dynamic).toMatchObject({
            anchor_rate_percent: "10",
            enabled: false,
            initial_pool_coins: "0",
            pool_rate_percent: "89",
            strategy_version: "dynamic_v3",
            target_rtp_percent: "89",
        });
        expect(dynamic.stages.map((stage) => stage.tiers.map((tier) => Number(tier.multiplier)))).toEqual([
            [0, 0.5, 1, 2],
            [0, 0.5, 1, 2],
            [0, 0.5, 1, 2],
        ]);
        expect(
            dynamic.stages.every(
                (stage) => stage.tiers.reduce((sum, tier) => sum + Number(tier.probabilityPercent), 0) === 100,
            ),
        ).toBe(true);
        expect(validateLuckyGiftForm(dynamic)).toEqual([]);
    });

    test("uses the wallet host diamond ratio and ignores the return-coin ratio", () => {
        const ratios = { lucky: 10, super_lucky: 1 };
        expect(giftHostDiamondRatioForPool(ratios, "lucky")).toBe(10);
        expect(giftHostDiamondRatioForPool(ratios, "campaign_super_lucky_v2")).toBe(1);

        const existing = configToForm({
            ...completeDynamicConfig(),
            anchor_rate_percent: 1,
            pool_id: "lucky",
            pool_rate_percent: 89,
            target_rtp_percent: 89,
            stages: targetStages(89),
        });
        const synced = applyGiftHostDiamondRatio(existing, ratios.lucky);
        expect(synced).toMatchObject({ anchor_rate_percent: "10", pool_rate_percent: "89" });
        expect(validateLuckyGiftForm(synced)).toEqual([]);
    });

    test("rejects invalid disabled draft values that the owner never accepts", () => {
        const draft = configToForm({ app_code: "lalu", pool_id: "default", strategy_version: "dynamic_v3" });
        const errors = validateLuckyGiftForm({
            ...draft,
            jackpot_spend_threshold_coins: "-1",
            max_single_payout: "-1",
            profit_rate_percent: "-1",
            anchor_rate_percent: "2",
            stages: draft.stages.map((stage) =>
                stage.stage === "normal" ? { ...stage, min_recharge_7d_coins: "-1" } : stage,
            ),
        });

        expect(errors).toContain("平台比例和主播钻石比例不能小于 0");
        expect(errors).toContain("大奖消费门槛不能小于 0");
        expect(errors).toContain("单次返奖上限不能小于 0");
        expect(errors).toContain("充值阶段门槛不能小于 0");
    });

    test("keeps ordinary and jackpot paths independent when they use the same multiplier", () => {
        const form = configToForm(completeDynamicConfig());
        const overlapping = {
            ...form,
            stages: form.stages.map((stage) =>
                stage.stage === "normal"
                    ? {
                          ...stage,
                          tiers: [
                              ...stage.tiers.map((tier, index) =>
                                  index === 0 ? { ...tier, probabilityPercent: "4.99" } : tier,
                              ),
                              {
                                  enabled: true,
                                  highWaterOnly: true,
                                  multiplier: "200",
                                  probabilityPercent: "0.01",
                                  tierId: "normal_200x",
                              },
                          ],
                      }
                    : stage,
            ),
        };

        expect(validateLuckyGiftForm(overlapping)).toEqual([]);
        const output = formToConfig({}, overlapping);
        expect(output.jackpot_multipliers).toEqual([200, 500, 1000]);
        expect(output.stages.find((stage) => stage.stage === "normal")?.tiers).toContainEqual({
            enabled: true,
            high_water_only: true,
            multiplier: 200,
            probability_percent: 0.01,
            tier_id: "normal_200x",
        });

        // 放开同倍率不等于放开高倍率安全要求；普通 200x 未确认安全标记时仍必须阻断。
        const unsafe = {
            ...overlapping,
            stages: overlapping.stages.map((stage) =>
                stage.stage === "normal"
                    ? {
                          ...stage,
                          tiers: stage.tiers.map((tier) =>
                              Number(tier.multiplier) === 200 ? { ...tier, highWaterOnly: false } : tier,
                          ),
                      }
                    : stage,
            ),
        };
        expect(validateLuckyGiftForm(unsafe)).toContain("正常阶段10x 及以上奖档必须限制为高水位");
    });
});

function completeDynamicConfig() {
    return {
        anchor_daily_payout_cap: 6_000_000,
        anchor_rate_percent: 1,
        app_code: "lalu",
        control_band_percent: 3,
        device_daily_payout_cap: 4_000_000,
        effective_from_ms: 0,
        enabled: true,
        gift_price_reference: 100,
        high_water_nonzero_factor_percent: 130,
        high_watermark_coins: 20_000_000,
        initial_pool_coins: 0,
        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_id: "default",
        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: dynamicStages(),
        strategy_version: "dynamic_v3",
        target_rtp_percent: 98,
        user_daily_payout_cap: 3_000_000,
        user_hourly_payout_cap: 2_000_000,
        // dynamic_v3 不解释 V2 高水位加权；序列化契约要求这三个字段始终显式归零回传。
        v2_high_water_boost_factor_percent: 0,
        v2_high_water_boost_recover_coins: 0,
        v2_high_water_boost_threshold_coins: 0,
    };
}

function dynamicStages() {
    return [
        dynamicStage("novice", 0, 0),
        dynamicStage("normal", 10_000, 20_000),
        dynamicStage("advanced", 20_000, 30_000),
    ];
}

function dynamicStage(stage, min7d, min30d) {
    return {
        min_recharge_30d_coins: min30d,
        min_recharge_7d_coins: min7d,
        stage,
        tiers: [
            tier(`${stage}_none`, 0, 5),
            tier(`${stage}_0_5x`, 0.5, 4),
            tier(`${stage}_1x`, 1, 86),
            tier(`${stage}_2x`, 2, 5),
        ],
    };
}

function targetStages(targetRTP) {
    return [
        targetStage("novice", 0, 0, targetRTP),
        targetStage("normal", 10_000, 20_000, targetRTP),
        targetStage("advanced", 20_000, 30_000, targetRTP),
    ];
}

function targetStage(stage, min7d, min30d, targetRTP) {
    return {
        min_recharge_30d_coins: min30d,
        min_recharge_7d_coins: min7d,
        stage,
        tiers: [tier(`${stage}_none`, 0, 100 - targetRTP), tier(`${stage}_1x`, 1, targetRTP)],
    };
}

function fixedStages() {
    return ["novice", "normal", "advanced"].map((stage) => ({
        stage,
        tiers: [
            tier(`${stage}_none`, 0, 10),
            tier(`${stage}_0_5x`, 0.5, 20),
            tier(`${stage}_1x`, 1, 55),
            tier(`${stage}_2x`, 2, 15),
        ],
    }));
}

function tier(tierId, multiplier, probabilityPercent) {
    return {
        enabled: true,
        high_water_only: false,
        multiplier,
        probability_percent: probabilityPercent,
        tier_id: tierId,
    };
}

describe("ops center lucky gift v2 high-water boost", () => {
    const fixedForm = (overrides = {}) =>
        configToForm({
            app_code: "yumi",
            control_band_percent: 1,
            enabled: true,
            gift_price_reference: 100,
            normal_max_equivalent_draws: 20_000,
            novice_max_equivalent_draws: 2_000,
            pool_id: "legacy",
            pool_rate_percent: 96,
            settlement_window_wager: 1_000_000,
            stages: fixedStages(),
            strategy_version: "fixed_v2",
            target_rtp_percent: 95,
            ...overrides,
        });

    test("round-trips the boost triple for fixed_v2", () => {
        const form = fixedForm({
            v2_high_water_boost_factor_percent: 150,
            v2_high_water_boost_recover_coins: 1_000_000,
            v2_high_water_boost_threshold_coins: 2_000_000,
        });
        expect(form.v2_high_water_boost_threshold_coins).toBe("2000000");
        expect(form.v2_high_water_boost_factor_percent).toBe("150");
        expect(form.v2_high_water_boost_recover_coins).toBe("1000000");
        expect(validateLuckyGiftForm(form)).toEqual([]);
        const config = formToConfig({}, form);
        expect(config.v2_high_water_boost_threshold_coins).toBe(2_000_000);
        expect(config.v2_high_water_boost_factor_percent).toBe(150);
        expect(config.v2_high_water_boost_recover_coins).toBe(1_000_000);
    });

    test("legacy fixed_v2 snapshots without the fields stay disabled and valid", () => {
        const form = fixedForm();
        expect(form.v2_high_water_boost_threshold_coins).toBe("0");
        expect(validateLuckyGiftForm(form)).toEqual([]);
    });

    test("rejects partial or out-of-range boost triples", () => {
        const halfConfigured = fixedForm({ v2_high_water_boost_factor_percent: 150 });
        expect(validateLuckyGiftForm(halfConfigured)).toContain("关闭 V2 高水位加权时，加权系数与恢复水位必须为 0");

        const factorTooLow = fixedForm({
            v2_high_water_boost_factor_percent: 100,
            v2_high_water_boost_recover_coins: 1_000_000,
            v2_high_water_boost_threshold_coins: 2_000_000,
        });
        expect(validateLuckyGiftForm(factorTooLow)).toContain("V2 高水位加权系数必须大于 100% 且不超过 1000%");

        const recoverAboveThreshold = fixedForm({
            v2_high_water_boost_factor_percent: 150,
            v2_high_water_boost_recover_coins: 3_000_000,
            v2_high_water_boost_threshold_coins: 2_000_000,
        });
        expect(validateLuckyGiftForm(recoverAboveThreshold)).toContain("V2 加权恢复水位必须大于 0 且不高于加权阈值");
    });

    test("switching to dynamic_v3 zeroes the fixed_v2-only fields on serialization", () => {
        const boosted = fixedForm({
            v2_high_water_boost_factor_percent: 150,
            v2_high_water_boost_recover_coins: 1_000_000,
            v2_high_water_boost_threshold_coins: 2_000_000,
        });
        const dynamic = applyDynamicStrategyDefaults(boosted);
        const config = formToConfig({}, dynamic);
        // owner 拒绝 dynamic_v3 携带 v2 加权字段；序列化必须归零，不能把 fixed_v2 的残值带进新策略版本。
        expect(config.v2_high_water_boost_threshold_coins).toBe(0);
        expect(config.v2_high_water_boost_factor_percent).toBe(0);
        expect(config.v2_high_water_boost_recover_coins).toBe(0);
        expect(validateLuckyGiftForm(dynamic)).toEqual([]);
    });
});
