
import { Application, BackendSession, pinus, HandlerService, } from 'pinus';
import { isArray, pick } from 'underscore';
import { gameData } from '../../../pubUtils/data';
import { STATUS } from '../../../consts/statusCode';
import { arrToMap, resResult } from '../../../pubUtils/util';
import { getTimeFunM } from '../../../pubUtils/timeUtil';
import { LadderMatchModel, LadderUpdateInter } from '../../../db/LadderMatch';
import { battleEndWhenChange, checkRank, generateInitRecInfo, generateOppPlayers, getBuyCntCost, getLadderData, getLadderEnemies, getLadderOppDetailData, getLadderOppStatus, getNumberArr, ladderBattleEndReward, refreshLadderDaily, refreshLadderEnemies, sendLadderDailyReward, uniqueArr } from '../../../services/ladderService';
import { LadderDataReturn, LadderDefense, LadderDefenseHero, LadderOppDetailReturn, LadderOppLineupReturn, LadderOppPlayerHeroInfo, LadderOppPlayerReturn } from '../../../domain/battleField/ladder';
import { LadderMatchRecModel } from '../../../db/LadderMatchRec';
import { HeroModel } from '../../../db/Hero';
import { LADDER } from '../../../pubUtils/dicParam';
import { handleCost } from '../../../services/role/rewardService';
import { DEBUG_MAGIC_WORD, ITEM_CHANGE_REASON, LADDER_OPP_STATUS, LADDER_STATUS, REDIS_KEY, WAR_TYPE } from '../../../consts';
import { checkBattleHeroesByHid } from '../../../services/normalBattleService';
import { ServerlistModel } from '../../../db/Serverlist';
import { saveLadderDefCeByData } from '../../../services/redisService';
import { pushLadderTopChangeMsg } from '../../../services/sysChatService';
import { checkTaskInLadderEnd, checkTaskInLadderStart, checkTaskInLadderSweep } from '../../../services/task/taskService';
import { BattleRecordModel } from '../../../db/BattleRecord';
import { isHeroHidden } from '../../../services/dataService';
import { getRemoteRplFilePath, getRemoteRplPrefix } from '../../../pubUtils/battleUtils';

export default function (app: Application) {
    new HandlerService(app, {});
    return new LadderHandler(app);
}

export class LadderHandler {
    constructor(private app: Application) {
    }

    // 1. 获取主界面
    async getData(msg: {}, session: BackendSession) {
        let roleId = session.get('roleId');
 
        let result = await getLadderData(roleId, true)
        return resResult(STATUS.SUCCESS, result);
    }

    // 2. 获取对手阵容信息
    async getOppLineup(msg: { roleId: string, rank: number }, session: BackendSession) {
        let roleId: string = session.get('roleId');
        let { roleId: targetRoleId, rank } = msg;

        let ladderData = await LadderMatchModel.findByRoleId(roleId);
        if(!ladderData || !ladderData.defense) return resResult(STATUS.LADDER_NOT_OPEN);

        let isRobot = targetRoleId.startsWith('robot');
        let result = new LadderOppLineupReturn();
        result.setMyRank(ladderData.rank);
        if(isRobot) {
            let dicLadderDifficultRatio = gameData.ladderDifficultRatio.get(rank);
            let dicWar = gameData.war.get(dicLadderDifficultRatio.gkId);
            let dicWarJson = gameData.warJson.get(dicWar.dispatchJsonId);
            result.setRobot(dicLadderDifficultRatio, dicWarJson);
        } else {
            let hisLadderData = await LadderMatchModel.findByRoleIdAndInclude(targetRoleId);
            result.setPlayer(hisLadderData);
        }
        return resResult(STATUS.SUCCESS, result);
    }

    // 3. 点击挑战
    async chooseOpp(msg: { roleId: string, rank: number, myRank: number }, session: BackendSession) {
        let roleId = session.get('roleId');
        let serverId = session.get('serverId');
        let sid = session.get('sid');
        let { roleId: targetRoleId, rank, myRank } = msg;
        // 检查双方排名等，不行的刷新对手回去
        let ladderData = await LadderMatchModel.findByRoleId(roleId);
        if(!ladderData || !ladderData.defense) return resResult(STATUS.LADDER_NOT_OPEN);
        
        if(!checkRank(myRank, rank)) return resResult(STATUS.LADDER_RANK_ERROR);
        let { status, isRobot, hisLadderData } = await getLadderOppStatus(ladderData, targetRoleId, myRank, rank);
        if(status != LADDER_OPP_STATUS.BATTLE) {
            // await LadderMatchModel.unlock(serverId, targetRoleId);
            let oppPlayers = await refreshLadderEnemies(ladderData);
            return resResult(STATUS.SUCCESS, {
                status, oppPlayers
            });
        }

        // 刷新次数
        let refOppObj = refreshLadderDaily(ladderData);
        let update: LadderUpdateInter = {};
        if(refOppObj.shouldRefOpp) {
            update = { ...refOppObj };
        }

        if(refOppObj.challengeCnt + 1 > LADDER.LADDER_CHALLENGE_FREE_TIMES + refOppObj.buyCnt) {
            await LadderMatchModel.unlock(serverId, targetRoleId);
            return resResult(STATUS.LADDER_CHALLENGE_CNT_MAX);
        }
        update.challengeCnt = refOppObj.challengeCnt + 1;
        ladderData = await LadderMatchModel.updateByRoleIdAndInclude(roleId, update);


        // 创建ladderMatchRec，发行battleCode
        let attackInfo = await generateInitRecInfo(false, false, ladderData.rank, ladderData);
        let defenseInfo = await generateInitRecInfo(isRobot, true, rank, hisLadderData);
        let rec = await LadderMatchRecModel.createRec(serverId, roleId, targetRoleId, hisLadderData?.defense, attackInfo, defenseInfo);

        // 倒计时，倒计时结束没有check设为失败并发失败通知
        pinus.app.rpc.systimer.systimerRemote.setLadderCountDown.broadcast(rec.battleCode, rec.checkTime, LADDER_STATUS.CHECK);

        let result = await getLadderOppDetailData(rec);
        await checkTaskInLadderStart(serverId, roleId, sid);
        return resResult(STATUS.SUCCESS, {
            status,
            challengeCnt: ladderData.challengeCnt,
            ...result
        });
    }

    // 4. 出战撤退
    async giveupCheck(msg: { battleCode: string }, session: BackendSession) {
        let roleId = session.get('roleId');
        let { battleCode } = msg;
        // 更新ladderMatchRec
        let rec = await LadderMatchRecModel.giveup(roleId, battleCode);
        if(rec) {
            pinus.app.rpc.systimer.systimerRemote.cancelLadderCountDown.broadcast(rec.battleCode);
            if(rec.defenseInfo && !rec.defenseInfo.isRobot) await LadderMatchModel.unlock(rec.serverId, rec.defenseInfo.roleId);
        }

        return resResult(STATUS.SUCCESS, { status: 0, time: 0 });
    }

    // 5. 获取对手具体战场数据
    async getOppData(msg: { battleCode: string }, session: BackendSession) {
        let roleId = session.get('roleId');
        let { battleCode } = msg;

        let rec = await LadderMatchRecModel.findByBattleCode(battleCode);
        if(!rec || rec.roleId1 != roleId) return resResult(STATUS.LADDER_REC_NOT_FOUND);
        if(rec.status == LADDER_STATUS.COMPLETE || rec.status == LADDER_STATUS.NO) return resResult(STATUS.LADDER_REC_STATUS_IS_COMPLETE);

        let result = await getLadderOppDetailData(rec);
        return resResult(STATUS.SUCCESS, result);
    }

    // 6. 布完阵开始挑战
    async checkBattle(msg: { battleCode: string, battleId: number, heroes: { actorId: number, order: number }[] }, session: BackendSession) {
        let roleId = session.get('roleId');
        let roleName = session.get('roleId');
        let { battleId, battleCode, heroes } = msg;

        let rec = await LadderMatchRecModel.findByBattleCode(battleCode);
        if(!rec || rec.roleId1 != roleId) return resResult(STATUS.LADDER_REC_NOT_FOUND);
        if(rec.status != LADDER_STATUS.CHECK) return resResult(STATUS.LADDER_REC_STATUS_ERR);

        for(let { actorId } of heroes) {
            if(isHeroHidden(actorId)) return resResult(STATUS.HERO_IS_HIDDEN);
        }
        let { isOK, heroes: dbHeroes } = await checkBattleHeroesByHid(roleId, heroes.map(cur => cur.actorId));
        if(!isOK) return resResult(STATUS.BATTLE_HERO_NOT_FOUND);

        let attackHeroes = dbHeroes.map(hero => {
            let heroInfo = new LadderOppPlayerHeroInfo();
            heroInfo.setByDefenseHero(hero);
            return heroInfo;
        });
        let warInfo = gameData.war.get(battleId);
        await BattleRecordModel.updateBattleRecordByCode(battleCode, {
            $set: {
                roleId, roleName, battleId,
                status: 0,
                warName: warInfo?.gk_name,
                warType: warInfo?.warType,
            }
        });
        rec = await LadderMatchRecModel.startBattle(battleCode, attackHeroes);
        pinus.app.rpc.systimer.systimerRemote.setLadderCountDown.broadcast(rec.battleCode, rec.battleTime, LADDER_STATUS.BATTLE);

        return resResult(STATUS.SUCCESS, {
            time: Math.floor(rec.battleTime/1000) + LADDER.LADDER_BATTLE_COUNTDOWN,
            battleCode: rec.battleCode
        })
    }

    // 7. 挑战结算
    async battleEnd(msg: { battleCode: string, isSuccess: boolean }, session: BackendSession) {
        let roleId = session.get('roleId');
        let roleName = session.get('roleName');
        let serverId = session.get('serverId');
        let sid = session.get('sid');
        let { battleCode, isSuccess } = msg;

        let rec = await LadderMatchRecModel.findByBattleCode(battleCode);
        if(!rec || rec.roleId1 != roleId) return resResult(STATUS.LADDER_REC_NOT_FOUND);
        if(rec.status != LADDER_STATUS.BATTLE) return resResult(STATUS.LADDER_REC_STATUS_ERR);
        let rank = rec.defenseInfo.oldRank;

        let ladderData = await LadderMatchModel.findByRoleId(roleId);
        if(!ladderData || !ladderData.defense) return resResult(STATUS.LADDER_NOT_OPEN);

        // 取消 checkBattle 的倒计时
        pinus.app.rpc.systimer.systimerRemote.cancelLadderCountDown.broadcast(battleCode);

        let result = new LadderDataReturn();
        // 交换双方排名， transaction
        let { isChange, atkLadderMatch, defLadderMatch, endTime } = await LadderMatchModel.changeRank(isSuccess, rec.attackInfo, rec.defenseInfo);

        if(isChange) {
            let oppPlayers = await battleEndWhenChange(atkLadderMatch, defLadderMatch, true);
            result.setOppPlayers(oppPlayers);
            rec = await LadderMatchRecModel.battleEnd(battleCode, isSuccess, endTime, atkLadderMatch.rank, defLadderMatch? defLadderMatch.rank: rec.attackInfo.oldRank);
            if(atkLadderMatch.rank == 1) {
                pushLadderTopChangeMsg(roleId, roleName, serverId);
            }
        } else {
            rec = await LadderMatchRecModel.battleEnd(battleCode, isSuccess, endTime);
            let oppPlayers = await getLadderEnemies(atkLadderMatch);
            result.setOppPlayers(oppPlayers);
        }
        await BattleRecordModel.updateBattleRecordByCode(battleCode, { $set: { status: isSuccess? 1: 2 } });
        if(defLadderMatch) {
            await LadderMatchModel.unlock(defLadderMatch.serverId, defLadderMatch.roleId);
        }

        let historyRank = ladderData.historyRank;
        ladderData = await LadderMatchModel.updateByRoleId(roleId, { historyRank: (historyRank > atkLadderMatch.rank || historyRank == 0)? atkLadderMatch.rank: historyRank });

        result.setLadderData(ladderData, rec);
        // 获取奖励
        let rewardResult = await ladderBattleEndReward(roleId, roleName, sid, ladderData.historyRank, isSuccess, historyRank, 1);
        let { battleGoods = [], breakGoods = [] } = rewardResult||{};

        await checkTaskInLadderEnd(serverId, roleId, sid, isSuccess, historyRank, atkLadderMatch.rank);
        return resResult(STATUS.SUCCESS, {...pick(result, ['rank', 'historyRank', 'challengeCnt','status', 'time', 'oppPlayers']), battleGoods, breakGoods});
    }

    // 8. 战5次（扫荡）
    async battleSweep(msg: { roleId: string, rank: number, count: number }, session: BackendSession) {
        let serverId = session.get('serverId');
        let roleId = session.get('roleId');
        let roleName = session.get('roleName');
        let sid = session.get('sid');
        let { roleId: targetRoleId, rank, count } = msg;
        if(count > LADDER.LADDER_INITIAL_CHALLENGE_TIMES) {
            return resResult(STATUS.LADDER_SWEEP_TIMES_OVER);
        }

        let ladderData = await LadderMatchModel.findByRoleId(roleId);
        if(!ladderData || !ladderData.defense) return resResult(STATUS.LADDER_NOT_OPEN);

        if(ladderData.rank >= rank) return resResult(STATUS.LADDER_RANK_ERR);

        // 刷新次数
        let refOppObj = refreshLadderDaily(ladderData);
        let update: LadderUpdateInter = {};
        if(refOppObj.shouldRefOpp) {
            update = { ...refOppObj };
        }

        if(refOppObj.challengeCnt + count > LADDER.LADDER_CHALLENGE_FREE_TIMES + refOppObj.buyCnt) {
            return resResult(STATUS.LADDER_CHALLENGE_CNT_MAX);
        }
        update.challengeCnt = refOppObj.challengeCnt + count;
        ladderData = await LadderMatchModel.updateByRoleIdAndInclude(roleId, update);

        // 获取奖励
        let { battleGoods } = await ladderBattleEndReward(roleId, roleName, sid, rank, true, rank, count);
        await checkTaskInLadderSweep(serverId, roleId, sid, count)
        return resResult(STATUS.SUCCESS, {
            challengeCnt: ladderData.challengeCnt,
            battleGoods
        });
    }

    // 9. 设置防守阵容
    async saveDefense(msg: { warId: number, heroes: { actorId: number, ai: number, dataId: number, order: number }[] }, session: BackendSession) {
        let { warId, heroes } = msg;
        let roleId = session.get('roleId');

        let ladderData = await LadderMatchModel.findByRoleId(roleId);
        if(!ladderData) return resResult(STATUS.LADDER_NOT_OPEN);

        let hids: number[] = heroes.map(cur => cur.actorId);
        if(isHeroHidden(...hids)) return resResult(STATUS.HERO_IS_HIDDEN);
        let dbHeroes = await HeroModel.findByHidRange(hids, roleId, '_id hid ce', true);

        let defenseHeroes = heroes.map(cur => {
            let dbHero = dbHeroes.find(ccur => ccur.hid == cur.actorId);
            return dbHero && new LadderDefenseHero(cur, dbHero._id, dbHero.ce);
        }).filter(cur => cur);
        let defense = new LadderDefense(defenseHeroes, warId);
        ladderData = await LadderMatchModel.updateByRoleId(roleId, { defense, hasDefense: true });
        // 返回
        let result = new LadderDataReturn();
        result.setLadderData(ladderData);
        await saveLadderDefCeByData(roleId, ladderData);

        return resResult(STATUS.SUCCESS, pick(result, 'defense'));
    }

    // 10. 刷新对手
    async refreshOppPlayers(msg: {}, session: BackendSession) {
        let roleId = session.get('roleId');
        let sid = session.get('sid');

        let ladderData = await LadderMatchModel.findByRoleId(roleId);
        if(!ladderData || !ladderData.defense) return resResult(STATUS.LADDER_NOT_OPEN);

        let update: LadderUpdateInter = {};
        // 刷新次数
        let refOppObj = refreshLadderDaily(ladderData);
        if(refOppObj.shouldRefOpp) {
            update = { ...refOppObj };
        }

        if(refOppObj.refOppCnt + 1 > LADDER.LADDER_REFRESH_TIMES) {
            return resResult(STATUS.LADDER_REFRESH_CNT_MAX);
        }
        update.refOppCnt = refOppObj.refOppCnt + 1;
        let oppPlayers = await refreshLadderEnemies(ladderData, update);

        // 返回
        return resResult(STATUS.SUCCESS, { refOppCnt: update.refOppCnt, oppPlayers });
    }

    // 11. 购买次数
    async buyCnt(msg: { count: number }, session: BackendSession) {
        let { count } = msg;
        let roleId = session.get('roleId');
        let sid = session.get('sid');

        let ladderData = await LadderMatchModel.findByRoleId(roleId);
        if(!ladderData) return resResult(STATUS.LADDER_NOT_OPEN);

        let update: LadderUpdateInter = {};
        // 刷新次数
        let refOppObj = refreshLadderDaily(ladderData);
        if(refOppObj.shouldRefOpp) {
            update = { ...refOppObj };
        }

        if(refOppObj.buyCnt + count > LADDER.LADDER_CHALLENGE_COST_TIMES) {
            return resResult(STATUS.LADDER_BUY_MAX);
        }
        let consumes = getBuyCntCost(refOppObj.buyCnt, count);
        if(!consumes) return resResult(STATUS.LADDER_BUY_MAX);

        let consumeResult = await handleCost(roleId, sid, consumes||[], ITEM_CHANGE_REASON.LADDER_BUY_CNT);
        if(!consumeResult) return resResult(STATUS.ROLE_MATERIAL_NOT_ENOUGH);

        update.buyCnt = refOppObj.buyCnt + count;
        ladderData = await LadderMatchModel.updateByRoleId(roleId, update);
        // 返回
        let result = new LadderDataReturn();
        result.setLadderData(ladderData);
        return resResult(STATUS.SUCCESS, pick(result, ['buyCnt', 'challengeCnt']));
    }

    // 12. 获取战报信息（2天内）
    async getRec(msg: {}, session: BackendSession) {
        let roleId = session.get('roleId');
        let list = await LadderMatchRecModel.findRecentRec(roleId, <number>getTimeFunM().getBeforeDay(2));
        return resResult(STATUS.SUCCESS, {
            list: list.map(rec => {
                const { roleId1, battleCode, hasRpl } = rec;
                const rplFileUrl = battleCode && hasRpl ? getRemoteRplFilePath(roleId1, WAR_TYPE.LADDER, battleCode) : '';
                return { ...rec, rplFileUrl };
            }),
            rplPrefixUrl: getRemoteRplPrefix(pinus.app.get('env'))
        });
    }

    // debug接口
    // 重置挑战次数 & 购买次数
    async resetCnt(msg: { magicWord: string }, session: BackendSession) {
        const { magicWord } = msg;
        if (magicWord !== DEBUG_MAGIC_WORD) {
            return resResult(STATUS.TOKEN_ERR);
        }

        let roleId = session.get('roleId');
        let ladderData = await LadderMatchModel.findByRoleId(roleId);
        if(!ladderData) return resResult(STATUS.LADDER_NOT_OPEN);
        await LadderMatchModel.updateByRoleId(roleId, { refDaily: new Date(Date.now() - 86400000) });
        return resResult(STATUS.SUCCESS);
    }

    async setMyRank(msg: { magicWord: string, rank: number }, session: BackendSession) {
        const { magicWord, rank } = msg;
        if (magicWord !== DEBUG_MAGIC_WORD) {
            return resResult(STATUS.TOKEN_ERR);
        }

        let roleId = session.get('roleId');
        let serverId = session.get('serverId');

        let atkLadderMatch = await LadderMatchModel.findByRoleId(roleId);
        if(!atkLadderMatch || !atkLadderMatch.defense) return resResult(STATUS.LADDER_NOT_OPEN);
        let myRank = atkLadderMatch.rank;

        let defLadderMatch = await LadderMatchModel.findByRank(serverId, rank);
        if(defLadderMatch) {
            atkLadderMatch = await LadderMatchModel.findOneAndUpdate({ roleId }, { $set: { rank, oldRank: myRank, historyRank: rank } }, { new: true }).lean();
            defLadderMatch = await LadderMatchModel.findOneAndUpdate({ roleId: defLadderMatch.roleId }, { $set: { rank: myRank, oldRank: rank } }, { new: true }).lean();
        } else {                    
            atkLadderMatch = await LadderMatchModel.findOneAndUpdate({ roleId }, { $set: { rank, oldRank: atkLadderMatch.rank, historyRank: rank } }, { new: true }).lean();
        }

        await battleEndWhenChange(atkLadderMatch, defLadderMatch, true);
        return resResult(STATUS.SUCCESS);
    }

    async setMyOpp(msg: { magicWord: string, oppRanks: number[] }, session: BackendSession) {
        const { magicWord, oppRanks } = msg;
        if (magicWord !== DEBUG_MAGIC_WORD) {
            return resResult(STATUS.WRONG_PARMS);
        }
        if(!isArray(oppRanks) || oppRanks.length <= 0) return resResult(STATUS.WRONG_PARMS);

        let roleId = session.get('roleId');
        let serverId = session.get('serverId');
        let ladderData = await LadderMatchModel.findByRoleId(roleId);
        if(!ladderData) return resResult(STATUS.LADDER_NOT_OPEN);
        
        let topTen = getNumberArr(1, 10);
        let ranks = [...topTen, ...oppRanks];
        let newRanks = uniqueArr(ranks.sort((a, b) => a - b));    

        await generateOppPlayers(newRanks, serverId, roleId);
        return resResult(STATUS.SUCCESS);
    }

    async sendDailyReward(msg: { magicWord: string }, session: BackendSession) {
        const { magicWord } = msg;
        if (magicWord !== DEBUG_MAGIC_WORD) {
            return resResult(STATUS.TOKEN_ERR);
        }
        let servers = await ServerlistModel.findByEnv(pinus.app.get('env'));
        for(let { id } of servers) {
            await sendLadderDailyReward(id);
        }
        return resResult(STATUS.SUCCESS)
    }
}