import { z } from "zod";

export const DEFAULT_POLICY_TEMPLATE_CODE = "first_google70000_coin_seller_92000_100000_v1";
export const DEFAULT_POLICY_TEMPLATE_VERSION = "v1";

const statusValues = ["active", "disabled"] as const;
const rewardAssetValues = ["COIN", "POINT"] as const;
const regionScopeModes = ["all_active_regions", "region_id"] as const;

type NumericInput = number | string;
type RuleObject = Record<string, unknown>;

export interface AgentLevelForm {
    extra?: RuleObject;
    level: NumericInput;
    manualReview: boolean;
    maxUsd: NumericInput;
    minUsd: NumericInput;
    name: string;
    qualifiedHostMin: NumericInput;
    ratioPercent: NumericInput;
}

export interface BDLevelForm {
    commissionPercent: NumericInput;
    extra?: RuleObject;
    level: NumericInput;
    name: string;
    salaryUsd: NumericInput;
    thresholdUsd: NumericInput;
}

export interface CoinSellerLevelForm {
    coinPerUsd: NumericInput;
    extra?: RuleObject;
    name: string;
    singleRechargeThresholdUsd: NumericInput;
    thresholdUsd: NumericInput;
    withdrawOrderEnabled: boolean;
}

export interface PolicyRuleForm {
    agent: {
        differential: {
            crossLevelFormula: string;
            enabled: boolean;
            maxDepth: NumericInput;
        };
        levels: AgentLevelForm[];
        period: string;
        qualifiedHostMin: NumericInput;
        supportDays: NumericInput;
        supportRatioPercent: NumericInput;
    };
    allowSelfBrushing: boolean;
    agency: {
        pointRatioPercent: NumericInput;
    };
    bd: {
        base: string;
        levels: BDLevelForm[];
        period: string;
        qualifiedAgencyMin: NumericInput;
        qualifiedHostPerAgencyMin: NumericInput;
    };
    coinSeller: {
        levels: CoinSellerLevelForm[];
        orderTimeoutSeconds: NumericInput;
        sellerPointRewardPercent: NumericInput;
        sellerPointSettlePercent: NumericInput;
        successRateCloseThresholdPercent: NumericInput;
        withdrawOrderEnabled: boolean;
    };
    gameInvite: {
        enabled: boolean;
        period: string;
    };
    googleCoinPerUsd: NumericInput;
    host: {
        affectsRoomHeat: boolean;
        minimumWithdrawPoints: NumericInput;
        pointRatioPercent: NumericInput;
        withdrawFeeBps: NumericInput;
    };
    manager: {
        coinSellerRechargeCommissionPercent: NumericInput;
        coinSellerRechargeThresholdUsd: NumericInput;
        hostPointCommissionPercent: NumericInput;
    };
    pointsPerUsd: NumericInput;
    rawJson: string;
    tasks: {
        newHost7dMaxPoints: NumericInput;
        rewardAssetType: (typeof rewardAssetValues)[number];
    };
}

export interface PolicyTemplateForm {
    description: string;
    name: string;
    rule: PolicyRuleForm;
    status: (typeof statusValues)[number];
    templateCode: string;
    templateVersion: string;
}

export interface PolicyInstanceForm {
    effectiveFrom: NumericInput | "";
    effectiveTo: NumericInput | "";
    instanceCode: string;
    regionId: NumericInput | "";
    regionScopeMode: (typeof regionScopeModes)[number];
    status: (typeof statusValues)[number];
    templateCode: string;
    templateVersion: string;
}

const agentLevelSchema = z.object({
    extra: z.record(z.string(), z.unknown()).optional(),
    level: z.union([z.string(), z.number()]),
    manualReview: z.boolean(),
    maxUsd: z.union([z.string(), z.number()]),
    minUsd: z.union([z.string(), z.number()]),
    name: z.string(),
    qualifiedHostMin: z.union([z.string(), z.number()]),
    ratioPercent: z.union([z.string(), z.number()]),
});

const bdLevelSchema = z.object({
    commissionPercent: z.union([z.string(), z.number()]),
    extra: z.record(z.string(), z.unknown()).optional(),
    level: z.union([z.string(), z.number()]),
    name: z.string(),
    salaryUsd: z.union([z.string(), z.number()]),
    thresholdUsd: z.union([z.string(), z.number()]),
});

const coinSellerLevelSchema = z.object({
    coinPerUsd: z.union([z.string(), z.number()]),
    extra: z.record(z.string(), z.unknown()).optional(),
    name: z.string(),
    singleRechargeThresholdUsd: z.union([z.string(), z.number()]),
    thresholdUsd: z.union([z.string(), z.number()]),
    withdrawOrderEnabled: z.boolean(),
});

export const policyRuleFormSchema = z
    .object({
        agent: z.object({
            differential: z.object({
                crossLevelFormula: z.string().trim().min(1, "请输入 Agent 差值公式"),
                enabled: z.boolean(),
                maxDepth: z.union([z.string(), z.number()]),
            }),
            levels: z.array(agentLevelSchema).min(1, "请至少配置一个 Agent 等级"),
            period: z.string().trim().min(1, "请输入 Agent 周期"),
            qualifiedHostMin: z.union([z.string(), z.number()]),
            supportDays: z.union([z.string(), z.number()]),
            supportRatioPercent: z.union([z.string(), z.number()]),
        }),
        allowSelfBrushing: z.boolean(),
        bd: z.object({
            base: z.string().trim().min(1, "请输入 BD 结算基准"),
            levels: z.array(bdLevelSchema).min(1, "请至少配置一个 BD 等级"),
            period: z.string().trim().min(1, "请输入 BD 周期"),
            qualifiedAgencyMin: z.union([z.string(), z.number()]),
            qualifiedHostPerAgencyMin: z.union([z.string(), z.number()]),
        }),
        coinSeller: z.object({
            levels: z.array(coinSellerLevelSchema).min(1, "请至少配置一个币商等级"),
            orderTimeoutSeconds: z.union([z.string(), z.number()]),
            sellerPointRewardPercent: z.union([z.string(), z.number()]),
            sellerPointSettlePercent: z.union([z.string(), z.number()]),
            successRateCloseThresholdPercent: z.union([z.string(), z.number()]),
            withdrawOrderEnabled: z.boolean(),
        }),
        gameInvite: z.object({
            enabled: z.boolean(),
            period: z.string().trim().min(1, "请输入游戏邀请周期"),
        }),
        googleCoinPerUsd: z.union([z.string(), z.number()]),
        host: z.object({
            affectsRoomHeat: z.boolean(),
            minimumWithdrawPoints: z.union([z.string(), z.number()]),
            pointRatioPercent: z.union([z.string(), z.number()]),
            withdrawFeeBps: z.union([z.string(), z.number()]),
        }),
        agency: z.object({
            pointRatioPercent: z.union([z.string(), z.number()]),
        }),
        manager: z.object({
            coinSellerRechargeCommissionPercent: z.union([z.string(), z.number()]),
            coinSellerRechargeThresholdUsd: z.union([z.string(), z.number()]),
            hostPointCommissionPercent: z.union([z.string(), z.number()]),
        }),
        pointsPerUsd: z.union([z.string(), z.number()]),
        rawJson: z.string(),
        tasks: z.object({
            newHost7dMaxPoints: z.union([z.string(), z.number()]),
            rewardAssetType: z.enum(rewardAssetValues),
        }),
    })
    .superRefine((value, context) => {
        try {
            parseRuleObject(value.rawJson);
        } catch (error) {
            context.addIssue({ code: "custom", message: errorMessage(error), path: ["rawJson"] });
        }
        requirePositiveInteger(context, ["pointsPerUsd"], value.pointsPerUsd, "POINT/USD 必须大于 0");
        requirePositiveInteger(context, ["googleCoinPerUsd"], value.googleCoinPerUsd, "Google 金币单价必须大于 0");
        requirePercent(
            context,
            ["host", "pointRatioPercent"],
            value.host.pointRatioPercent,
            "主播收益比例必须在 0-100 之间",
            false,
        );
        requirePercent(
            context,
            ["agency", "pointRatioPercent"],
            value.agency.pointRatioPercent,
            "Agency 分成比例必须在 0-100 之间",
            true,
        );
        if (numberValue(value.host.pointRatioPercent) + numberValue(value.agency.pointRatioPercent) > 100) {
            context.addIssue({
                code: "custom",
                message: "主播收益比例和 Agency 分成比例合计不能超过 100",
                path: ["agency", "pointRatioPercent"],
            });
        }
        requireNonNegativeInteger(
            context,
            ["host", "minimumWithdrawPoints"],
            value.host.minimumWithdrawPoints,
            "最低提现积分不能小于 0",
        );
        requireIntegerRange(
            context,
            ["host", "withdrawFeeBps"],
            value.host.withdrawFeeBps,
            0,
            10000,
            "提现手续费 bps 必须在 0-10000 之间",
        );
        requirePercent(
            context,
            ["agent", "supportRatioPercent"],
            value.agent.supportRatioPercent,
            "Agent 扶持比例必须在 0-100 之间",
            false,
        );
        requireNonNegativeInteger(
            context,
            ["agent", "supportDays"],
            value.agent.supportDays,
            "Agent 扶持天数不能小于 0",
        );
        requirePositiveInteger(
            context,
            ["agent", "qualifiedHostMin"],
            value.agent.qualifiedHostMin,
            "Agent 合格主播数必须大于 0",
        );
        requireNonNegativeInteger(
            context,
            ["agent", "differential", "maxDepth"],
            value.agent.differential.maxDepth,
            "Agent 差值深度不能小于 0",
        );
        validateAgentLevels(context, value.agent.levels);
        requirePositiveInteger(
            context,
            ["bd", "qualifiedAgencyMin"],
            value.bd.qualifiedAgencyMin,
            "BD 合格 Agency 数必须大于 0",
        );
        requirePositiveInteger(
            context,
            ["bd", "qualifiedHostPerAgencyMin"],
            value.bd.qualifiedHostPerAgencyMin,
            "BD 每 Agency 合格主播数必须大于 0",
        );
        validateBDLevels(context, value.bd.levels);
        requirePositiveNumber(
            context,
            ["manager", "coinSellerRechargeThresholdUsd"],
            value.manager.coinSellerRechargeThresholdUsd,
            "经理币商充值门槛必须大于 0",
        );
        requirePercent(
            context,
            ["manager", "coinSellerRechargeCommissionPercent"],
            value.manager.coinSellerRechargeCommissionPercent,
            "经理币商提成比例必须在 0-100 之间",
            true,
        );
        requirePercent(
            context,
            ["manager", "hostPointCommissionPercent"],
            value.manager.hostPointCommissionPercent,
            "经理主播提成比例必须在 0-100 之间",
            true,
        );
        requirePositiveInteger(
            context,
            ["coinSeller", "orderTimeoutSeconds"],
            value.coinSeller.orderTimeoutSeconds,
            "币商订单超时秒数必须大于 0",
        );
        requirePercent(
            context,
            ["coinSeller", "sellerPointSettlePercent"],
            value.coinSeller.sellerPointSettlePercent,
            "币商结算比例必须在 0-100 之间",
            true,
        );
        requirePercent(
            context,
            ["coinSeller", "sellerPointRewardPercent"],
            value.coinSeller.sellerPointRewardPercent,
            "币商奖励比例必须在 0-100 之间",
            true,
        );
        if (
            numberValue(value.coinSeller.sellerPointSettlePercent) +
                numberValue(value.coinSeller.sellerPointRewardPercent) >
            100
        ) {
            context.addIssue({
                code: "custom",
                message: "币商结算比例和奖励比例合计不能超过 100",
                path: ["coinSeller", "sellerPointRewardPercent"],
            });
        }
        requirePercent(
            context,
            ["coinSeller", "successRateCloseThresholdPercent"],
            value.coinSeller.successRateCloseThresholdPercent,
            "成功率关闭门槛必须在 0-100 之间",
            true,
        );
        validateCoinSellerLevels(context, value.coinSeller.levels);
        requireNonNegativeInteger(
            context,
            ["tasks", "newHost7dMaxPoints"],
            value.tasks.newHost7dMaxPoints,
            "新主播 7 日任务积分不能小于 0",
        );
    });

export const policyTemplateFormSchema = z.object({
    description: z.string().trim().max(512, "模板说明不能超过 512 个字符"),
    name: z.string().trim().min(1, "请输入模板名称").max(160, "模板名称不能超过 160 个字符"),
    rule: policyRuleFormSchema,
    status: z.enum(statusValues),
    templateCode: z.string().trim().min(1, "请输入模板编码").max(96, "模板编码不能超过 96 个字符"),
    templateVersion: z.string().trim().min(1, "请输入模板版本").max(64, "模板版本不能超过 64 个字符"),
});

export const policyInstanceFormSchema = z
    .object({
        effectiveFrom: z.union([z.string(), z.number()]).optional(),
        effectiveTo: z.union([z.string(), z.number()]).optional(),
        instanceCode: z.string().trim().min(1, "请输入实例编码").max(96, "实例编码不能超过 96 个字符"),
        regionId: z.union([z.string(), z.number()]).optional(),
        regionScopeMode: z.enum(regionScopeModes),
        status: z.enum(statusValues),
        templateCode: z.string().trim().min(1, "请选择模板编码").max(96, "模板编码不能超过 96 个字符"),
        templateVersion: z.string().trim().min(1, "请选择模板版本").max(64, "模板版本不能超过 64 个字符"),
    })
    .superRefine((value, context) => {
        if (value.regionScopeMode === "region_id") {
            requirePositiveInteger(context, ["regionId"], value.regionId || "", "请选择适用区域");
        }
        const fromMs = timeValueToMs(value.effectiveFrom);
        const toMs = timeValueToMs(value.effectiveTo);
        if (fromMs < 0 || toMs < 0 || (toMs > 0 && fromMs > 0 && toMs <= fromMs)) {
            context.addIssue({ code: "custom", message: "生效时间范围不正确", path: ["effectiveTo"] });
        }
    });

export function policyTemplateFormFromItem(
    item?:
        | (Partial<PolicyTemplateForm> & {
              ruleJson?: unknown;
              rule_json?: unknown;
              template_code?: string;
              template_version?: string;
          })
        | null,
): PolicyTemplateForm {
    const rule = ruleFormFromJson(item?.ruleJson ?? item?.rule_json ?? defaultPolicyRule());
    return {
        description: stringValue(
            item?.description,
            "新文档口径：允许自刷；BD 按 UTC 当月自然月；Huwaa 使用 POINT/COIN_SELLER_POINT。",
        ),
        name: stringValue(item?.name, "第一套政策：Google 70000 + 币商 92000-100000"),
        rule,
        status: normalizeStatus(item?.status),
        templateCode: stringValue(item?.templateCode ?? item?.template_code, DEFAULT_POLICY_TEMPLATE_CODE),
        templateVersion: stringValue(item?.templateVersion ?? item?.template_version, DEFAULT_POLICY_TEMPLATE_VERSION),
    };
}

export function policyTemplatePayloadFromForm(form: PolicyTemplateForm) {
    const parsed = policyTemplateFormSchema.parse(form);
    return {
        description: parsed.description.trim(),
        name: parsed.name.trim(),
        rule_json: ruleObjectFromForm(parsed.rule),
        status: parsed.status,
        template_code: parsed.templateCode.trim(),
        template_version: parsed.templateVersion.trim(),
    };
}

export function policyInstanceFormFromTemplate(
    template?: {
        templateCode?: string;
        templateVersion?: string;
        template_code?: string;
        template_version?: string;
    } | null,
): PolicyInstanceForm {
    const templateCode = stringValue(template?.templateCode ?? template?.template_code, DEFAULT_POLICY_TEMPLATE_CODE);
    const templateVersion = stringValue(
        template?.templateVersion ?? template?.template_version,
        DEFAULT_POLICY_TEMPLATE_VERSION,
    );
    return {
        effectiveFrom: "",
        effectiveTo: "",
        instanceCode:
            templateCode === DEFAULT_POLICY_TEMPLATE_CODE ? "huwaa_first_google70000_coin_seller_92000_100000" : "",
        regionId: "",
        regionScopeMode: "all_active_regions",
        status: "active",
        templateCode,
        templateVersion,
    };
}

export function policyInstancePayloadFromForm(form: PolicyInstanceForm) {
    const parsed = policyInstanceFormSchema.parse(form);
    return {
        effective_from_ms: timeValueToMs(parsed.effectiveFrom),
        effective_to_ms: timeValueToMs(parsed.effectiveTo),
        instance_code: parsed.instanceCode.trim(),
        region_scope:
            parsed.regionScopeMode === "region_id"
                ? `region_id:${integerValue(parsed.regionId || 0)}`
                : "all_active_regions",
        status: parsed.status,
        template_code: parsed.templateCode.trim(),
        template_version: parsed.templateVersion.trim(),
    };
}

export function ruleFormFromJson(raw: unknown): PolicyRuleForm {
    const rule = parseRuleObject(raw);
    const host = objectValue(rule.host);
    const agent = objectValue(rule.agent);
    const agency = objectValue(rule.agency);
    const differential = objectValue(agent.differential);
    const bd = objectValue(rule.bd);
    const manager = objectValue(rule.manager);
    const coinSeller = objectValue(rule.coin_seller);
    const tasks = objectValue(rule.tasks);
    const gameInvite = objectValue(rule.game_invite);
    return {
        agency: {
            pointRatioPercent: inputValue(agency.point_ratio_percent, 0),
        },
        agent: {
            differential: {
                crossLevelFormula: stringValue(differential.cross_level_formula, "parent_minus_direct_child"),
                enabled: booleanValue(differential.enabled, true),
                maxDepth: inputValue(differential.max_depth, 2),
            },
            levels: arrayValue(agent.levels).map(agentLevelFromRule),
            period: stringValue(agent.period, "rolling_30d"),
            qualifiedHostMin: inputValue(agent.qualified_host_min, 3),
            supportDays: inputValue(agent.support_days, 30),
            supportRatioPercent: inputValue(agent.support_ratio_percent, 8),
        },
        allowSelfBrushing: booleanValue(rule.allow_self_brushing, true),
        bd: {
            base: stringValue(bd.base, "host_effective_income"),
            levels: arrayValue(bd.levels).map(bdLevelFromRule),
            period: stringValue(bd.period, "utc_calendar_month"),
            qualifiedAgencyMin: inputValue(bd.qualified_agency_min, 3),
            qualifiedHostPerAgencyMin: inputValue(bd.qualified_host_per_agency_min, 3),
        },
        coinSeller: {
            levels: arrayValue(coinSeller.levels).map(coinSellerLevelFromRule),
            orderTimeoutSeconds: inputValue(coinSeller.order_timeout_seconds, 7200),
            sellerPointRewardPercent: inputValue(coinSeller.seller_point_reward_percent, 5),
            sellerPointSettlePercent: inputValue(coinSeller.seller_point_settle_percent, 95),
            successRateCloseThresholdPercent: inputValue(coinSeller.success_rate_close_threshold_percent, 95),
            withdrawOrderEnabled: booleanValue(coinSeller.withdraw_order_enabled, true),
        },
        gameInvite: {
            enabled: booleanValue(gameInvite.enabled, true),
            period: stringValue(gameInvite.period, "rolling_30d"),
        },
        googleCoinPerUsd: inputValue(rule.google_coin_per_usd, 70000),
        host: {
            affectsRoomHeat: booleanValue(host.affects_room_heat, false),
            minimumWithdrawPoints: inputValue(host.minimum_withdraw_points, 1000000),
            pointRatioPercent: inputValue(host.point_ratio_percent, 70),
            withdrawFeeBps: inputValue(host.withdraw_fee_bps, 500),
        },
        manager: {
            coinSellerRechargeCommissionPercent: inputValue(manager.coin_seller_recharge_commission_percent, 3),
            coinSellerRechargeThresholdUsd: inputValue(manager.coin_seller_recharge_threshold_usd, 2000),
            hostPointCommissionPercent: inputValue(manager.host_point_commission_percent, 2),
        },
        pointsPerUsd: inputValue(rule.points_per_usd, 100000),
        rawJson: JSON.stringify(rule, null, 2),
        tasks: {
            newHost7dMaxPoints: inputValue(tasks.new_host_7d_max_points, 350000),
            rewardAssetType: normalizeRewardAsset(tasks.reward_asset_type),
        },
    };
}

export function ruleJsonFromForm(form: PolicyRuleForm): string {
    return JSON.stringify(ruleObjectFromForm(form));
}

export function ruleObjectFromForm(form: PolicyRuleForm): RuleObject {
    const parsed = policyRuleFormSchema.parse(form);
    const base = parseRuleObject(parsed.rawJson);
    const baseAgent = objectValue(base.agent);
    const baseBD = objectValue(base.bd);
    const baseCoinSeller = objectValue(base.coin_seller);
    const baseAgentLevelExtras = extraByKey(arrayValue(baseAgent.levels), "level", [
        "level",
        "manual_review",
        "max_usd",
        "min_usd",
        "name",
        "qualified_host_min",
        "ratio_percent",
    ]);
    const baseBDLevelExtras = extraByKey(arrayValue(baseBD.levels), "level", [
        "commission_percent",
        "level",
        "name",
        "salary_usd",
        "threshold_usd",
    ]);
    const baseCoinSellerLevelExtras = extraByKey(arrayValue(baseCoinSeller.levels), "name", [
        "coin_per_usd",
        "name",
        "single_recharge_threshold_usd",
        "threshold_usd",
        "withdraw_order_enabled",
    ]);

    return {
        ...base,
        agency: {
            ...objectValue(base.agency),
            point_ratio_percent: numberValue(parsed.agency.pointRatioPercent),
        },
        allow_self_brushing: parsed.allowSelfBrushing,
        google_coin_per_usd: integerValue(parsed.googleCoinPerUsd),
        points_per_usd: integerValue(parsed.pointsPerUsd),
        host: {
            ...objectValue(base.host),
            affects_room_heat: parsed.host.affectsRoomHeat,
            minimum_withdraw_points: integerValue(parsed.host.minimumWithdrawPoints),
            point_ratio_percent: numberValue(parsed.host.pointRatioPercent),
            withdraw_fee_bps: integerValue(parsed.host.withdrawFeeBps),
        },
        agent: {
            ...baseAgent,
            differential: {
                ...objectValue(baseAgent.differential),
                cross_level_formula: parsed.agent.differential.crossLevelFormula.trim(),
                enabled: parsed.agent.differential.enabled,
                max_depth: integerValue(parsed.agent.differential.maxDepth),
            },
            levels: parsed.agent.levels.map((level) =>
                agentLevelToRule(level, baseAgentLevelExtras.get(String(integerValue(level.level))) || {}),
            ),
            period: parsed.agent.period.trim(),
            qualified_host_min: integerValue(parsed.agent.qualifiedHostMin),
            support_days: integerValue(parsed.agent.supportDays),
            support_ratio_percent: numberValue(parsed.agent.supportRatioPercent),
        },
        bd: {
            ...baseBD,
            base: parsed.bd.base.trim(),
            levels: parsed.bd.levels.map((level) =>
                bdLevelToRule(level, baseBDLevelExtras.get(String(integerValue(level.level))) || {}),
            ),
            period: parsed.bd.period.trim(),
            qualified_agency_min: integerValue(parsed.bd.qualifiedAgencyMin),
            qualified_host_per_agency_min: integerValue(parsed.bd.qualifiedHostPerAgencyMin),
        },
        manager: {
            ...objectValue(base.manager),
            coin_seller_recharge_commission_percent: numberValue(parsed.manager.coinSellerRechargeCommissionPercent),
            coin_seller_recharge_threshold_usd: numberValue(parsed.manager.coinSellerRechargeThresholdUsd),
            host_point_commission_percent: numberValue(parsed.manager.hostPointCommissionPercent),
        },
        coin_seller: {
            ...baseCoinSeller,
            levels: parsed.coinSeller.levels.map((level) =>
                coinSellerLevelToRule(level, baseCoinSellerLevelExtras.get(String(level.name || "").trim()) || {}),
            ),
            order_timeout_seconds: integerValue(parsed.coinSeller.orderTimeoutSeconds),
            seller_point_reward_percent: numberValue(parsed.coinSeller.sellerPointRewardPercent),
            seller_point_settle_percent: numberValue(parsed.coinSeller.sellerPointSettlePercent),
            success_rate_close_threshold_percent: numberValue(parsed.coinSeller.successRateCloseThresholdPercent),
            withdraw_order_enabled: parsed.coinSeller.withdrawOrderEnabled,
        },
        tasks: {
            ...objectValue(base.tasks),
            new_host_7d_max_points: integerValue(parsed.tasks.newHost7dMaxPoints),
            reward_asset_type: parsed.tasks.rewardAssetType,
        },
        game_invite: {
            ...objectValue(base.game_invite),
            enabled: parsed.gameInvite.enabled,
            period: parsed.gameInvite.period.trim(),
        },
    };
}

export function defaultPolicyRule(): RuleObject {
    return {
        agency: { point_ratio_percent: 0 },
        points_per_usd: 100000,
        allow_self_brushing: true,
        google_coin_per_usd: 70000,
        host: {
            point_ratio_percent: 70,
            affects_room_heat: false,
            minimum_withdraw_points: 1000000,
            withdraw_fee_bps: 500,
        },
        agent: {
            period: "rolling_30d",
            support_ratio_percent: 8,
            support_days: 30,
            qualified_host_min: 3,
            levels: [
                { level: 1, name: "Lv1", min_usd: 0, max_usd: 200, ratio_percent: 4, qualified_host_min: 3 },
                { level: 2, name: "Lv2", min_usd: 200, max_usd: 500, ratio_percent: 8, qualified_host_min: 5 },
                { level: 3, name: "Lv3", min_usd: 500, max_usd: 2000, ratio_percent: 16, qualified_host_min: 10 },
                { level: 4, name: "Lv4", min_usd: 2000, max_usd: 0, ratio_percent: 20, manual_review: true },
            ],
            differential: { enabled: true, max_depth: 2, cross_level_formula: "parent_minus_direct_child" },
        },
        bd: {
            period: "utc_calendar_month",
            qualified_agency_min: 3,
            qualified_host_per_agency_min: 3,
            base: "host_effective_income",
            levels: [
                { level: 1, name: "测试BD-1", threshold_usd: 100, salary_usd: 5, commission_percent: 1 },
                { level: 2, name: "测试BD-2", threshold_usd: 300, salary_usd: 15, commission_percent: 1 },
                { level: 3, name: "正式BD-1", threshold_usd: 700, salary_usd: 35, commission_percent: 1.5 },
                { level: 4, name: "正式BD-2", threshold_usd: 1000, salary_usd: 50, commission_percent: 1.5 },
                { level: 5, name: "高级BD-1", threshold_usd: 2000, salary_usd: 100, commission_percent: 2 },
                { level: 6, name: "高级BD-2", threshold_usd: 3000, salary_usd: 150, commission_percent: 2 },
                { level: 7, name: "普通BD-1", threshold_usd: 5000, salary_usd: 250, commission_percent: 2.5 },
                { level: 8, name: "普通BD-2", threshold_usd: 7000, salary_usd: 350, commission_percent: 2.5 },
                { level: 9, name: "普通Admin-1", threshold_usd: 10000, salary_usd: 500, commission_percent: 3 },
                { level: 10, name: "普通Admin-2", threshold_usd: 20000, salary_usd: 1000, commission_percent: 3 },
                { level: 11, name: "超级Admin-1", threshold_usd: 30000, salary_usd: 1500, commission_percent: 3 },
                { level: 12, name: "超级Admin-2", threshold_usd: 40000, salary_usd: 2000, commission_percent: 3 },
            ],
        },
        manager: {
            coin_seller_recharge_threshold_usd: 2000,
            coin_seller_recharge_commission_percent: 3,
            host_point_commission_percent: 2,
        },
        coin_seller: {
            withdraw_order_enabled: true,
            levels: [
                { name: "Beginner", threshold_usd: 100, coin_per_usd: 92000, withdraw_order_enabled: false },
                { name: "Standard", threshold_usd: 500, coin_per_usd: 96000, withdraw_order_enabled: false },
                {
                    name: "Senior",
                    single_recharge_threshold_usd: 1000,
                    coin_per_usd: 100000,
                    withdraw_order_enabled: true,
                },
            ],
            order_timeout_seconds: 7200,
            seller_point_settle_percent: 95,
            seller_point_reward_percent: 5,
            success_rate_close_threshold_percent: 95,
        },
        tasks: { reward_asset_type: "POINT", new_host_7d_max_points: 350000 },
        game_invite: { enabled: true, period: "rolling_30d" },
    };
}

function agentLevelFromRule(raw: unknown): AgentLevelForm {
    const level = objectValue(raw);
    return {
        extra: omitKeys(level, [
            "level",
            "manual_review",
            "max_usd",
            "min_usd",
            "name",
            "qualified_host_min",
            "ratio_percent",
        ]),
        level: inputValue(level.level, ""),
        manualReview: booleanValue(level.manual_review, false),
        maxUsd: inputValue(level.max_usd, 0),
        minUsd: inputValue(level.min_usd, 0),
        name: stringValue(level.name, ""),
        qualifiedHostMin: inputValue(level.qualified_host_min, 0),
        ratioPercent: inputValue(level.ratio_percent, 0),
    };
}

function bdLevelFromRule(raw: unknown): BDLevelForm {
    const level = objectValue(raw);
    return {
        commissionPercent: inputValue(level.commission_percent, 0),
        extra: omitKeys(level, ["commission_percent", "level", "name", "salary_usd", "threshold_usd"]),
        level: inputValue(level.level, ""),
        name: stringValue(level.name, ""),
        salaryUsd: inputValue(level.salary_usd, 0),
        thresholdUsd: inputValue(level.threshold_usd, 0),
    };
}

function coinSellerLevelFromRule(raw: unknown): CoinSellerLevelForm {
    const level = objectValue(raw);
    return {
        coinPerUsd: inputValue(level.coin_per_usd, 0),
        extra: omitKeys(level, [
            "coin_per_usd",
            "name",
            "single_recharge_threshold_usd",
            "threshold_usd",
            "withdraw_order_enabled",
        ]),
        name: stringValue(level.name, ""),
        singleRechargeThresholdUsd: inputValue(level.single_recharge_threshold_usd, ""),
        thresholdUsd: inputValue(level.threshold_usd, ""),
        withdrawOrderEnabled: booleanValue(level.withdraw_order_enabled, false),
    };
}

function agentLevelToRule(level: AgentLevelForm, rawExtra: RuleObject): RuleObject {
    return {
        ...level.extra,
        ...rawExtra,
        level: integerValue(level.level),
        name: String(level.name || "").trim(),
        min_usd: numberValue(level.minUsd),
        max_usd: numberValue(level.maxUsd),
        ratio_percent: numberValue(level.ratioPercent),
        qualified_host_min: integerValue(level.qualifiedHostMin),
        ...(level.manualReview ? { manual_review: true } : {}),
    };
}

function bdLevelToRule(level: BDLevelForm, rawExtra: RuleObject): RuleObject {
    return {
        ...level.extra,
        ...rawExtra,
        level: integerValue(level.level),
        name: String(level.name || "").trim(),
        threshold_usd: numberValue(level.thresholdUsd),
        salary_usd: numberValue(level.salaryUsd),
        commission_percent: numberValue(level.commissionPercent),
    };
}

function coinSellerLevelToRule(level: CoinSellerLevelForm, rawExtra: RuleObject): RuleObject {
    const result: RuleObject = {
        ...level.extra,
        ...rawExtra,
        name: String(level.name || "").trim(),
        coin_per_usd: integerValue(level.coinPerUsd),
        withdraw_order_enabled: level.withdrawOrderEnabled,
    };
    if (String(level.singleRechargeThresholdUsd || "").trim()) {
        result.single_recharge_threshold_usd = numberValue(level.singleRechargeThresholdUsd);
    }
    if (String(level.thresholdUsd || "").trim()) {
        result.threshold_usd = numberValue(level.thresholdUsd);
    }
    return result;
}

function validateAgentLevels(context: z.RefinementCtx, levels: AgentLevelForm[]) {
    const seen = new Set<number>();
    levels.forEach((level, index) => {
        const levelNo = integerValue(level.level);
        if (!Number.isInteger(levelNo) || levelNo <= 0 || seen.has(levelNo)) {
            context.addIssue({
                code: "custom",
                message: "Agent 等级必须为不重复的正整数",
                path: ["agent", "levels", index, "level"],
            });
        }
        seen.add(levelNo);
        requireNonNegativeNumber(
            context,
            ["agent", "levels", index, "minUsd"],
            level.minUsd,
            "Agent 最小 USD 不能小于 0",
        );
        requireNonNegativeNumber(
            context,
            ["agent", "levels", index, "maxUsd"],
            level.maxUsd,
            "Agent 最大 USD 不能小于 0",
        );
        if (numberValue(level.maxUsd) > 0 && numberValue(level.maxUsd) <= numberValue(level.minUsd)) {
            context.addIssue({
                code: "custom",
                message: "Agent 最大 USD 必须大于最小 USD，0 表示无上限",
                path: ["agent", "levels", index, "maxUsd"],
            });
        }
        requirePercent(
            context,
            ["agent", "levels", index, "ratioPercent"],
            level.ratioPercent,
            "Agent 提成比例必须在 0-100 之间",
            false,
        );
        requireNonNegativeInteger(
            context,
            ["agent", "levels", index, "qualifiedHostMin"],
            level.qualifiedHostMin,
            "Agent 合格主播数不能小于 0",
        );
    });
}

function validateBDLevels(context: z.RefinementCtx, levels: BDLevelForm[]) {
    const seen = new Set<number>();
    let previousThreshold = 0;
    levels
        .map((level, index) => ({
            index,
            level,
            levelNo: integerValue(level.level),
            threshold: numberValue(level.thresholdUsd),
        }))
        .sort((left, right) => left.levelNo - right.levelNo)
        .forEach(({ index, level, levelNo, threshold }) => {
            if (!Number.isInteger(levelNo) || levelNo <= 0 || seen.has(levelNo)) {
                context.addIssue({
                    code: "custom",
                    message: "BD 等级必须为不重复的正整数",
                    path: ["bd", "levels", index, "level"],
                });
            }
            seen.add(levelNo);
            if (threshold <= 0 || threshold <= previousThreshold) {
                context.addIssue({
                    code: "custom",
                    message: "BD 收入门槛必须逐级递增",
                    path: ["bd", "levels", index, "thresholdUsd"],
                });
            }
            requireNonNegativeNumber(
                context,
                ["bd", "levels", index, "salaryUsd"],
                level.salaryUsd,
                "BD 固定工资不能小于 0",
            );
            requirePercent(
                context,
                ["bd", "levels", index, "commissionPercent"],
                level.commissionPercent,
                "BD 提成比例必须在 0-100 之间",
                false,
            );
            previousThreshold = threshold;
        });
}

function validateCoinSellerLevels(context: z.RefinementCtx, levels: CoinSellerLevelForm[]) {
    levels.forEach((level, index) => {
        if (!String(level.name || "").trim()) {
            context.addIssue({
                code: "custom",
                message: "请输入币商等级名称",
                path: ["coinSeller", "levels", index, "name"],
            });
        }
        requirePositiveInteger(
            context,
            ["coinSeller", "levels", index, "coinPerUsd"],
            level.coinPerUsd,
            "币商金币单价必须大于 0",
        );
        if (!String(level.thresholdUsd || "").trim() && !String(level.singleRechargeThresholdUsd || "").trim()) {
            context.addIssue({
                code: "custom",
                message: "币商等级必须配置累计门槛或单次充值门槛",
                path: ["coinSeller", "levels", index, "thresholdUsd"],
            });
        }
        if (String(level.thresholdUsd || "").trim()) {
            requirePositiveNumber(
                context,
                ["coinSeller", "levels", index, "thresholdUsd"],
                level.thresholdUsd,
                "币商累计门槛必须大于 0",
            );
        }
        if (String(level.singleRechargeThresholdUsd || "").trim()) {
            requirePositiveNumber(
                context,
                ["coinSeller", "levels", index, "singleRechargeThresholdUsd"],
                level.singleRechargeThresholdUsd,
                "币商单次充值门槛必须大于 0",
            );
        }
    });
}

function parseRuleObject(raw: unknown): RuleObject {
    if (typeof raw === "string") {
        try {
            return parseRuleObject(JSON.parse(raw || "{}"));
        } catch (error) {
            throw new Error(`JSON 格式错误：${errorMessage(error)}`);
        }
    }
    if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
        throw new Error("规则 JSON 必须是对象");
    }
    return raw as RuleObject;
}

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

function arrayValue(value: unknown): unknown[] {
    return Array.isArray(value) ? value : [];
}

function inputValue(value: unknown, fallback: NumericInput): NumericInput {
    if (typeof value === "number" && Number.isFinite(value)) {
        return String(value);
    }
    if (typeof value === "string" && value.trim()) {
        return value.trim();
    }
    return String(fallback);
}

function stringValue(value: unknown, fallback = ""): string {
    if (typeof value === "string") {
        return value;
    }
    if (value === null || value === undefined) {
        return fallback;
    }
    return String(value);
}

function booleanValue(value: unknown, fallback: boolean): boolean {
    return typeof value === "boolean" ? value : fallback;
}

function normalizeStatus(value: unknown): (typeof statusValues)[number] {
    return String(value || "active").toLowerCase() === "disabled" ? "disabled" : "active";
}

function normalizeRewardAsset(value: unknown): (typeof rewardAssetValues)[number] {
    return String(value || "COIN").toUpperCase() === "POINT" ? "POINT" : "COIN";
}

function timeValueToMs(value: unknown): number {
    if (value === "" || value === null || value === undefined) {
        return 0;
    }
    const number = Number(value);
    return Number.isFinite(number) && number >= 0 ? Math.trunc(number) : -1;
}

function numberValue(value: unknown): number {
    const raw = String(value ?? "").trim();
    if (!/^\d+(\.\d+)?$/.test(raw)) {
        return Number.NaN;
    }
    return Number(raw);
}

function integerValue(value: unknown): number {
    const number = numberValue(value);
    return Number.isFinite(number) ? Math.trunc(number) : Number.NaN;
}

function requirePositiveInteger(context: z.RefinementCtx, path: (number | string)[], value: unknown, message: string) {
    const number = integerValue(value);
    if (!Number.isInteger(number) || number <= 0) {
        context.addIssue({ code: "custom", message, path });
    }
}

function requireNonNegativeInteger(
    context: z.RefinementCtx,
    path: (number | string)[],
    value: unknown,
    message: string,
) {
    const number = integerValue(value);
    if (!Number.isInteger(number) || number < 0) {
        context.addIssue({ code: "custom", message, path });
    }
}

function requirePositiveNumber(context: z.RefinementCtx, path: (number | string)[], value: unknown, message: string) {
    const number = numberValue(value);
    if (!Number.isFinite(number) || number <= 0) {
        context.addIssue({ code: "custom", message, path });
    }
}

function requireNonNegativeNumber(
    context: z.RefinementCtx,
    path: (number | string)[],
    value: unknown,
    message: string,
) {
    const number = numberValue(value);
    if (!Number.isFinite(number) || number < 0) {
        context.addIssue({ code: "custom", message, path });
    }
}

function requirePercent(
    context: z.RefinementCtx,
    path: (number | string)[],
    value: unknown,
    message: string,
    allowZero: boolean,
) {
    const number = numberValue(value);
    if (!Number.isFinite(number) || number > 100 || (allowZero ? number < 0 : number <= 0)) {
        context.addIssue({ code: "custom", message, path });
    }
}

function requireIntegerRange(
    context: z.RefinementCtx,
    path: (number | string)[],
    value: unknown,
    min: number,
    max: number,
    message: string,
) {
    const number = integerValue(value);
    if (!Number.isInteger(number) || number < min || number > max) {
        context.addIssue({ code: "custom", message, path });
    }
}

function extraByKey(items: unknown[], key: string, knownKeys: string[]): Map<string, RuleObject> {
    const result = new Map<string, RuleObject>();
    items.forEach((item) => {
        const record = objectValue(item);
        const rawKey = record[key];
        if (rawKey !== undefined && rawKey !== null && String(rawKey).trim()) {
            result.set(String(rawKey).trim(), omitKeys(record, knownKeys));
        }
    });
    return result;
}

function omitKeys(source: RuleObject, keys: string[]): RuleObject {
    const blocked = new Set(keys);
    return Object.fromEntries(Object.entries(source).filter(([key]) => !blocked.has(key)));
}

function errorMessage(error: unknown): string {
    return error instanceof Error ? error.message : String(error || "未知错误");
}
