import { ObjectId, OptionalId } from 'mongodb';
import { ApiCall, BaseConnection, TsrpcError } from 'tsrpc';
import { checkPlayerGift } from '../api_s2c/event/xianshilibao/fun';
import { md_redPoint_check } from '../api_s2c/gongyu/mingdao/ApiOpen';
import { CollectionPeiJian } from '../module/collection_peijian';
import { Wjjl } from '../module/collection_wjjl';
import { MongodbCollections } from '../module/mongodb';
import { G123 } from '../sdk/G123';
import { ResGetList } from '../shared/protocols/item/PtlGetList';
import { ResLogin } from '../shared/protocols/user/PtlLogin';
import { player } from '../shared/protocols/user/type';
import { HeroShared, otherBuff } from '../shared/public/hero';
import { PublicShared } from '../shared/public/public';
import { HeroFun } from './hero';
import { ShiwuFun } from './shiwu';
import { UserFun } from './user';
import { getItemByItemId, getItemNum } from './item';
import { getGud, setGud } from './gud';
import { addGameLog } from "../gameLog";
import { PushGiftFun } from "./pushgift";
import { ActionLog } from './actionLog/actionLog';
import HeroSkinFun from './heroskin';

export type call = {
    get otherBuff(): otherBuff;
    uid: string;
    eventMsg: k_v<k_v<any>>;
    conn: {
        readonly id: string;
        get otherBuff(): otherBuff;
        get heroPos(): k_v<string>;
        uid: string;
        gud: player;
        item?: ResGetList['list'];
        refreshPower(): Promise<any>;
        sendMsg: BaseConnection['sendMsg'];
    };
    service: { name: string };
    addEventMsg: ApiCall['addEventMsg'];
    req: {}
};

export class PlayerFun {
    /**
     * 获取玩家atn数量
     */
    static async getAtnNum(call: call, atn: atn): Promise<number> {

        if (atn.a == 'attr') {
            let _gud = await getGud(call.conn.uid);
            return _gud[atn.t] || 0;
            //return call.conn.gud[atn.t] || 0;
        } else if (atn.a == 'item') {
            let numInfo = await getItemNum(call.conn.uid, atn.t);
            return numInfo[atn.t]?.num || 0;
            //去掉item Redis相关
            //return call.conn.item?.[atn.t]?.num || await G.redis.get('item', call.conn.uid, atn.t, 'num') || 0;
        } else return 0;
    }

    /**
     * 判断消耗是否满足
     * @param err 默认会中断调用栈 向客户端返回道具不足错误信息
     */
    static async checkNeedIsMeet(call: call, need: atn[], err = true) {
        for (let atn of need) {
            let has = await this.getAtnNum(call, atn);

            if (has < atn.n) {
                if (err) {
                    // 消耗不足 触发推送礼包
                    PushGiftFun.chkItemGift(call.uid, atn)
                    throw new TsrpcError('', { code: -104, atn: atn });
                } else {
                    return { isOk: false, atn: atn };
                }
            }
        }
        return { isOk: true, atn: null };
    }

    /**
     * 轮询多个消耗条件是否满足
     */
    static async checkNeedByArgs(call: call, ...args: atn[]): Promise<{
        isOk: boolean;
        atn: atn;
    }> {
        let need = args.shift();
        let meet = await this.checkNeedIsMeet(call, [need], false);

        if (meet.isOk) return {
            isOk: true,
            atn: need
        };
        if (args.length < 1) {
            throw new TsrpcError('', { code: -104, atn: meet.atn });
        }
        return await this.checkNeedByArgs(call, ...args);
    }

    /**
     * 扣除消耗 仅限 item attr
     */
    static async cutNeed(call: call, val: atn[]) {
        let needArr = PublicShared.mergePrize(val);
        needArr.forEach(v => v.n *= -1);
        let all = [];

        let attr = needArr.filter(atn => atn.a == 'attr' && atn.n != 0);
        if (attr.length > 0) {
            all.push(this.addAttr(call, attr));
        }

        let item = needArr.filter(atn => atn.a == 'item' && atn.n != 0);
        if (item.length > 0) {
            all.push(this.addItem(call, item));
        }
        // 记录消耗
        addGameLog(call.uid, call.service.name, call.req, { need: val })
        await Promise.all(all);
        G.emit('USE_ITEM', call.conn.gud, needArr.map(need => {
            return { ...need, n: Math.abs(need.n) };
        }));
    }

    /**
     * 发送奖励
     */
    static async sendPrize(call: call, prizeList: atn[]) {
        prizeList = PublicShared.mergePrize(prizeList);

        let attr = prizeList.filter(atn => atn.a == 'attr' && atn.n != 0);
        let item = prizeList.filter(atn => atn.a == 'item' && atn.n != 0);
        let hero = prizeList.filter(atn => atn.a == 'hero' && atn.n != 0);
        let equip = prizeList.filter(atn => atn.a == 'equip' && atn.n != 0);
        let shiwu = prizeList.filter(atn => atn.a == 'shiwu' && atn.n != 0);
        let peijian = prizeList.filter(atn => atn.a == 'peijian' && atn.n != 0);
        let heroskin = prizeList.filter(atn => atn.a == 'heroskin' && atn.n != 0);

        // 记录获得
        addGameLog(call.uid, call.service.name, call.req, { prize: prizeList })
        await Promise.all([
            attr.length > 0 && this.addAttr(call, attr),
            item.length > 0 && this.addItem(call, item),
            hero.length > 0 && this.addHero(call, hero),
            equip.length > 0 && this.addEquip(call, equip),
            shiwu.length > 0 && this.addShiwu(call, shiwu),
            peijian.length > 0 && this.addPeijian(call, peijian),
            heroskin.length > 0 && this.addHeroskin(call, heroskin),
        ]);

        return prizeList;
    };

    //attr里的指定字段的值，不能小于0
    static fixAttrLteZero(t: string, val: number) {
        if (['jinbi', 'rmbmoney', 'payExp', 'nexp'].includes(t) && val < 0) {
            return 0
        } else {
            return val;
        }
    }

    /**
     * 修改玩家属性或货币
     */
    static async addAttr(call: call, val: Partial<{
        [k in keyof ResLogin['gud']]: ResLogin['gud'][k]
    }> | atn[]) {
        let change = {};
        if (val instanceof Array) {
            let all = [];
            for (let atn of val) {
                change[atn.t] = this.fixAttrLteZero(atn.t, await this.getAtnNum(call, atn) + atn.n);
                if (atn.t == 'rmbmoney') {
                    this.changeAttrLog(call.conn.uid, change[atn.t], atn, call.conn.gud.rmbmoney)
                    // 扣除钻石时
                    if (atn.n < 0) {
                        // 监听任务消耗任务
                        G.emit("Class_task_156", 'Class_task_156', call, -atn.n, 0);
                    }
                }

                if ((atn.t == 'rmbmoney' || atn.t == 'jinbi') && atn.n > 0) {
                    //统计今日获取的金币和钻石
                    ActionLog.addDayLog(call.conn.uid, { key: 'got_' + atn.t, val: atn.n });
                }

                // 增加vip经验的任务监听
                if (atn.t == "payExp" && atn.n > 0) {
                    G.emit("Class_task_157", 'Class_task_157', call, atn.n, 0);
                }
                all.push(this.changeAttr(call.conn.uid, change));
                all.push(this.upAttr(call, { ...atn, n: change[atn.t] }));
                //await this.changeAttr(call.conn.uid, change);
                //await this.upAttr(call, {...atn, n: change[atn.t]});
            }
            await Promise.all(all);
        } else {
            change = val;
            await this.changeAttr(call.conn.uid, change);
            for (let [k, v] of Object.entries(val)) {
                if (['lv', 'vip', 'renown', 'wxcLv', 'tujianLv'].includes(k)) {
                    UserFun.activeHeadFrame(call.uid, k, v);
                    UserFun.activeChatFrame(call.uid, k, v);
                }
            }
        }
        // 修改属性应在相关奖励领取之前，否则奖励内获取的用户数据是旧数据
        // await this.changeAttr(call.conn.uid, change);
        call.addEventMsg('msg_s2c/PlayerChange', change);

        // 等级改变 触发推送礼包
        if (change["lv"]) {
            PushGiftFun.chkLvGift(call.uid, change["lv"])
        }
        // 关卡改变 触发推送礼包
        if (change["mapId"]) {
            PushGiftFun.chkLevelGift(call.uid, change["mapId"])
        }

    }

    static async changeAttrLog(uid: string, change, atn, before) {
        let data = {
            uid,
            before,
            rmbmoney: change,
            change: atn.n,
            isAdd: atn.n > 0,
            cTime: G.time,
            atn
        }
        G.mongodb.collection('rmbuse').insertOne(data);
        // 消费竞赛开启时写入跨服数据库
        if (G.huodong.xfjs && !data.isAdd && typeof data.change == 'number') {
            G.crossmongodb.collection('rmbuse').updateOne({ uid: data.uid, type: `xfjs_${G.huodong.xfjsId}` }, {
                $set: { time: G.time },
                $inc: { change: data.change }
            }, { upsert: true });
        }
    }

    static async changeAttr(uid: string, change: Partial<player>) {
        setGud(uid, change);

        G.mongodb.collection('user').updateOne({ uid: uid }, { $set: change });

        if (G.server.uid_connections[uid]) {
            checkPlayerGift(G.server.uid_connections[uid].gud, change);
            //Object.assign(G.server.uid_connections[uid].gud, change);
        }

        addGameLog(uid, "_changeAttr", {}, change)
    }

    /**
     * 检查玩家是否满足等级提升 vip提升等
     */
    static async upAttr(call: call, atn: atn) {

        switch (atn.t) {
            case 'nexp':
                let addLv = 0;
                const conf = G.gc.playerLv;
                const curLv = call.conn.gud.lv;
                while (conf[curLv + addLv + 1] && atn.n >= conf[curLv + addLv + 1].need) {
                    addLv++;
                    G123.sendUserLevelUp({ ...call.conn.gud, lv: curLv + addLv, nexp: atn.n });
                }
                addLv && await this.addAttr(call, { lv: curLv + addLv });
                break;
            case 'payExp':
                let addVip = 0;
                const vipConf = G.gc.vip;
                const curVip = call.conn.gud.vip;
                while (vipConf[curVip + addVip + 1] && atn.n >= vipConf[curVip + addVip + 1].exp) {
                    addVip++;
                }
                addVip && await this.addAttr(call, { vip: curVip + addVip });
                break;
        }
    }

    /**
     * 添加道具
     */
    static async addItem(call: call, val: atn[]) {
        for (let atn of val) {
            let upObj = {
                filter: { uid: call.uid, itemId: atn.t },
                update: {
                    $setOnInsert: {
                        firstTime: G.time,
                    },
                    $set: {
                        lastTime: G.time,
                    },
                    $inc: {
                        num: atn.n
                    },
                },
                options: {
                    upsert: true
                }
            };
            let itemInfo = await getItemByItemId(call.uid, atn.t);
            let item = itemInfo[atn.t];
            //去掉item Redis相关
            //let item = call.conn.item?.[atn.t] || await G.redis.get('item', call.uid, atn.t);

            if (!item) {
                let data = {
                    _id: '',
                    num: atn.n,
                    uid: call.uid,
                    itemId: atn.t,
                    firstTime: upObj.update.$setOnInsert.firstTime,
                    lastTime: upObj.update.$setOnInsert.firstTime
                };
                G.mongodb.collection('item').updateOne(upObj.filter, upObj.update, upObj.options);
                call.addEventMsg('msg_s2c/ItemChange', atn.t, data);
                addGameLog(call.uid, "_itemChange", { "additem": 1 }, {
                    "filter": upObj.filter,
                    "update": upObj.update,
                    "options": upObj.options
                })
            } else {
                if (item.num + atn.n <= 0) {
                    await Promise.all([
                        G.mongodb.collection('item').deleteOne({ uid: call.uid, itemId: atn.t })
                    ]);
                    call.addEventMsg('msg_s2c/ItemChange', atn.t, { num: 0 });
                    addGameLog(call.uid, "_itemChange", { "delitem": 1 }, { "itemId": atn.t })
                } else {
                    await Promise.all([
                        G.mongodb.collection('item').updateOne(upObj.filter, upObj.update, upObj.options)
                    ]);
                    call.addEventMsg('msg_s2c/ItemChange', atn.t, {
                        num: item.num + atn.n,
                        lastTime: upObj.update.$set.lastTime
                    });
                    addGameLog(call.uid, "_itemChange", { "attritem": 1 }, {
                        "filter": upObj.filter,
                        "update": upObj.update,
                        "options": upObj.options,
                        newNum: item.num + atn.n
                    })
                }
            }
        }
    }

    /**
     * 添加装备
     */
    static async addEquip(call: call, val: atn[]) {
        let lshd: k_v<number> = {};
        let insertData: OptionalId<OptionalId<MongodbCollections['equip']>>[] = val.map(v => {
            return new Array(v.n).fill(1).map(v1 => {
                return {
                    lv: 0,
                    uid: call.uid,
                    star: 0,
                    wearaId: '',
                    equipId: v.t,
                    getTime: G.time,
                    adjustment: 0
                };
            });
        }).reduce((a, b) => a.concat(b));

        let result = await G.mongodb.collection('equip').insertMany(insertData);
        addGameLog(call.uid, "_addEquip", {}, insertData)

        insertData.forEach((v, key) => {
            let id = result.insertedIds[key].toHexString();
            let { _id, ...ops } = v;

            Wjjl.setVal(call.uid, `has_equip_color_${G.gc.equip[ops.equipId].colour}`, 1, false);

            call.addEventMsg('msg_s2c/EquipChange', id, {
                _id: id,
                ...ops
            });

            if (!lshd[v.equipId]) lshd[v.equipId] = 0;
            lshd[v.equipId]++;
        });

        G.mongodb.collection('playerInfo', 'lshd_equip').updateOne(
            {
                uid: call.uid,
                type: 'lshd_equip'
            },
            {
                $inc: lshd
            },
            {
                upsert: true
            }
        );

        call.addEventMsg('msg_s2c/LshdChange', 'equip', lshd);
    }

    /**
     * 删除装备
     */
    static async cutEquip(call: call, _idArr: string[]) {
        for (let _id of _idArr) {
            G.mongodb.collection('equip').deleteOne({ uid: call.uid, _id: new ObjectId(_id) });
            call.addEventMsg('msg_s2c/EquipChange', _id, { num: 0 });

            addGameLog(call.uid, "_cutEquip", {}, { _id: _id })
        }
    }

    /**
     * 添加英雄
     */
    static async addHero(call: call, val: atn[]) {
        let lshd: k_v<number> = {};
        let insertData: any[] = val.map(v => {
            return new Array(v.n).fill(1).map(v1 => {
                return {
                    lv: 1,
                    uid: call.uid,
                    jieji: 0,
                    heroId: v.t,
                    getTime: G.time,
                };
            });
        }).reduce((a, b) => a.concat(b));

        if (!insertData.length) return

        insertData.forEach(v => {
            v.zhanli = HeroShared.getHeroZhanLi(v, call.otherBuff);
        });

        let result = await G.mongodb.collection('hero').insertMany(insertData);
        addGameLog(call.uid, "_addHero", {}, insertData)

        for (let key = 0; key < insertData.length; key++) {
            let v = insertData[key]

            let id = result.insertedIds[key].toHexString();
            let { _id, ...ops } = v;

            call.addEventMsg('msg_s2c/HeroChange', id, {
                _id: id,
                ...ops
            });

            if (!lshd[v.heroId]) lshd[v.heroId] = 0;
            lshd[v.heroId]++;
        }

        G.mongodb.collection('playerInfo', 'lshd_hero').updateOne(
            {
                uid: call.uid,
                type: 'lshd_hero'
            },
            {
                $inc: lshd
            },
            {
                upsert: true
            }
        );

        call.addEventMsg('msg_s2c/LshdChange', 'hero', lshd);
    }

    /**
     * 删除英雄
     */
    static async cutHero(call: call, _idArr: string[]) {
        for (let _id of _idArr) {
            await HeroFun.delHero(call, _id);
            G.mongodb.collection('hero').deleteOne({ uid: call.uid, _id: new ObjectId(_id) });
            call.addEventMsg('msg_s2c/HeroChange', _id, { num: 0 });
            addGameLog(call.uid, "_cutHero", {}, { _id: _id })
        }
    }

    /**
     * 添加饰物
     */
    static async addShiwu(call: call, val: atn[]) {
        let insertData: OptionalId<MongodbCollections['shiwu']>[] = val.map(v => {
            return new Array(v.n).fill(1).map(v1 => {
                let shiwu: MongodbCollections['shiwu'] = {
                    uid: call.uid,
                    colour: v.colour,
                    wearId: '',
                    shiwuId: v.t,
                    jichu: ShiwuFun.randomJichu({ colour: v.colour, shiwuId: v.t }),
                    fujia: []
                };
                if (v.shiwuBuff) {
                    shiwu.jichu = v.shiwuBuff.jichu;
                    shiwu.fujia = v.shiwuBuff.fujia;
                    if (v.shiwuBuff.zhuanshu) shiwu.zhuanshu = v.shiwuBuff.zhuanshu;
                } else {
                    ShiwuFun.randomFujiaAll(shiwu);
                    ShiwuFun.randomZhuanshu(shiwu);
                }
                return shiwu;
            });
        }).reduce((a, b) => a.concat(b));

        let result = await G.mongodb.collection('shiwu').insertMany(insertData);
        addGameLog(call.uid, "_addShiWu", {}, insertData)

        insertData.forEach((v, key) => {
            let id = result.insertedIds[key].toHexString();
            let { _id, ...ops } = v;
            call.addEventMsg('msg_s2c/ShiwuChange', id, {
                _id: id,
                ...ops
            });
        });

        if (insertData.filter(v => v.colour == 5)) {
            md_redPoint_check(G.server.uid_connections[call.uid], 'peijian_colour_5');
        }

        return Object.values(result.insertedIds).map(v => G.mongodb.conversionId(v));
    }

    /**
     * 删除饰物
     */
    static async cutShiwu(call: call, _idArr: string[]) {
        for (let _id of _idArr) {
            G.mongodb.collection('shiwu').deleteOne({ uid: call.uid, _id: new ObjectId(_id) });
            call.addEventMsg('msg_s2c/ShiwuChange', _id, { num: 0 });
            addGameLog(call.uid, "_cutShiwu", {}, { _id: _id })
        }
    }

    /**
     * 添加配件
     */
    static async addPeijian(call: call, val: atn[]) {
        let lshd: k_v<number> = {};
        let insertData: OptionalId<CollectionPeiJian>[] = val.map(v => {
            return new Array(v.n).fill(1).map(v1 => {
                return {
                    lv: 1,
                    uid: call.uid,
                    wearId: '',
                    jinglian: 0,
                    peijianId: v.t.toString()
                };
            });
        }).reduce((a, b) => a.concat(b));

        let result = await G.mongodb.collection('peijian').insertMany(insertData);
        addGameLog(call.uid, "_addPeiJian", {}, insertData)

        insertData.forEach((v, key) => {
            let { _id, uid, ...ops } = v;
            let id = _id.toHexString();

            if (G.gc.peijian[v.peijianId].colour != 5) {
                if (!lshd[v.peijianId]) lshd[v.peijianId] = 0;
                lshd[v.peijianId]++;
            }

            G.redis.set('peijian', call.uid, id, { _id: id, ...ops });
            call.addEventMsg('msg_s2c/PeijianChange', id, { _id: id, ...ops });
        });

        G.mongodb.collection('playerInfo', 'lshd_peijian').updateOne(
            {
                uid: call.uid,
                type: 'lshd_peijian'
            },
            {
                $inc: lshd
            },
            {
                upsert: true
            }
        );

        call.addEventMsg('msg_s2c/LshdChange', 'peijian', lshd);

        return Object.values(result.insertedIds).map(v => G.mongodb.conversionId(v));
    }

    /**
     * 添加皮肤
     * */
    static async addHeroskin(call: call, val: atn[]) {
        let prize = [];
        let upskin = {};
        let heroskin = HeroSkinFun.getHeroSkin(call);
        for (let p of val) {
            if (!heroskin[p.t]) {
                upskin[p.t] = 1;
            } else {
                prize = prize.concat(G.gc.heroSkin[p.t].zhuanhuan);
            }
        }

        if (Object.keys(upskin).length > 0) {
            await this.addAttr(call, { heroskin: Object.assign(heroskin, upskin) });
        }

        // 皮肤存在转换为其他奖励
        if (prize.length > 0) {
            await this.sendPrize(call, prize);
        }
    }

    /**
     * 删除配件
     */
    static async cutPeijian(call: call, _idArr: string[]) {
        for (let _id of _idArr) {
            G.redis.del('peijian', call.uid, _id);
            G.mongodb.collection('peijian').deleteOne({ uid: call.uid, _id: new ObjectId(_id) });
            call.addEventMsg('msg_s2c/PeijianChange', _id, { num: 0 });
            addGameLog(call.uid, "_cutPeijian", {}, { _id: _id })
        }
    }

    /**
     * 获取playattr属性
     */
    static async getAttr(uid: string, where: { ctype: string; }) {
        let _w = where;
        Object.assign(_w, { uid: uid });
        const _res = await G.mongodb.collection('playattr').find(_w).toArray();
        _res.forEach(v => {
            if (v._id) {
                delete v['_id'];
            }
        });
        return _res;
    }

    /**
     * 获取单条attr数据
     */
    static async getAttrOne(uid: string, where: { ctype: string; }) {
        let _w = where;
        Object.assign(_w, { uid: uid });
        const _res = await G.mongodb.collection('playattr').findOne(_w);
        if (_res) {
            delete _res['_id'];
        }
        return _res;
    }

    /**
     * 取出 当天 符合条件的数据
     */
    static async getAttrByDate(uid: string, where: { ctype: string; }, time = 0) {
        if (time == 0) {
            time = G.time;
        }
        let _zeroTime = PublicShared.getToDayZeroTime(time);
        let _tmp = { lasttime: { $gte: _zeroTime, $lte: _zeroTime + 24 * 60 * 60 - 1 } };

        let _w = where;
        Object.assign(_w, { uid: uid, ..._tmp });
        const _res = await G.mongodb.collection('playattr').find(_w).toArray();
        _res.forEach(v => {
            if (v._id) {
                delete v['_id'];
            }
        });
        return _res;
    }

    /**
     * 设置playAttr属性
     */
    static async setAttr(uid: string, where: { ctype: string; }, data: {}, islasttime = 1) {
        let _w = where;
        Object.assign(_w, { uid: uid });

        if (islasttime == 1) {
            data["lasttime"] = G.time;
        }

        let _exists = await this.getAttrOne(uid, _w);
        if (!_exists) {
            // 加入创建数据时间
            data["ctime"] = G.time;
        }
        let _res = await G.mongodb.collection('playattr').updateMany(_w, { $set: data }, { upsert: true });
        return _res;
    }
}