import {Context} from "koa";
import * as crypto from 'crypto';

import {ChannelHandler, LoginResult, PaymentResult} from "../interfaces/ChannelHandler";
import {ChannelConfig} from "../../config/channelConfig";
import {PaymentHelper} from "../../utils/PaymentHelper";
import {setWebgameAuthInfo} from "../../utils/webgameAuthCache";
import {formatDate, getClientIp} from "../../utils/common";
import {WEBGAME_APP_ID, WEBGAME_REPORT_URL} from "../../config/thirdParams";
import {getRoleInfoByUidAndServerId} from "../../mongo/mongodb";
import {query} from "../../sql/query";

const logger = require("../../utils/log");
const User = require("../../model/UserModel");
const axios = require("axios");

const DEFAULT_GATE_DOMAIN = "https://mind.yishanyou.com/webgame/index.html";

export class WebGameChannelHandler implements ChannelHandler {

    private toIntOrZero(v: any): number {
        if (v === undefined || v === null || v === '') return 0;
        const n = Number(v);
        return Number.isFinite(n) ? Math.trunc(n) : 0;
    }

    // 上报登录/支付事件（fire-and-forget，不阻塞主流程）
    // 上报地址由 WEBGAME_REPORT_URL 配置，格式：POST /api/v2/webgame/log (application/x-www-form-urlencoded)
    private reportEvent(logType: 'login' | 'payment', params: Record<string, any>, config: ChannelConfig): void {
        const reportUrl = WEBGAME_REPORT_URL;
        if (!reportUrl) return;

        const loginKey = config.loginConfig?.signKey ?? '';
        const timestamp = String(Math.floor(Date.now() / 1000));
        const userId = String(params.user_id ?? '');
        const gameId = String(WEBGAME_APP_ID);
        const sign = crypto.createHash('md5').update(userId + gameId + timestamp + loginKey).digest('hex').toLowerCase();

        const payload: Record<string, string> = {
            user_id: userId,
            game_id: gameId,
            sign,
            timestamp,
            server_id: String(params.server_id ?? ''),
            log_type: logType,
        };
        if (params.log_data) payload.log_data = params.log_data;
        if (params.ip_addr)  payload.ip_addr  = String(params.ip_addr);

        axios.post(reportUrl + '/api/v2/webgame/log', new URLSearchParams(payload).toString(), {
            headers: {'Content-Type': 'application/x-www-form-urlencoded'},
            timeout: 5000,
        }).then((res: any) => {
            logger.info('WebGame 事件上报成功', {logType, result: res.data});
        }).catch((err: any) => {
            logger.warn('WebGame 事件上报失败', {logType, error: err?.message});
        });
    }

    async handleLogin(ctx: Context,config:ChannelConfig): Promise<LoginResult> {
        const data = ctx.request.body as Record<string, unknown>;
        const uid = data.uid;
        // token 仅透传，不做校验
        if (uid === undefined || uid === null) {
            return {code: 0, msg: "缺少必要参数: uid"};
        }

        const uidStr = String(uid);
        let uidDecoded: string;
        try {
            uidDecoded = decodeURIComponent(uidStr.replace(/\+/g, "%20"));
        } catch {
            uidDecoded = uidStr;
        }

        const platformStr = data.platform == null ? "" : String(data.platform);

        // 登录成功后写入 accounts / account_login_logs（参考 checkUserToken）
        try {
            const ip = getClientIp(ctx);
            const create_time = formatDate(new Date());

            const channel_id = config.channelId;
            const device_no = (data as any).device_no ?? "";
            const reg_device = (data as any).reg_device ?? "";
            const device_type = this.toIntOrZero((data as any).device_type);
            const device_model = (data as any).device_model ?? "";
            const device_version = (data as any).device_version ?? "";
            const system_version = (data as any).system_version ?? "";

            const accountInfo = (await User.checkAccountIsExist(uidDecoded, channel_id))[0];
            if (!accountInfo) {
                const accountRes = await User.createAccount(
                    uidDecoded,
                    channel_id,
                    ip,
                    device_no,
                    reg_device,
                    create_time,
                    platformStr
                );

                if (!accountRes || accountRes.affectedRows <= 0) {
                    logger.error("WebGame 登录落库失败: 添加账户失败", {uid: uidDecoded, channel_id});
                    return {code: 0, msg: "添加账户失败"};
                }
            }

            const logRes = await User.logAccountLogin(
                uidDecoded,
                ip,
                device_type,
                device_no,
                device_model,
                device_version,
                system_version,
                create_time,
                channel_id,
                platformStr
            );

            if (!logRes || logRes.affectedRows <= 0) {
                logger.error("WebGame 登录落库失败: 添加登录日志失败", {uid: uidDecoded, channel_id});
                return {code: 0, msg: "添加日志失败"};
            }
        } catch (e: any) {
            logger.error("WebGame 登录落库异常", {msg: e?.message, stack: e?.stack});
            return {code: 0, msg: "登录落库异常"};
        }

        // 登录成功后打点上报
        this.reportEvent('login', {
            user_id: uidDecoded,
            game_id: WEBGAME_APP_ID,
            server_id: (data as any).server_id ?? '',
            ip_addr: getClientIp(ctx),
        }, config);

        return {
            code: 1,
            msg: "success",
            data: {
                uid: uidDecoded,
            },
        };
    }

    /**
     * 联运 Web 游戏登录：生成带签名的游戏入口 URL。
     * 签名 md5(uid+platform+gkey+skey+time+is_adult[+exts]+'#'+lkey)，缺省字段在签名中按空串拼接；uid 为 urldecode 后的值参与签名。
     * 必填：uid、platform、time、back_url、type；gkey、skey、is_adult、exts 可选（不传则不出现在 URL 查询串中）。
     */
    async getGameUrl(ctx: Context, config: ChannelConfig): Promise<LoginResult> {
        const data = ctx.request.body as Record<string, unknown>;
        const lkey = config.loginConfig?.signKey;
        if (!lkey) {
            logger.error("WebGame 获取游戏 URL 失败: 未配置登录密钥 loginConfig.signKey");
            return {code: 0, msg: "服务器未配置登录密钥"};
        }

        const gateDomain = DEFAULT_GATE_DOMAIN;

        const uid = data.uid;
        const platform = data.platform;
        const gkey = data.gkey;
        const skey = data.skey;
        const time = data.time;
        const is_adult = data.is_adult;
        const back_url = data.back_url;
        const type = data.type;

        if (
            uid === undefined ||
            uid === null ||
            platform === undefined ||
            platform === null ||
            time === undefined ||
            time === null ||
            back_url === undefined ||
            back_url === null ||
            type === undefined ||
            type === null
        ) {
            return {
                code: 0,
                msg: "缺少必要参数: uid, platform, time, back_url, type",
            };
        }

        const uidStr = String(uid);
        let uidDecoded: string;
        try {
            uidDecoded = decodeURIComponent(uidStr.replace(/\+/g, "%20"));
        } catch {
            uidDecoded = uidStr;
        }

        const platformStr = String(platform);
        const gkeyStr = gkey === undefined || gkey === null ? "" : String(gkey);
        const skeyStr = skey === undefined || skey === null ? "" : String(skey);
        const timeStr = String(time);
        const isAdultStr =
            is_adult === undefined || is_adult === null ? "" : String(is_adult);

        const extsVal = data.exts;
        const hasExts =
            extsVal !== undefined &&
            extsVal !== null &&
            String(extsVal) !== "";

        let signRaw = `${uidDecoded}${platformStr}${gkeyStr}${skeyStr}${timeStr}${isAdultStr}`;
        if (hasExts) {
            signRaw += String(extsVal);
        }
        signRaw += `#${lkey}`;

        const sign = crypto.createHash("md5").update(signRaw).digest("hex").toLowerCase();

        const base = gateDomain.replace(/\/+$/, "");
        const params = new URLSearchParams();
        params.set("uid", uidDecoded);
        params.set("platform", platformStr);
        if (gkeyStr !== "") {
            params.set("gkey", gkeyStr);
        }
        if (skeyStr !== "") {
            params.set("skey", skeyStr);
        }
        params.set("time", timeStr);
        if (isAdultStr !== "") {
            params.set("is_adult", isAdultStr);
        }
        params.set("back_url", String(back_url));
        params.set("type", String(type));
        if (hasExts) {
            params.set("exts", String(extsVal));
        }
        params.set("sign", sign);

        const gameUrl = `${base}/login.html?${params.toString()}`;

        // 供客户端只用 sign 调用 /webgame/auth 做权限判断
        setWebgameAuthInfo(sign, {
            uid: uidDecoded,
            platform: platformStr,
            time: timeStr,
            gkey: gkeyStr || undefined,
            skey: skeyStr || undefined,
            is_adult: isAdultStr || undefined,
            exts: hasExts ? String(extsVal) : undefined,
        });

        // 登录成功后写入 accounts / account_login_logs（参考 checkUserToken）
        try {
            const ip = getClientIp(ctx);
            const create_time = formatDate(new Date());

            const channel_id = config.channelId;
            const device_no = (data as any).device_no ?? "";
            const reg_device = (data as any).reg_device ?? "";
            const device_type = this.toIntOrZero((data as any).device_type);
            const device_model = (data as any).device_model ?? "";
            const device_version = (data as any).device_version ?? "";
            const system_version = (data as any).system_version ?? "";

            const accountInfo = (await User.checkAccountIsExist(uidDecoded, channel_id))[0];
            if (!accountInfo) {
                const accountRes = await User.createAccount(
                    uidDecoded,
                    channel_id,
                    ip,
                    device_no,
                    reg_device,
                    create_time,
                    platformStr
                );

                if (!accountRes || accountRes.affectedRows <= 0) {
                    logger.error("WebGame 登录落库失败: 添加账户失败", {uid: uidDecoded, channel_id});
                    return {code: 0, msg: "添加账户失败"};
                }
            }

            const logRes = await User.logAccountLogin(
                uidDecoded,
                ip,
                device_type,
                device_no,
                device_model,
                device_version,
                system_version,
                create_time,
                channel_id,
                platformStr
            );

            if (!logRes || logRes.affectedRows <= 0) {
                logger.error("WebGame 登录落库失败: 添加登录日志失败", {uid: uidDecoded, channel_id});
                return {code: 0, msg: "添加日志失败"};
            }
        } catch (e: any) {
            logger.error("WebGame 登录落库异常", {msg: e?.message, stack: e?.stack});
            return {code: 0, msg: "登录落库异常"};
        }
        logger.info("WebGame 生成游戏登录 URL", {gateDomain: base, signLen: signRaw.length});
        return {
            code: 1,
            msg: "success",
            data: {
                gameUrl,
            },
        };
    }

    async handlePayment(ctx: Context, config: ChannelConfig): Promise<PaymentResult> {
        const data = ctx.request.body as any;
        logger.info("WebGame渠道支付回调参数", {url: ctx.href, params: data});

        // 新回调字段（不验 sign）
        const status = data?.status;
        const amount = data?.amount;
        const channelOrderId = data?.channel_order_id ?? data?.game_order ?? data?.id;
        const cpOrderId = data?.channel_exts ?? data?.extras_params ?? data?.game_order;

        // 兼容旧字段（历史上有 id/user_id/sign 等）
        const user_id = data?.user_id ?? data?.userId ?? data?.channel_uid;

        if (!cpOrderId || !status || amount === undefined || amount === null) {
            logger.warn("WebGame渠道支付回调失败: 缺少必要参数", {cpOrderId, status, amount});
            return {code: 0, msg: "缺少必要参数"};
        }

        if (status !== "completed") {
            logger.warn("WebGame渠道支付状态非completed", {channelOrderId, status});
            return {code: 0, msg: `支付状态非completed: ${status}`};
        }

        try {
            const validation = await PaymentHelper.validateOrder(String(cpOrderId));
            if (!validation.valid) {
                return {
                    code: validation.message?.includes("重复发货") ? 1 : 0,
                    msg: validation.message || "订单验证失败"
                };
            }

            const orderInfo = validation.orderInfo;

            const payAmount = parseFloat(String(amount));
            if (!Number.isFinite(payAmount)) {
                return {code: 0, msg: "支付金额格式错误"};
            }
            if (Math.abs(Number(orderInfo.amount) - payAmount) > 0.01) {
                logger.warn("WebGame渠道支付金额不匹配", {
                    channelOrderId,
                    requestAmount: payAmount,
                    orderAmount: orderInfo.amount
                });
                return {code: 0, msg: `订单金额不一致: 订单金额${orderInfo.amount}元，支付金额${payAmount}元`};
            }

            logger.info(`WebGame渠道支付订单开始发货`, {
                channelOrderId,
                cpOrderId,
                user_id,
                amount: payAmount
            });

            const result = await PaymentHelper.deliverOrder(
                orderInfo,
                ctx.request.ip,
                validation.url,
                String(channelOrderId ?? ''),
            );

            logger.info(`WebGame渠道支付订单发货完成`, {channelOrderId, cpOrderId, result});

            // 支付成功后打点上报
            if (result.code === 1) {
                this.reportEvent('payment', {
                    user_id: user_id ?? orderInfo.uid,
                    game_id: WEBGAME_APP_ID,
                    server_id: String(orderInfo.server_id ?? ''),
                    ip_addr: ctx.request.ip,
                    log_data: JSON.stringify({
                        order_id: cpOrderId,
                        channel_order_id: channelOrderId,
                        amount: payAmount,
                    }),
                }, config);
            }

            return result;
        } catch (error: any) {
            logger.error("WebGame渠道支付回调处理异常", {error: error.message, stack: error.stack});
            return {code: 0, msg: "回调处理异常"};
        }
    }

    // 角色查询接口（GET），平台查询指定区服下指定账号的角色信息
    // sign = md5(user_id + server_id + time + login_key)
    async handleRoleList(ctx: Context, config: ChannelConfig): Promise<any> {
        const q = {...ctx.query, ...ctx.request.body} as any;
        logger.info('WebGame 角色查询请求', {q});

        const {user_id, pid, server_id, time, sign} = q;

        if (!user_id || !pid || !server_id || !time || !sign) {
            return {code: 2, message: '参数不全', data: []};
        }

        const loginKey = config.loginConfig?.signKey ?? '';
        const expectedSign = crypto.createHash('md5')
            .update(String(user_id) + String(server_id) + String(time) + loginKey)
            .digest('hex').toLowerCase();

        if (expectedSign !== String(sign).toLowerCase()) {
            logger.warn('WebGame 角色查询签名错误', {user_id, server_id, expectedSign, sign});
            return {code: 3, message: '签名错误', data: []};
        }

        try {
            // 查 db_name
            const rows = await query(
                `SELECT db_name FROM game_server WHERE tag = ? AND sid = ? LIMIT 1`,
                [config.channelId, String(server_id)]
            ) as any[];

            if (!rows || rows.length === 0 || !rows[0].db_name) {
                logger.warn('WebGame 角色查询找不到区服', {server_id, channelId: config.channelId});
                return {code: 5, message: '服务器错误', data: []};
            }

            const dbName = rows[0].db_name;
            const newUniqueTag = `${config.channelId}|${server_id}|${user_id}`;

            // 查 MongoDB 角色信息
            const {getDb} = require("../../mongo/mongodb");
            const db = getDb(dbName);
            const collection = db.collection('char');

            const filter: any = {newUniqueTag};
            if (q.role_id) filter.roleId = String(q.role_id);

            const docs = await collection.find(filter).toArray();

            if (!docs || docs.length === 0) {
                return {code: 4, message: '未创建角色', data: []};
            }

            const data = docs.map((doc: any) => ({
                role_id:    doc.roleId    ?? doc._id.toString(),
                name:       encodeURIComponent(doc.name ?? ''),
                lv:         doc.lv        ?? 0,
                sex:        doc.sex       ?? 'm',
                vocation:   doc.vocation  ?? 0,
                createTime: doc.createTime
                    ? new Date(doc.createTime).toISOString().replace('T', ' ').slice(0, 19)
                    : '',
                power:      doc.zhandouli ?? doc.power ?? 0,
            }));

            return {code: 1, message: 'ok', data};
        } catch (e: any) {
            logger.error('WebGame 角色查询异常', {msg: e?.message, stack: e?.stack});
            return {code: 5, message: '服务器错误', data: []};
        }
    }

    // 登录跳转接口：验签 + 落库，返回拼接了平台参数的游戏 URL
    // sign = md5(user_id + server_id + pid + time + login_key)
    // 时间戳有效期 10 分钟
    async handleLoginUrl(ctx: Context, config: ChannelConfig): Promise<any> {
        const q = {...ctx.query, ...ctx.request.body} as any;
        logger.info('WebGame 登录跳转请求', {q});

        const {user_id, server_id, pid, time, sign} = q;

        if (!user_id || !server_id || !pid || !time || !sign) {
            return {code: 0, msg: '缺少必要参数'};
        }

        const loginKey = config.loginConfig?.signKey ?? '';
        const expectedSign = crypto.createHash('md5')
            .update(String(user_id) + String(server_id) + String(pid) + String(time) + loginKey)
            .digest('hex').toLowerCase();

        if (expectedSign !== String(sign).toLowerCase()) {
            logger.warn('WebGame 登录跳转签名错误', {user_id, expectedSign, sign});
            return {code: 0, msg: '签名错误'};
        }

        const ts = parseInt(String(time));
        if (Math.abs(Math.floor(Date.now() / 1000) - ts) > 600) {
            return {code: 0, msg: '登录链接已过期'};
        }

        // 落库
        try {
            const ip = getClientIp(ctx);
            const create_time = formatDate(new Date());
            const channel_id = config.channelId;
            const uidStr = String(user_id);

            const accountInfo = (await User.checkAccountIsExist(uidStr, channel_id))[0];
            if (!accountInfo) {
                await User.createAccount(uidStr, channel_id, ip, '', '', create_time, String(pid));
            }
            await User.logAccountLogin(uidStr, ip, 0, '', '', '', '', create_time, channel_id, String(pid));
        } catch (e: any) {
            logger.error('WebGame 登录跳转落库异常', {msg: e?.message});
        }

        // 拼接游戏 URL
        const base = DEFAULT_GATE_DOMAIN.replace(/\/+$/, '');
        const params = new URLSearchParams();
        params.set('user_id', String(user_id));
        params.set('server_id', String(server_id));
        params.set('pid', String(pid));
        params.set('time', String(time));
        params.set('sign', String(sign));
        if (q.ext   != null) params.set('ext',     String(q.ext));
        if (q.client != null) params.set('client',  String(q.client));
        if (q.isAdult != null) params.set('isAdult', String(q.isAdult));

        const gameUrl = `${base}?${params.toString()}`;
        logger.info('WebGame 登录跳转生成游戏URL', {user_id, server_id, gameUrl});

        // 登录打点
        this.reportEvent('login', {
            user_id,
            game_id: WEBGAME_APP_ID,
            server_id,
            ip_addr: getClientIp(ctx),
        }, config);

        return {code: 1, msg: 'success', data: {gameUrl}};
    }

    // 充值下单接口（GET），平台调用我们，验证签名后返回确认
    // sign = md5(user_id + server_id + role_id + time + pay_key)
    // extra_info 由客户端传入我们系统的 orderId，透传给回调
    async handleCreateOrder(ctx: Context, config: ChannelConfig): Promise<any> {
        const q = {...ctx.query, ...ctx.request.body} as any;
        logger.info('WebGame 充值下单请求', {q});

        const {user_id, server_id, role_id, goods_id, goods_name, money, extra_info, sign, time} = q;

        if (!user_id || !server_id || !role_id || !goods_id || !money || !sign || !time) {
            return {code: 0, message: '缺少必要参数'};
        }

        const payKey = config.paymentConfig?.callbackKey ?? '';
        const expectedSign = crypto.createHash('md5')
            .update(String(user_id) + String(server_id) + String(role_id) + String(time) + payKey)
            .digest('hex').toLowerCase();

        if (expectedSign !== String(sign).toLowerCase()) {
            logger.warn('WebGame 充值下单签名错误', {user_id, expectedSign, sign});
            return {code: 2, message: '签名错误'};
        }

        logger.info('WebGame 充值下单验证通过', {user_id, server_id, role_id, goods_id, money, extra_info});
        return {code: 1, message: 'ok'};
    }

    // 充值回调接口（GET），平台支付成功后调用，发货
    // sign = md5(user_id + order_id + money + time + server_id + role_id + extra_info + pay_key)
    // extra_info 为客户端传入的内部 orderId，用于发货查询
    async handlePayCallback(ctx: Context, config: ChannelConfig): Promise<any> {
        const q = {...ctx.query, ...ctx.request.body} as any;
        logger.info('WebGame 充值回调请求', {q});

        const {user_id, pid, order_id, money, time, server_id, role_id, extra_info, sign} = q;

        if (!user_id || !order_id || !money || !time || !server_id || !role_id || !sign) {
            return {code: 4, message: '缺少必要参数'};
        }

        const payKey = config.paymentConfig?.callbackKey ?? '';
        const extraStr = extra_info == null ? '' : String(extra_info);
        const expectedSign = crypto.createHash('md5')
            .update(String(user_id) + String(order_id) + String(money) + String(time) + String(server_id) + String(role_id) + extraStr + payKey)
            .digest('hex').toLowerCase();

        if (expectedSign !== String(sign).toLowerCase()) {
            logger.warn('WebGame 充值回调签名错误', {user_id, order_id, expectedSign, sign});
            return {code: 2, message: '签名错误'};
        }

        // extra_info 为内部 orderId
        if (!extraStr) {
            logger.warn('WebGame 充值回调 extra_info 为空，无法定位内部订单');
            return {code: 4, message: '充值失败，请联系客服'};
        }

        const validation = await PaymentHelper.validateOrder(extraStr);
        if (!validation.valid) {
            if (validation.message?.includes('重复发货')) {
                return {code: 3, message: '订单号重复'};
            }
            logger.warn('WebGame 充值回调订单验证失败', {extra_info: extraStr, msg: validation.message});
            return {code: 4, message: '充值失败，请联系客服'};
        }

        const orderInfo = validation.orderInfo;
        const payAmount = parseFloat(String(money));
        if (!Number.isFinite(payAmount) || Math.abs(Number(orderInfo.amount) - payAmount) > 0.01) {
            logger.warn('WebGame 充值回调金额不匹配', {order_id, money, orderAmount: orderInfo.amount});
            return {code: 4, message: '充值失败，请联系客服'};
        }

        logger.info('WebGame 充值回调开始发货', {order_id, extra_info: extraStr, user_id, money: payAmount});
        const result = await PaymentHelper.deliverOrder(orderInfo, ctx.request.ip, validation.url, String(order_id));
        logger.info('WebGame 充值回调发货完成', {order_id, result});

        if (result.code === 1) {
            // 支付成功打点上报
            this.reportEvent('payment', {
                user_id,
                game_id: WEBGAME_APP_ID,
                server_id,
                ip_addr: ctx.request.ip,
                log_data: JSON.stringify({order_id, platform_order_id: order_id, amount: payAmount}),
            }, config);
            return {code: 1, message: 'ok'};
        }

        return {code: 4, message: '充值失败，请联系客服'};
    }

    // 聊天监控接口：客户端传业务参数，服务端生成 game_id/timestamp/sign 后转发 SDK
    async handleChatMonitor(ctx: Context, config: ChannelConfig): Promise<any> {
        const q = ctx.request.body as any;
        logger.info('WebGame 聊天监控请求', {q});

        const {user_id, chat_content, channel, role_id, role_name, server_id, account_name} = q;

        if (!user_id || !chat_content || !channel || !role_id || !role_name || !server_id || !account_name) {
            return {code: 0, data: null, msg: '缺少必要参数'};
        }

        const reportUrl = WEBGAME_REPORT_URL;
        if (!reportUrl) {
            logger.error('WebGame 聊天监控未配置 WEBGAME_REPORT_URL');
            return {code: 0, data: null, msg: '服务器配置错误'};
        }

        const loginKey = config.loginConfig?.signKey ?? '';
        const gameId = String(WEBGAME_APP_ID);
        const timestamp = String(Math.floor(Date.now() / 1000));
        const sign = crypto.createHash('md5')
            .update(String(user_id) + gameId + timestamp + loginKey)
            .digest('hex').toLowerCase();

        const payload: Record<string, string> = {
            user_id: String(user_id),
            game_id: gameId,
            sign,
            timestamp,
            chat_content: String(chat_content),
            channel: "世界",
            role_id: String(role_id),
            role_name: String(role_name),
            server_id: String(server_id),
            account_name: String(account_name),

        };

        // 可选字段透传
        const optionalFields = ['level', 'gold', 'chat_time', 'ip_addr',
            'sec_chat_user_id', 'sec_chat_role_id', 'sec_chat_nickname', 'sec_chat_role_level'];
        for (const field of optionalFields) {
            if (q[field] !== undefined && q[field] !== null && q[field] !== '') {
                payload[field] = String(q[field]);
            }
        }

        try {
            const res = await axios.post(
                reportUrl + '/api/v2/webgame/chat-monitor',
                payload,
                {headers: {'Content-Type': 'application/json'}, timeout: 5000}
            );
            console.log("请求地址,",reportUrl + '/api/v2/webgame/chat-monitor',"参数",payload);
            logger.info('WebGame 聊天监控 SDK 响应', {result: res.data});
            return res.data;
        } catch (err: any) {
            logger.error('WebGame 聊天监控请求 SDK 失败', {error: err?.message});
            return {code: 0, data: null, msg: '聊天监控请求失败'};
        }
    }

    // 签名规则：所有参数（除sign）按字典序排序，拼接key=value&...&key={game_secret}，MD5
    private verifySign(data: any, gameSecret: string): boolean {
        try {
            const receivedSign = data.sign;
            if (!receivedSign) return false;

            const params = {...data};
            delete params.sign;

            const sortedKeys = Object.keys(params).sort();
            const signStr = sortedKeys
                .map(key => {
                    const v = params[key];
                    if (v === null || v === undefined) return `${key}=`;
                    return `${key}=${v}`;
                })
                .join('&');

            const stringToSign = `${signStr}&key=${gameSecret}`;
            const calculatedSign = crypto.createHash('md5').update(stringToSign).digest('hex').toLowerCase();

            logger.info("WebGame渠道签名验证", {calculatedSign, receivedSign: receivedSign.toLowerCase()});

            return calculatedSign === receivedSign.toLowerCase();
        } catch (error: any) {
            logger.error("WebGame渠道签名验证出错", {error: error.message});
            return false;
        }
    }
}
