import {ApiCall} from 'tsrpc';
import {Wjjl} from '../module/collection_wjjl';
import {formatNpcData} from '../shared/fightControl/fightFun';
import {fightResult} from '../shared/fightControl/fightType';
import {hongdianVal} from '../shared/protocols/hongdian/PtlGet';
import {jjcChange} from '../shared/protocols/jjc/PtlOpen';
import {rankInfo} from '../shared/protocols/type';
import {PublicShared} from '../shared/public/public';
import {Queue} from '../shared/public/queue';
import {FightFun} from './fight';
import {UserFun} from './user';
import {sortEd} from '../module/redis'

const jjcQueue = new Queue();

/**++++++++++++++++++特别注意：redis内的排名数据"rank:jjc:sort"数组值均为唯一，不要手动修改为重复值，否则排名计算逻辑会不准确+++++++++++++++++++++++++ */
/**++++++++++++++++++mongodb rank表的update与insert对性能有影响时可删除，保留只为尽量保持原有逻辑的数据逻辑不改变*/
export class JJCFun {
    /**当玩家挑战或者被挑战时不可被其他玩家挑战 */
    static lockPlayer: k_v<boolean> = {};

    static async getMyData(uid: string) {
        let data = await G.mongodb.collection('jjc').findOne(
            {uid: uid}
        );
        if (!data) {
            data = (await G.mongodb.collection('jjc').findOneAndUpdate(
                {uid: uid}, {
                    $set: {resetTime: G.time, receivedArr: [], buyFightNum: 0, useFightNum: 0, recordWin: 0}
                }, {upsert: true, returnDocument: 'after'}
            )).value
        }
        return data
    }

    // 设置挑战状态
    static async setLockPlayer(uid, boolean) {
        return boolean ? await G.redis.rawSet(`rank:jjc:lockPlayer:${uid}`, "1", {EX: 10}) : await G.redis.rawDel(`rank:jjc:lockPlayer:${uid}`);
    }

    // 获取挑战状态
    static async getLockPlayer(uid) {
        return await G.redis.rawGet(`rank:jjc:lockPlayer:${uid}`);
    }

    /**排名缓存 */
        // static rankList: rankInfo[];
    static uTimeOffset: number = 60;

    /**
     * 获取指定条数的rankList
     * @param min
     * @param max
     * @param isUpdate 是否更新数据
     */
    static async getRankList(min: number = 0, max: number = 50, isUpdate: boolean = true) {
        // 获取指定排名用户uid
        let sortInfo = await this.getRankListUid(min, max)
        if (!sortInfo.length) return []
        // 获取用户详细数据
        let rankList = []
        let updateArr = []
        for (let i = 0; i < sortInfo.length; i++) {
            let uid = sortInfo[i]
            let rankInfo = await G.redis.hGet('rank:jjc:data', uid)
            if (!rankInfo?.player) continue
            rankList.push(rankInfo)
            // 比对utime，判断是否更新数据
            if (!rankInfo.player.isNpc) {
                if (!rankInfo.utime || (rankInfo.utime && rankInfo.utime < (G.time - this.uTimeOffset))) {
                    updateArr.push(uid)
                }
            }
            //if (!rankInfo.player.isNpc && rankInfo.utime && rankInfo.utime < (G.time - this.uTimeOffset)) updateArr.push(uid)
        }
        // 更新数据
        if (isUpdate && updateArr.length > 0) {
            let playerArrInfo = await G.mongodb.collection("user").find({uid: {$in: updateArr}}).toArray()
            for (let i = 0; i < playerArrInfo.length; i++) {
                let playerInfo = playerArrInfo[i]
                let index = rankList.findIndex(x => x.player.uid == playerInfo.uid)
                rankList[index].player = playerInfo
                rankList[index].utime = G.time
                this.updatePlayerData(playerInfo.uid, rankList[index]);
            }
        }
        return rankList
    }

    /**
     * 每10分钟刷新前一千名玩家的用户数据
     * @param min
     * @param max
     * @param uTimeOffset
     * @param isUpdate 是否更新数据
     */
    static async checkUpdatePlayer(min: number = 0, max: number = 1000, uTimeOffset: number = 600, isUpdate: boolean = true) {
        // 获取指定排名用户uid
        let sortInfo = await this.getRankListUid(min, max)
        if (!sortInfo.length) return []
        let updateArr = []

        let users = await G.redis.hGetAll('rank:jjc:data')

        for (let i = 0; i < sortInfo.length; i++) {
            let uid = sortInfo[i]
            let rankInfo = users[uid]
            if (!rankInfo?.player) continue
            // 比对utime，判断是否更新数据
            if (!rankInfo.player.isNpc) {
                if (!rankInfo.utime || (rankInfo.utime && rankInfo.utime < (G.time - uTimeOffset))) {
                    updateArr.push(rankInfo)
                }
            }
        }
        // 更新数据
        if (isUpdate && updateArr.length > 0) {
            let playerArrInfo = await G.mongodb.collection("user").find({uid: {$in: updateArr.map(i => i.uid)}}).toArray()
            for (let i = 0; i < playerArrInfo.length; i++) {
                let playerInfo = playerArrInfo[i]
                let index = updateArr.findIndex(x => x.player.uid == playerInfo.uid)
                updateArr[index].player = playerInfo
                updateArr[index].utime = G.time
                this.updatePlayerData(playerInfo.uid, updateArr[index]);
            }
        }
    }

    /**
     * 获取指定范围排名的用户，仅返回uid[]
     * @param min
     * @param max
     * @return uid[]
     */
    static async getRankListUid(min: number = 0, max: number = 50) {
        let sortInfo = await G.redis.zRange('rank:jjc:sort', min, max - 1)
        return sortInfo
    }

    /**
     * 获取玩家排名
     * @param uid
     * @return number
     */
    static async getRankSortByUid(uid: string): Promise<number> {
        let rank = await G.redis.zRank('rank:jjc:sort', uid) // 原逻辑索引未+1，此处返回索引。
        return rank === 0 || rank > 0 ? rank : -1
    }

    /**
     * 获取指定玩家数据
     * @param uid
     * @return <rankInfo>
     */
    static async getPlayerData(uid: string): Promise<rankInfo> {
        let rankInfo = await G.redis.hGet('rank:jjc:data', uid) // 原逻辑索引未+1，此处返回索引。
        if (!rankInfo || (!rankInfo.player.isNpc && rankInfo.utime && rankInfo.utime < (G.time - this.uTimeOffset))) {
            // 新玩家数据，更新
            if (!rankInfo) {
                this.addNewPlayerSort(uid)
                rankInfo = <rankInfo>{}
            }
            rankInfo.player = await G.mongodb.collection("user").findOne({uid: uid})
            rankInfo.utime = G.time
            this.updatePlayerData(uid, rankInfo)
        }
        return rankInfo
    }

    /**
     * 获取指定排名玩家数据
     * @param rank
     * @return <rankInfo>
     */
    static async getRankPlayerData(rank: number): Promise<rankInfo> {
        // 函数内max存在-1操作，查自己需保持min=max
        let rankList = await this.getRankList(rank, rank + 1)
        let rankInfo = rankList[0]
        return rankInfo || {}
    }

    // 写入新玩家排名
    static async addNewPlayerSort(uid: string) {
        let rankLen = await G.redis.zCard('rank:jjc:sort'); // 获取排名的总条数，确认新玩家的排名值（ps：总条数既新玩家排名，不需+1）
        this.updatePlayerRank(<sortEd>{value: uid, score: rankLen})
    }

    /**更新玩家排名数据 */
    static updatePlayerRank(sortEd: sortEd | sortEd[]) {
        G.redis.zAdd('rank:jjc:sort', sortEd)
        return
    }

    /**更新玩家数据 */
    static updatePlayerData(uid: string, rankInfo: rankInfo) {
        G.redis.hSet('rank:jjc:data', uid, rankInfo)
        return
    }

    /**新玩家第一次进入或者排名变更 */
    static async addRankInfo(rankInfo: rankInfo, changeInfo?: rankInfo) {
        // jjcQueue.enqueue(async () => {
        if (!changeInfo) {
            // 空数据预防
            if (!rankInfo) return
            // redis写入新玩家数据
            this.updatePlayerData(rankInfo.player.uid, rankInfo)
            // redis写入新玩家排名
            this.addNewPlayerSort(rankInfo.player.uid)
            let rankLen = await G.redis.zCard('rank:jjc:sort'); // 获取排名的总条数，确认新玩家的排名值（ps：总条数既新玩家排名，不需+1）
            // this.updatePlayerRank(<sortEd>{value: rankInfo.player.uid, score: rankLen})
            Wjjl.setVal(rankInfo.player.uid, 'jjc_rank', rankLen);
            // 
            G.mongodb.collection('rank').updateOne({type: 'jjc'}, {$push: {rankList: rankInfo}});
        } else {
            // 获取自己与对手的排名索引
            const myIndex = await G.redis.zScore('rank:jjc:sort', rankInfo.player.uid)
            const heIndex = await G.redis.zScore('rank:jjc:sort', changeInfo.player.uid)

            if (myIndex <= heIndex) return;

            Wjjl.setVal(rankInfo.player.uid, 'jjc_rank', heIndex + 1);
            UserFun.activeHeadFrame(rankInfo.player.uid, 'jjc_rank', heIndex + 1);
            // 交换更新排名索引值，无需更新数据
            this.updatePlayerRank([
                <sortEd>{value: rankInfo.player.uid, score: heIndex},
                <sortEd>{value: changeInfo.player.uid, score: myIndex}
            ])
            let change = G.mongodb.createTreeObj({
                key: `rankList.${heIndex}`,
                val: rankInfo
            }, {key: `rankList.${myIndex}`, val: changeInfo});
            G.mongodb.collection('rank').updateOne({type: 'jjc'}, {$set: change});
        }
        // });

        return rankInfo;
    }


    /**刷新对手 */
    static async randomEnemy(uid: string) {
        const enemy: number[] = [];
        const curIndex = await this.getRankSortByUid(uid);
        if (curIndex == 0) enemy.push(1, 2, [3, 4].random());
        else if (curIndex == 1) enemy.push(0, [2, 3].random(), 4);
        else if (curIndex == 2) enemy.push(0, 1, [3, 4].random());
        else if (curIndex == 3) enemy.push(0, [1, 2].random(), 4);
        else if (curIndex == 4) enemy.push([0, 1].random(), 2, 3);
        else if (curIndex == 5) enemy.push(1, [2, 3].random(), 4);
        else if (curIndex == 6) enemy.push(2, [3, 4].random(), 5);
        else if (curIndex == 7) enemy.push(3, [4, 5].random(), 6);
        else if (curIndex == 8) enemy.push(4, [5, 6].random(), 7);
        else if (curIndex == 9) enemy.push(5, [6, 7].random(), 8);
        else {
            enemy.push(...[
                [Math.floor(curIndex * .7), Math.floor(curIndex * .8) - 1],
                [Math.floor(curIndex * .8), Math.floor(curIndex * .9) - 1],
                [Math.floor(curIndex * .9), curIndex - 1],
            ].map(arr => PublicShared.randomNum(arr[0], arr[1])));
        }
        let enemyRes = []
        for (let e of enemy) {
            if (e >= 0) {
                let rankInfo = await this.getRankPlayerData(e)
                let rank = await this.getRankSortByUid(rankInfo.player.uid)
                enemyRes.push({...rankInfo, rank: rank})
            }
        }
        return {
            rank: curIndex,
            enemy: enemyRes
        };
    }

    /**存录像 */
    static async saveFightLog(uid: string, fightLog: fightResult) {
        if (uid.indexOf('npc_') != -1) return;
        FightFun.saveLog(uid, 'jjc', fightLog);
    }

    /**服务器启动后初始化缓存数据 */
    static async init() {
        console.log('加载竞技场缓存...');
        // redis数据存在，不初始化, mongodb在此的数据可以剔除。
        // 或建立与redis"rank:jjc:sort"相对应的表,但走新表逻辑要考虑初始化的影响,暂缓。
        // 重新初始化一定要删除redis内数据
        if ((await this.getRankList()).length > 0) return
        let rankInfo = await G.mongodb.collection('rank').findOne({type: 'jjc'});
        if (!rankInfo) {
            rankInfo = {
                _id: null,
                type: 'jjc',
                rankList: Object.values(G.gc.jjc_npc).map(npc => {
                    let {id, npcId, ...attr} = npc;
                    return {
                        ...formatNpcData(npcId, {...attr, uid: id})
                    };
                })
            };
            G.mongodb.collection('rank').insertOne({type: 'jjc', rankList: rankInfo.rankList});
        }
        // 剔除空数据
        rankInfo.rankList = rankInfo.rankList.filter(x => x && x.player)
        // 获取全部用户uid
        let uidArr = rankInfo.rankList.map(x => x.player.uid)
        // 数组去重
        uidArr = [...new Set(uidArr)]
        // 整理写入redis的排名数据 
        let rank: sortEd[] = uidArr.map((uid, index) => ({value: uid, score: index}))
        this.updatePlayerRank(rank)
        // 记录ranklist详细数据到redis
        rankInfo.rankList && rankInfo.rankList.forEach(item => {
            this.updatePlayerData(item.player.uid, item)
        })
        console.log('加载竞技场缓存 succ');
    }

    /**获取玩家jjc数据 */
    static async getData(call: ApiCall) {
        let jjsPlayer = await this.getMyData(call.uid);
        if (jjsPlayer) {
            let rank = await this.getRankSortByUid(call.uid);
            let data = JSON.parse(JSON.stringify(jjsPlayer))
            data.rank = rank;
        }
        // 排名数据即时取
        return jjsPlayer;
    }

    /**修改玩家jjc数据 */
    static async changeData(call: ApiCall, change: jjcChange) {
        //console.log(change);
        // Object.entries(change).forEach(val => G.redis.set('jjc', call.uid, val[0] as any, val[1]));
        await G.mongodb.collection('jjc').updateOne({uid: call.uid}, {$set: change});
    }

    /**红点 */
    static async getHongDian(call: ApiCall) {
        let _res: hongdianVal = {
            show: false
        };
        // 挑战次数不足
        let _mydata = await this.getData(call) || {
            _id: null, uid: call.uid, ...{
                resetTime: G.time,
                receivedArr: [],
                buyFightNum: 0,
                useFightNum: 0
            }
        };
        if (_mydata.useFightNum < _mydata.buyFightNum + G.gc.jjc_com.fightNum) {
            _res.show = true;
        }

        if (!_res.show) {
            let _con = JSON.parse(JSON.stringify(G.gc.jjc_tz));
            for (let index = 0; index < Object.keys(_con).length; index++) {
                const element = Object.keys(_con)[index];
                let _tmp = _con[element];
                if (_mydata.receivedArr.find(r => r == _tmp.id)) continue;
                if (_mydata.useFightNum < _tmp.num) continue;
                _res.show = true;
                break;
            }
        }
        return _res;
    }
}