import {ApiCall} from 'tsrpc';
import {YangChengMuBiaofun} from '../api_s2c/event/yangchengmubiao/fun';
import {MsgPayChange} from '../shared/protocols/msg_s2c/MsgPayChange';
import {payLog} from '../shared/protocols/pay/PtlGetList';
import {player} from '../shared/protocols/user/type';
import {PublicShared} from '../shared/public/public';
import {EmailFun} from './email';
import {HuoDongFun} from './huodongfun';
import {call, PlayerFun} from './player';
import {number} from "mathjs";
import {getGud} from './gud';
import {getConf as zmlbGetConf} from '../api_s2c/event/zhoumolibao/ApiOpen';
import {Christmasfun} from "../api_s2c/event/christmas/fun";

async function checkPayIsActive(payId: string, logs: payLog[], payArgs) {
    let conf: any = await this.getConf(payId, payArgs);
    if (!conf) return false;

    let lastLog = logs.last();
    if (!lastLog) return false;

    if (conf.time == -1) return true;
    return G.time < lastLog.eTime;
}

export class PayFun {
    static async addPayLog(uid: string, payId: string, type: "system" | "user" | "legu", payArgs) {
        let conf: any = await this.getConf(payId, payArgs);
        let time = PublicShared.getToDayZeroTime(G.time);

        let obj: payLog = {time: G.time, type: type};
        if (conf.time != -1) obj.eTime = time + conf.time;

        obj.args = payArgs

        if (payId == 'G123SendGift') {
            obj.popup_id = payArgs.popup_id
            obj.template_id = payArgs.template_id
            obj.buyNumber = conf.buyNumber
        }

        let result = await G.mongodb.collection('payLogNew').findOneAndUpdate(
            {uid: uid, key: payId, del_time: {$exists: false}},
            {$push: {values: obj}},
            {upsert: true, returnDocument: 'after'}
        );

        let msg: MsgPayChange = {[payId]: result.value?.values || []};
        if (payId == 'G123SendGift') {
            G.server.sendMsgByUid(uid, 'msg_s2c/PayChange', R.groupBy(i => i.popup_id)(result.value?.values));
        } else {
            G.server.sendMsgByUid(uid, 'msg_s2c/PayChange', msg);
        }
    }

    static changeG123GiftLog(uid: string, popupId: string) {
        G.mongodb.collection('giftLog').updateOne({popup_id: popupId}, {$inc: {buyNumber: 1}}, {upsert: true});
    }

    static async delPayLog(uid: string, ...args: { payId: string, val: payLog[]; }[]) {
        args.map(a => {
            G.mongodb.collection('payLogNew').updateOne(
                {uid: uid, key: a.payId, del_time: {$exists: false}},
                {$set: {del_time: G.time}},
                {upsert: true}
            );
        })

        G.mongodb.collection('payLogNew').insertMany(args.map(i => ({key: i.payId, uid: uid, values: i.val})));

        G.server.sendMsgByUid(uid, 'msg_s2c/PayChange', Object.fromEntries(args.map(a => [a.payId, a.val])));
    }

    static async getPayLog(uid: string, payId: string): Promise<payLog[]>;
    static async getPayLog(uid: string, payId: string) {
        // let allPlayerPayLog = await G.redis.hGet('player:payLog', uid)

        let val = await G.mongodb.collection("payLogNew").findOne({uid: uid, key: payId, del_time: {$exists: false}}, {
            projection: {
                _id: 0,
                uid: 0
            }
        });
        let allPlayerPayLog = {[payId]: val?.values || []}

        return allPlayerPayLog?.[payId] || [];
    }

    static async getPayLogs(uid: string, payIds?: string[]) {
        let where = {
            uid: uid,
            del_time: {$exists: false}
        }
        if (payIds?.length) where['key'] = {$in: payIds}

        let logs = await G.mongodb.collection("payLogNew").find(where, {
            projection: {
                _id: 0,
                uid: 0
            }
        }).toArray();

        if (payIds?.length) {
            return Object.fromEntries(payIds.map(id => [id, logs.find(i => i.key == id)?.values || []]));
        }
        let allLogs = {}
        logs.map(i => allLogs[i.key] = i.values)
        return allLogs
    }

    /**
     * 获取支付配置，主要是针对G123礼包、GM后台内部充值的特殊处理
     * 让这两个不在预设充值挡位内的，可以正常发奖
     * @param payId
     * @param payArgs
     */
    static async getConf(payId, payArgs) {
        let conf: any = G.gc.pay[payId]
        if (payId == 'G123SendGift') {
            let giftInfo = await G.mongodb.collection('giftLog').findOne({popup_id: payArgs.popup_id});

            let gPrize = giftInfo.items
            let bPrize = []
            for (let index in gPrize) {
                let args = index.split('^')
                bPrize.push({a: args[0], t: args[1], n: gPrize[index]})
            }

            conf = {
                id: 'G123SendGift',
                money: giftInfo.price,
                payExp: [{"a": "attr", "t": "payExp", "n": giftInfo.vipPoints}],
                prize: bPrize,
                firstPayPrize: [],
                name: 'G123SendGift',
                time: -1,
                buys: giftInfo.purchaseLimitAmount,
                needVip: 0,
                front: {},
                buyNumber: giftInfo.buyNumber
            }
        } else if (payId == 'LeguSendGift') {
            conf = {
                id: 'LeguSendGift',
                money: payArgs.money,
                payExp: payArgs.payExp,
                prize: payArgs.prize,
                firstPayPrize: [],
                name: 'LeguSendGift',
                time: -1,
                buys: 0,
                needVip: 0,
                front: {}
            }
        } else if (payId == '136GiftAll') {
            // 136礼包一键购买，加入所有礼包奖励
            conf = {
                ...conf,
                prize: R.compose(R.flatten(), R.map(i => i.prize), R.filter(i => i.id.indexOf('136Gift') != -1), R.values())(G.gc.pay)
            }
        }
        return conf
    }

    /**
     * 实际作用是支付后发奖
     * 针对G123礼包有特殊处理，不在游戏配置内预设的充值挡位
     * todo 目前还缺验证通道，内部充值可以直接调用
     * @param uid
     * @param payId
     * @param payArgs
     * @param type
     * @param orderNo
     */
    static async pay(uid: string, payId: string, payArgs?: any, type?: "system" | "user" | "legu", orderNo: string = '') {

        if (orderNo) {
            //判断订单号是否已经支付过
            let payed = await G.mongodb.collection('payOrderLog' as any).count({"orderNo": orderNo});
            if (payed) {
                console.log(uid, payId, payArgs, orderNo);
                console.log('订单号已存在');
                return;
            }
        }
        /**
         * 不要轻易改动这个方法，不允许集成业务逻辑在内部
         * 如果有业务逻辑通过事件驱动，在支付成功后emit事件
         */

        let conf: any = await this.getConf(payId, payArgs);

        let prize = [...conf.prize];
        let player = await getGud(uid);

        if (conf.firstPayPrize.length > 0) {
            let logs = await this.getPayLog(uid, payId);

            if (!logs || logs.length < 1) {
                prize = PublicShared.mergePrize([].concat(conf.prize, conf.firstPayPrize));
            }
        }

        let call = this.getCall(player);
        call.req = {
            uid: uid,
            payId: payId,
            payArgs: payArgs,
            orderNo: orderNo
        }
        //改为统一方法，此处自选不这样用
        // if (payId.indexOf('zixuanlibao') != -1) {
        //     // 后端唯一标识 htype 4 自选/定制 礼包
        //     // @ts-ignore
        //     let _hdList = await HuoDongFun.gethdList(call, 4)
        //     _hdList.forEach(ele => {
        //         let selectPrize = ele.data.gift.find(v => v.payId == payId)?.prize;
        //         if (prize) {
        //             let index = payArgs instanceof Array ? payArgs : [];
        //             let select = selectPrize.map((v, i) => v[index[i]] ? v[index[i]] : v[0]);
        //             prize.push(...select);
        //         }
        //     })
        // }
        if (payId.indexOf('ycmb') != -1) {
            // @ts-ignore     // 养成活动充值礼包
            let _hdids = await YangChengMuBiaofun.getCon(call);
            Object.values(_hdids).forEach(ele => {
                let selectPrize = ele.data.gift.find(v => v.payId == payId)?.prize;
                if (selectPrize) {
                    prize.push(...selectPrize);
                }
            });
        }

        if (payId.indexOf('wkdlibao') != -1) {
            let call = this.getCall(player)
            let conf = await zmlbGetConf(call, {payId})
            prize.push(...conf.prize)
        }

        /**
         * todo 不要轻易改动这个方法，不允许集成业务逻辑在内部
         * todo 此方法仅仅是支付成功后的发奖
         * todo 如果有业务逻辑通过事件驱动，在支付成功后emit事件
         * todo 下面这个方法不应该这样写
         */
        //圣诞节活动充值
        try {
            await Christmasfun.payChristmas(payId, call);
        } catch (e) {
            console.log("Christmasfun.payChristmas Error", e);
        }

        /**
         * 如果是自选奖励，payArgs里传入自选项，直接发奖
         * 在check里判断拦截自选项的奖励
         */
        if (payArgs && payArgs?.htype && payArgs?.selectList) {
            let selectPrize = await G.ioredis.get(`pay:${payId}:${player.uid}`);
            if (selectPrize) {
                prize.push(...JSON.parse(selectPrize))
                G.ioredis.del(`pay:${payId}:${player.uid}`)
            }
        }

        let isReplaceConf = await this.checkBuysAfterPay(uid, payId, conf, payArgs, player)
        if (isReplaceConf) {
            let prizePayId = `zuanshi_${conf.money}`
            payArgs.toPrizePayId = prizePayId
            conf = this.replacePrizeToChongzhi(prizePayId, conf)
            prize = conf.prize;
        }

        await PlayerFun.sendPrize(call, prize);
        await PlayerFun.addAttr(call, conf.payExp);
        if (payId == 'G123SendGift') {
            this.changeG123GiftLog(uid, payArgs.popup_id)
        }

        await this.addPayLog(player.uid, payId, type, payArgs);
        // this.addPayLogNew(player.uid, payId, type);

        //记录支付信息log
        if (orderNo) {
            await G.mongodb.collection('payOrderLog' as any).insertOne({
                "orderNo": orderNo,
                "payId": payId,
                "uid": uid,
                "payArgs": payArgs,
                "type": type,
                "ctime": new Date().getTime()
            });
        }


        G.server.sendMsgByUid(uid, 'msg_s2c/Collection', {
            fromApi: `pay_${payId}`,
            msg: call.eventMsg
        });

        G.server.sendMsgByUid(uid, 'msg_s2c/PayResult', {
            code: 1,
            data: {
                id: payId,
                prize: prize,
                price: payArgs?.price
            }
        });

        G.emit('PLAYER_PAY', player, payId, payArgs, call);
        G.emit("Class_task_116", 'Class_task_116', call, 1, 0);
    }

    /**
     * 转换订单奖励成直冲钻石奖励
     * @param payId
     * @param conf
     */
    static replacePrizeToChongzhi(payId, conf) {
        return {
            id: payId,
            money: conf.money,
            payExp: [
                {
                    "a": "attr",
                    "t": "payExp",
                    "n": conf.money * 10
                }
            ],
            prize: [
                {
                    "a": "attr",
                    "t": "rmbmoney",
                    "n": conf.money * 10
                }
            ],
            firstPayPrize: [],
            name: `replacePrize`,
            time: -1,
            buys: 0,
            needVip: 0,
            front: {}
        }

    }

    /**
     * 支付成功后，某些情况下会有超出限购次数的限购订单
     * 这种情况需检查，返回true是需要转换成钻石的订单
     * @param uid
     * @param payId
     * @param conf
     * @param payArgs
     * @param player
     */
    static async checkBuysAfterPay(uid, payId, conf, payArgs, player) {
        if (payId == 'G123SendGift') {
            let giftInfo = await G.mongodb.collection('giftLog').findOne({popup_id: payArgs.popup_id});
            if (giftInfo.purchaseLimitAmount && giftInfo.buyNumber >= giftInfo.purchaseLimitAmount && number(giftInfo.price) > 0) {
                return true
            }
        } else {
            let buyLog = await this.getPayLog(uid, payId) || [];
            if (conf.buys > 0 && conf.time > 0) {
                buyLog = buyLog.filter(v => v.time >= PublicShared.getToDayZeroTime(G.time));
            }
            if (conf.buys > 0 && buyLog.length >= conf.buys) return true;
            //针对周末礼包的单独处理
            if (payId.indexOf('wkdlibao') != -1) {
                let call = this.getCall(player)
                let conf = await zmlbGetConf(call, {payId})
                if (buyLog.length && buyLog.length >= conf.buyNum) return true
            }
        }
        return false
    }

    /**
     * 拉起订单前检查购买前置条件，如符合，通知前端拉起支付
     * 针对G123礼包有特殊处理，不在游戏配置内预设的充值挡位
     * todo 目前没有订单锁，所以会造成限购商品可以二次拉起支付
     * @param player
     * @param payId
     * @param payArgs
     */
    static async check(player: player, payId: string, payArgs?: any) {
        let conf: any = await this.getConf(payId, payArgs);

        if (!conf) return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: 0});
        if (player.vip < conf.needVip) return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -3});
        if (conf.front.cond) {
            let pays = await this.getPayLogs(player.uid, conf.front.pays);
            //@ts-ignore
            let actives = Object.entries(pays).map(v => checkPayIsActive(v[0], v[1], payArgs)).filter(v => v);
            if (conf.front.cond == '|' && actives.length < 1) return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -4});
            if (conf.front.cond == '&' && actives.length < conf.front.pays.length) return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -4});
        }

        if (payId == 'G123SendGift') {
            let giftInfo = await G.mongodb.collection('giftLog').findOne({popup_id: payArgs.popup_id});

            if (giftInfo.purchaseLimitAmount && giftInfo.buyNumber >= giftInfo.purchaseLimitAmount) {
                return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -1});
            }

            if (number(giftInfo.price) <= 0) {
                await this.pay(player.uid, payId, payArgs, 'user');
                return
            }
        } else {
            let buyLog = await this.getPayLog(player.uid, payId) || [];

            if (conf.buys > 0 && conf.time > 0) {
                buyLog = buyLog.filter(v => v.time >= PublicShared.getToDayZeroTime(G.time));
            }

            if (conf.buys > 0 && buyLog.length >= conf.buys) return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -1});

            // todo 没看懂这个判断针对什么支付，不支持周末礼包这个类型的限购，所以加上判断：payId.indexOf('wkdlibao') == -1
            if (conf.time != -1 && buyLog.slice(-1)[0]?.eTime > G.time && conf.buys == 0 && payId.indexOf('wkdlibao') == -1) {
                return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -2});
            }

            /**
             * 活动相关的自选礼包，目前htype 4、14支持此方法
             * payArgs里传相应参数，selectList选择的奖励列表
             * 往后所有自选类型，走这个方法，配置格式同4、14
             */
            if (payArgs && payArgs?.htype && payArgs?.selectList) {
                let call = this.getCall(player)
                // @ts-ignore
                let _hdInfo = (await HuoDongFun.gethdList(call, payArgs.htype))[0]
                // 活动类购买不符合条件返回-5
                if (!_hdInfo) G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -5})
                let giftInfo = _hdInfo.data.gift.find(v => v.payId == payId);
                if (!giftInfo) G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -5})

                let dlzPrize = R.flatten(giftInfo.dlz.map(i => R.values(i)))
                let selectPrize = payArgs.selectList.map(v => {
                    let sPrize = dlzPrize.find(o => o.a == v.a && o.t == v.t && o.n == v.n)
                    // 严格判断，自选奖励不存在奖励列表，返回-6
                    if (!sPrize) return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -6});
                    return sPrize
                })
                // 自选奖励过判断后，存入redis，6小时内支付有效 todo 6小时可能有些长
                let prize = [...giftInfo.prize, ...selectPrize]
                G.ioredis.setex(`pay:${payId}:${player.uid}`, 21600, JSON.stringify(prize));
            }

            //针对每日礼包的单独处理
            let zeroTime = PublicShared.getToDayZeroTime(G.time)
            if (payId.indexOf('136Gift') != -1 && payId != '136GiftAll') {
                let buyLog136 = await this.getPayLog(player.uid, '136GiftAll');
                buyLog136 = buyLog136.filter(v => v.time >= zeroTime);
                if (buyLog136.length) return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -1});
            }
            if (payId.indexOf('136Gift') != -1 && payId == '136GiftAll') {
                let ids = R.compose(R.map(i => i.id), R.filter(i => i.id.indexOf('136Gift') != -1 && i.id != '136GiftAll'), R.values())(G.gc.pay)
                let buyLog136 = await this.getPayLogs(player.uid, ids);
                let buyLog136list = R.compose(R.filter(v => v.time >= zeroTime), R.flatten(), R.values())(buyLog136)
                if (buyLog136list.length) return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -1});
            }
            //针对周末礼包的单独处理
            if (payId.indexOf('wkdlibao') != -1) {
                let call = this.getCall(player)
                let conf = await zmlbGetConf(call, {payId})
                if (buyLog.length && buyLog.length >= conf.buyNum) return G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: -1});
            }
        }

        if (G.config.debug) {
            await this.pay(player.uid, payId, payArgs, 'user', "debuger_" + new Date().getTime());
        } else {
            G.server.sendMsgByUid(player.uid, 'msg_s2c/PayResult', {code: 200});
        }
    }

    static getCall(player: player): call {

        return {
            get otherBuff() {
                return this.conn.otherBuff;
            },
            uid: player.uid,
            conn: G.server.uid_connections[player.uid] ? G.server.uid_connections[player.uid] : {
                id: null,
                uid: player.uid,
                gud: player,
                item: null,
                otherBuff: null,
                heroPos: null,
                refreshPower: null,
                sendMsg: null,
            },
            service: {name: "pay"},
            req: {},
            eventMsg: null,
            addEventMsg() {
                return ApiCall.prototype.addEventMsg.call(this, ...arguments);
            }
        };
    }

    /**
     * 当玩家购买过有时间期效性的礼包 并且在生效期内每天有邮件奖励时 每天首次登陆时根据时间补发邮件 玩家每天首次登陆会进入检查
     * @param player
     * @param lastTime 上一次登陆时间
     * @param curTime 每日首次登陆时间
     */
    static async checkGiftDayEmail(player: player, lastTime: number, curTime: number) {
        const buyLogs = await this.getPayLogs(player.uid);
        const keys = Object.keys(buyLogs).filter(payId => G.gc.payEmail[payId]?.length > 0);
        const curZeroTime = PublicShared.getToDayZeroTime(curTime);
        const lastZeroTime = PublicShared.getToDayZeroTime(lastTime);

        keys.forEach(payId => {
            let payEmailConf = G.gc.payEmail[payId] as _gcType['payEmail']['caifutequan'];
            let latelyLog = buyLogs[payId].slice(-1)[0];
            if (!latelyLog || (latelyLog.eTime && lastZeroTime >= latelyLog.eTime)) return;

            let countDay = 1;
            let payZeroTime = PublicShared.getToDayZeroTime(latelyLog.time);
            let payZeroEtime = PublicShared.getToDayZeroTime(latelyLog.eTime);

            let days: number
            if (curZeroTime > payZeroEtime) {
                days = (payZeroEtime - lastZeroTime) / (24 * 3600);
            } else {
                days = (curZeroTime - lastZeroTime) / (24 * 3600);
            }

            for (let n = 1; n <= days; n++) {
                countDay++;
                // if (payZeroTime + n * 24 * 3600 <= payZeroEtime) continue;
                payEmailConf.forEach(conf => {
                    if (conf.day == 1 || countDay % conf.day == 0) {
                        EmailFun.addEmail({
                            uid: player.uid,
                            type: 'system',
                            title: conf.title,
                            content: conf.content,
                            prize: conf.prize,
                            createTime: lastZeroTime + n * 24 * 3600
                        });
                    }
                });
            }
        });
    }

    /**
     * 获取某个时间段内 玩家充值x元以上的天数
     */
    static async getPayDaysBuyPayNum(uid: string, sTime: number, eTime: number, ckeckNeed: number) {
        let logs = await G.mongodb.collection('dayPay').find({uid: uid}).toArray() || [];
        return logs.filter(log => log.time >= sTime && log.time <= eTime && log.payNum >= ckeckNeed).length;
    }

    /**
     * 获取某个时间段内 玩家的充值总金额
     */
    static async getPayDaysAllPayNum(uid: string, sTime: number, eTime: number) {
        let logs = await G.mongodb.collection('dayPay').find({
            uid: uid,
            time: {$gte: sTime, $lt: eTime}
        }).toArray() || [];

        if (logs.length <= 0) return 0;
        return logs.map(log => log.payNum).reduce((a, b) => a + b);
    }
}