import { describe, expect, test } from "vitest";
import {
    defaultPolicyRule,
    policyInstancePayloadFromForm,
    policyRuleFormSchema,
    ruleFormFromJson,
    ruleJsonFromForm,
    ruleObjectFromForm,
    type PolicyInstanceForm,
    type PolicyRuleForm,
} from "./schema";

type RuleRecord = Record<string, unknown>;

function validRuleForm(): PolicyRuleForm {
    return ruleFormFromJson(defaultPolicyRule());
}

function clone<TValue>(value: TValue): TValue {
    return JSON.parse(JSON.stringify(value)) as TValue;
}

function record(value: unknown): RuleRecord {
    return value && typeof value === "object" && !Array.isArray(value) ? (value as RuleRecord) : {};
}

function recordArray(value: unknown): RuleRecord[] {
    return Array.isArray(value) ? value.map(record) : [];
}

function invalidRuleMessages(form: PolicyRuleForm): string[] {
    const result = policyRuleFormSchema.safeParse(form);
    if (result.success) {
        throw new Error("expected policy rule form to be invalid");
    }
    return result.error.issues.map((issue) => issue.message);
}

function expectInvalidRule(form: PolicyRuleForm, message: string) {
    expect(invalidRuleMessages(form)).toContain(message);
}

function baseInstanceForm(): PolicyInstanceForm {
    return {
        effectiveFrom: "",
        effectiveTo: "",
        instanceCode: " huwaa_policy ",
        regionId: "",
        regionScopeMode: "all_active_regions",
        status: "active",
        templateCode: " first_google70000_coin_seller_92000_100000_v1 ",
        templateVersion: " v1 ",
    };
}

describe("policy rule form schema", () => {
    test("hydrates and serializes the Huwaa default policy rule", () => {
        const form = validRuleForm();

        expect(form).toMatchObject({
            agency: { pointRatioPercent: "0" },
            allowSelfBrushing: true,
            googleCoinPerUsd: "70000",
            host: {
                affectsRoomHeat: false,
                minimumWithdrawPoints: "1000000",
                pointRatioPercent: "70",
                withdrawFeeBps: "500",
            },
            pointsPerUsd: "100000",
        });
        expect(form.agent.levels).toHaveLength(4);
        expect(form.agent.levels[3]).toMatchObject({ level: "4", manualReview: true, maxUsd: "0", minUsd: "2000" });
        expect(form.bd.levels).toHaveLength(12);
        expect(form.coinSeller.levels.map((level) => level.coinPerUsd)).toEqual(["92000", "96000", "100000"]);

        const rule = ruleObjectFromForm(form);
        expect(rule).toMatchObject({
            allow_self_brushing: true,
            coin_seller: {
                levels: [
                    { coin_per_usd: 92000, name: "Beginner", threshold_usd: 100, withdraw_order_enabled: false },
                    { coin_per_usd: 96000, name: "Standard", threshold_usd: 500, withdraw_order_enabled: false },
                    {
                        coin_per_usd: 100000,
                        name: "Senior",
                        single_recharge_threshold_usd: 1000,
                        withdraw_order_enabled: true,
                    },
                ],
                seller_point_reward_percent: 5,
                seller_point_settle_percent: 95,
            },
            google_coin_per_usd: 70000,
            host: { withdraw_fee_bps: 500 },
            points_per_usd: 100000,
        });
        expect(JSON.parse(ruleJsonFromForm(form))).toMatchObject({
            google_coin_per_usd: 70000,
            points_per_usd: 100000,
        });
    });

    test("preserves unknown JSON fields while visual fields override known policy keys", () => {
        const base = defaultPolicyRule();
        const baseAgent = record(base.agent);
        const baseBD = record(base.bd);
        const baseCoinSeller = record(base.coin_seller);
        const baseAgentLevels = recordArray(baseAgent.levels);
        const baseBDLevels = recordArray(baseBD.levels);
        const baseCoinSellerLevels = recordArray(baseCoinSeller.levels);
        const rawRule = {
            ...base,
            future_top_level: "preserve-top",
            agent: {
                ...baseAgent,
                future_agent: "preserve-agent",
                levels: [
                    { ...baseAgentLevels[0], future_agent_level: "preserve-agent-level" },
                    ...baseAgentLevels.slice(1),
                ],
            },
            bd: {
                ...baseBD,
                future_bd: "preserve-bd",
                levels: [{ ...baseBDLevels[0], future_bd_level: "preserve-bd-level" }, ...baseBDLevels.slice(1)],
            },
            coin_seller: {
                ...baseCoinSeller,
                future_coin_seller: "preserve-coin-seller",
                levels: [
                    { ...baseCoinSellerLevels[0], future_coin_seller_level: "preserve-coin-seller-level" },
                    ...baseCoinSellerLevels.slice(1),
                ],
            },
            game_invite: { ...record(base.game_invite), future_game_invite: "preserve-game-invite" },
            host: { ...record(base.host), future_host: "preserve-host" },
            tasks: { ...record(base.tasks), future_tasks: "preserve-tasks" },
        };
        const form = ruleFormFromJson(rawRule);

        form.allowSelfBrushing = false;
        form.googleCoinPerUsd = "71000";
        form.host.pointRatioPercent = "72.5";
        form.agency.pointRatioPercent = "20";
        form.agent.levels[0].ratioPercent = "6";
        form.bd.levels[0].commissionPercent = "1.75";
        form.coinSeller.levels[0].coinPerUsd = "93000";
        form.gameInvite.enabled = false;
        form.tasks.rewardAssetType = "COIN";

        const rule = ruleObjectFromForm(form);
        expect(rule).toMatchObject({
            allow_self_brushing: false,
            future_top_level: "preserve-top",
            google_coin_per_usd: 71000,
            agent: {
                future_agent: "preserve-agent",
            },
            bd: {
                future_bd: "preserve-bd",
            },
            coin_seller: {
                future_coin_seller: "preserve-coin-seller",
            },
            game_invite: { enabled: false, future_game_invite: "preserve-game-invite" },
            host: { future_host: "preserve-host", point_ratio_percent: 72.5 },
            agency: { point_ratio_percent: 20 },
            tasks: { future_tasks: "preserve-tasks", reward_asset_type: "COIN" },
        });
        expect(recordArray(record(rule.agent).levels)[0]).toMatchObject({
            future_agent_level: "preserve-agent-level",
            ratio_percent: 6,
        });
        expect(recordArray(record(rule.bd).levels)[0]).toMatchObject({
            commission_percent: 1.75,
            future_bd_level: "preserve-bd-level",
        });
        expect(recordArray(record(rule.coin_seller).levels)[0]).toMatchObject({
            coin_per_usd: 93000,
            future_coin_seller_level: "preserve-coin-seller-level",
        });
    });

    test("does not reattach removed level unknown fields by array index", () => {
        const base = defaultPolicyRule();
        const baseAgent = record(base.agent);
        const baseBD = record(base.bd);
        const baseCoinSeller = record(base.coin_seller);
        const baseAgentLevels = recordArray(baseAgent.levels);
        const baseBDLevels = recordArray(baseBD.levels);
        const baseCoinSellerLevels = recordArray(baseCoinSeller.levels);
        const form = ruleFormFromJson({
            ...base,
            agent: {
                ...baseAgent,
                levels: [
                    { ...baseAgentLevels[0], marker: "deleted-agent-level" },
                    { ...baseAgentLevels[1], marker: "kept-agent-level" },
                ],
            },
            bd: {
                ...baseBD,
                levels: [
                    { ...baseBDLevels[0], marker: "deleted-bd-level" },
                    { ...baseBDLevels[1], marker: "kept-bd-level" },
                ],
            },
            coin_seller: {
                ...baseCoinSeller,
                levels: [
                    { ...baseCoinSellerLevels[0], marker: "deleted-coin-seller-level" },
                    { ...baseCoinSellerLevels[1], marker: "kept-coin-seller-level" },
                ],
            },
        });

        form.agent.levels = form.agent.levels.slice(1);
        form.bd.levels = form.bd.levels.slice(1);
        form.coinSeller.levels = form.coinSeller.levels.slice(1);

        const rule = ruleObjectFromForm(form);
        expect(recordArray(record(rule.agent).levels)[0]).toMatchObject({ level: 2, marker: "kept-agent-level" });
        expect(recordArray(record(rule.bd).levels)[0]).toMatchObject({ level: 2, marker: "kept-bd-level" });
        expect(recordArray(record(rule.coin_seller).levels)[0]).toMatchObject({
            name: "Standard",
            marker: "kept-coin-seller-level",
        });
    });

    test("rejects non-object and invalid advanced JSON", () => {
        const nonObjectJson = validRuleForm();
        nonObjectJson.rawJson = "[]";
        expect(invalidRuleMessages(nonObjectJson).some((message) => message.includes("规则 JSON 必须是对象"))).toBe(
            true,
        );

        const invalidJson = validRuleForm();
        invalidJson.rawJson = "{bad json";
        expect(invalidRuleMessages(invalidJson).some((message) => message.startsWith("JSON 格式错误："))).toBe(true);
        expect(() => ruleFormFromJson("[]")).toThrow("规则 JSON 必须是对象");
    });

    test("rejects invalid Agent level identity, thresholds, and ratio range", () => {
        const duplicateLevel = validRuleForm();
        duplicateLevel.agent.levels[1].level = duplicateLevel.agent.levels[0].level;
        expectInvalidRule(duplicateLevel, "Agent 等级必须为不重复的正整数");

        const invalidWindow = validRuleForm();
        invalidWindow.agent.levels[0].minUsd = "100";
        invalidWindow.agent.levels[0].maxUsd = "100";
        expectInvalidRule(invalidWindow, "Agent 最大 USD 必须大于最小 USD，0 表示无上限");

        const invalidRatio = validRuleForm();
        invalidRatio.agent.levels[0].ratioPercent = "101";
        expectInvalidRule(invalidRatio, "Agent 提成比例必须在 0-100 之间");
    });

    test("rejects invalid BD level identity, increasing threshold, and commission range", () => {
        const duplicateLevel = validRuleForm();
        duplicateLevel.bd.levels[1].level = duplicateLevel.bd.levels[0].level;
        expectInvalidRule(duplicateLevel, "BD 等级必须为不重复的正整数");

        const nonIncreasingThreshold = validRuleForm();
        nonIncreasingThreshold.bd.levels[1].thresholdUsd = nonIncreasingThreshold.bd.levels[0].thresholdUsd;
        expectInvalidRule(nonIncreasingThreshold, "BD 收入门槛必须逐级递增");

        const invalidCommission = validRuleForm();
        invalidCommission.bd.levels[0].commissionPercent = "0";
        expectInvalidRule(invalidCommission, "BD 提成比例必须在 0-100 之间");
    });

    test("rejects invalid coin seller level thresholds, percentages, and host fee bps", () => {
        const missingThreshold = validRuleForm();
        missingThreshold.coinSeller.levels[0].thresholdUsd = "";
        missingThreshold.coinSeller.levels[0].singleRechargeThresholdUsd = "";
        expectInvalidRule(missingThreshold, "币商等级必须配置累计门槛或单次充值门槛");

        const invalidCoinRate = validRuleForm();
        invalidCoinRate.coinSeller.levels[0].coinPerUsd = "0";
        expectInvalidRule(invalidCoinRate, "币商金币单价必须大于 0");

        const invalidPercent = validRuleForm();
        invalidPercent.coinSeller.sellerPointSettlePercent = "101";
        expectInvalidRule(invalidPercent, "币商结算比例必须在 0-100 之间");

        const invalidPercentTotal = validRuleForm();
        invalidPercentTotal.coinSeller.sellerPointSettlePercent = "96";
        invalidPercentTotal.coinSeller.sellerPointRewardPercent = "5";
        expectInvalidRule(invalidPercentTotal, "币商结算比例和奖励比例合计不能超过 100");

        const invalidFeeBps = validRuleForm();
        invalidFeeBps.host.withdrawFeeBps = "10001";
        expectInvalidRule(invalidFeeBps, "提现手续费 bps 必须在 0-10000 之间");

        const invalidCombinedRatio = validRuleForm();
        invalidCombinedRatio.host.pointRatioPercent = "81";
        invalidCombinedRatio.agency.pointRatioPercent = "20";
        expectInvalidRule(invalidCombinedRatio, "主播收益比例和 Agency 分成比例合计不能超过 100");
    });
});

describe("policy instance payload", () => {
    test("builds all_active_regions payload and trims template fields", () => {
        expect(policyInstancePayloadFromForm(baseInstanceForm())).toEqual({
            effective_from_ms: 0,
            effective_to_ms: 0,
            instance_code: "huwaa_policy",
            region_scope: "all_active_regions",
            status: "active",
            template_code: "first_google70000_coin_seller_92000_100000_v1",
            template_version: "v1",
        });
    });

    test("builds region_id scope and preserves UTC epoch millisecond boundaries", () => {
        const startMs = Date.UTC(2026, 0, 1, 0, 0, 0, 0);
        const endMs = startMs + 1;

        expect(
            policyInstancePayloadFromForm({
                ...baseInstanceForm(),
                effectiveFrom: startMs,
                effectiveTo: endMs,
                regionId: "1001",
                regionScopeMode: "region_id",
            }),
        ).toMatchObject({
            effective_from_ms: 1767225600000,
            effective_to_ms: 1767225600001,
            region_scope: "region_id:1001",
        });
    });

    test("rejects missing region id and invalid effective time boundaries", () => {
        const startMs = Date.UTC(2026, 0, 1, 0, 0, 0, 0);

        expect(() => policyInstancePayloadFromForm({ ...baseInstanceForm(), regionScopeMode: "region_id" })).toThrow(
            "请选择适用区域",
        );
        expect(() =>
            policyInstancePayloadFromForm({ ...baseInstanceForm(), effectiveFrom: startMs, effectiveTo: startMs }),
        ).toThrow("生效时间范围不正确");
        expect(() =>
            policyInstancePayloadFromForm({ ...baseInstanceForm(), effectiveFrom: startMs, effectiveTo: startMs - 1 }),
        ).toThrow("生效时间范围不正确");
        expect(() =>
            policyInstancePayloadFromForm({ ...baseInstanceForm(), effectiveFrom: "-1", effectiveTo: "" }),
        ).toThrow("生效时间范围不正确");
    });

    test("truncates decimal millisecond inputs after schema validation accepts the range", () => {
        const form = clone(baseInstanceForm());
        form.effectiveFrom = "1767225600000.9";
        form.effectiveTo = "1767225600002.1";

        expect(policyInstancePayloadFromForm(form)).toMatchObject({
            effective_from_ms: 1767225600000,
            effective_to_ms: 1767225600002,
        });
    });
});
