import { ITID, CONSUME_TYPE, ITEM_TABLE, REDIS_KEY, TASK_TYPE, CURRENCY, CURRENCY_TYPE, MAIL_TYPE, HANDLE_REWARD_TYPE, HERO_SYSTEM_TYPE, CURRENCY_BY_TYPE } from './../consts';
import { EquipModel, EquipType } from './../db/Equip';
import { getRandSingleEelm, resResult } from '../pubUtils/util';
import { RoleModel, RoleType } from '../db/Role';
import { setAp } from './actionPointService';
import { pushCalPlayerCe, pushCalAllHeroCe, calPlayerCeAndSave } from './playerCeService';
import { ItemModel, ItemType } from '../db/Item';
import { STATUS } from '../consts/statusCode';
import { pinus } from 'pinus';
import { addEquips, addBags, addSkin, addFigure, unlockFigure as pubUnlockFigure, transPiece, getGoldObject, getCoinObject, getApObject } from '../pubUtils/itemUtils';
import { ItemInter, RewardInter, } from '../pubUtils/interface';
import { gameData } from '../pubUtils/data';
import { uniq } from 'underscore';
import { HeroModel, HeroType, HeroUpdate } from '../db/Hero';
import { Figure } from '../domain/dbGeneral';
import { Rank } from './rankService';
import { checkActivityTask, checkTaskWithHero, pushActivityUpdate, pushTaskUpdate } from './taskService';
import { CreateHeroParam } from '../domain/roleField/hero';
import { HeroSkin } from '../db/Hero';
import { errlogger } from '../util/logger';
import { BAG } from '../pubUtils/dicParam';
import { sendMailByContent } from './mailService';
import { calEquipSeids } from '../pubUtils/playerCe';
import { CreateHeroes } from '../pubUtils/roleUtil';
import { SkinUpdate } from '../db/Skin';
import { getInitHeroById } from './roleService';
import { getActivities } from './activity/activityService';

export class CheckMeterial {
    private roleId: string;
    private itemsIndb: Map<number, number> = new Map(); // 玩家已有的东西
    private consumes: ItemInter[] = []; // 消耗，正向数

    private goldId = CURRENCY_BY_TYPE.get(CURRENCY_TYPE.GOLD);
    private coinId = CURRENCY_BY_TYPE.get(CURRENCY_TYPE.COIN);

    constructor(roleId: string, role?: RoleType, items?: ItemType[]) {
        this.roleId = roleId;
        if(role) {
            this.itemsIndb.set(this.goldId, role.gold);
            this.itemsIndb.set(this.coinId, role.coin);
        }
        if(items && items.length) {
            for(let {id, count} of items) {
                this.itemsIndb.set(id, count);
            }
        }
    }

    public async decrease(goods: {id: number, count: number}[]) {
        let { items, gold, coin } = sortItems(goods, HANDLE_REWARD_TYPE.COST);
        let isEnough = true;
        for(let { id, count} of items) {
            if(!this.itemsIndb.has(id)) {
                let item = await ItemModel.findbyRoleAndGidAndCount(this.roleId, id, count);
                if(!item) {
                    isEnough = false; break;
                }
                this.itemsIndb.set(id, item.count);
            }
            if(this.itemsIndb.get(id) < count) {
                isEnough = false; break;
            }
            this.itemsIndb.set(id, this.itemsIndb.get(id) - count);
        }

        if(gold.length > 0) {
            if(!this.itemsIndb.has(this.goldId)) {
                let role = await RoleModel.findByRoleId(this.roleId, 'gold coin');
                this.itemsIndb.set(this.goldId, role.gold);
                this.itemsIndb.set(this.coinId, role.coin);
            }
            let goldCost = gold.reduce((pre, cur) => { return pre + cur.count }, 0);
            if(this.itemsIndb.get(this.goldId) < goldCost) isEnough = false;
        }
        if(isEnough && coin.length > 0) {
            if(!this.itemsIndb.has(this.coinId)) {
                let role = await RoleModel.findByRoleId(this.roleId, 'gold coin');
                this.itemsIndb.set(this.goldId, role.gold);
                this.itemsIndb.set(this.coinId, role.coin);
            }
            let coinCost = coin.reduce((pre, cur) => pre + cur, 0);
            if(this.itemsIndb.get(this.coinId) < coinCost) isEnough = false;
        }

        if(isEnough) this.consumes.push(...goods);
        return isEnough;
    }

    public getConsume() {
        return this.consumes;
    }
}


export async function handleCost(roleId: string, sid: string, goods: Array<ItemInter>) {

    let uids = [{ uid: roleId, sid }];
    let { items, equips, gold, coin } = sortItems(goods, HANDLE_REWARD_TYPE.COST);
    let equipSeqIds = equips.map(cur => cur.seqId);
    let resEquips: EquipType[] = [];

    // 检查货币是否充足
    let role = await RoleModel.findByRoleId(roleId);
    if (gold.length > 0 || coin.length > 0) {
        let { gold: originGold, coin: originCoin } = role;
        for(let {count} of gold) { originGold -= count };
        for(let count of coin) { originCoin -= count };
        if(originGold < 0 || originCoin < 0) return false;
    }
    //检查装备是否存在
    if (equips.length > 0) {
        resEquips = await EquipModel.getEquips(roleId, equipSeqIds);
        if (resEquips.length < equips.length)
            return false;
    }
    //检查并修改道具
    if (items.length > 0) {
        let { hasError, result } = await ItemModel.decreaseItems(roleId, items);
        if (hasError) return false;
        pinus.app.get('channelService').pushMessageByUids('onItemUpdate', resResult(STATUS.SUCCESS, { goods: result }), uids);
    }

    //删除装备
    if (resEquips.length > 0) {
        let heroMap = new Map<number, { hero: HeroType, equips: EquipType[]}>();
        for(let equip of resEquips) {
            if(equip.hid > 0) {
                if(!heroMap.has(equip.hid))  {
                    let hero = await HeroModel.findByHidAndRoleWithEquip(equip.hid, roleId);
                    heroMap.set(equip.hid, { hero, equips: [] });
                }
                heroMap.get(equip.hid).equips.push(equip);
            }
        }
        for(let [_hid, {hero, equips} ] of heroMap) {
            let args = calEquipSeids(hero);
            for(let equip of equips) {
                hero = await HeroModel.removeEquip(roleId, hero.hid, equip.ePlaceId, equip._id);
            }
    
            await calPlayerCeAndSave(HERO_SYSTEM_TYPE.EQUIP, sid, roleId, hero, {}, args);
            await checkTaskWithHero(roleId, sid, TASK_TYPE.EQUIP_BY_HERO, hero, [-1]);
        }


        await EquipModel.deleteEquips(roleId, equipSeqIds);
        pinus.app.get('channelService').pushMessageByUids('onEquipDel', resResult(STATUS.SUCCESS, { equips: equipSeqIds }), uids);
    }

    //消耗玩家货币
    if (gold.length > 0 || coin.length > 0) {
        let costCoin = coin.reduce((pre, cur) => pre + cur, 0);
        role = await RoleModel.decreaseGoldAndCoin(roleId, gold, costCoin);
        pinus.app.get('channelService').pushMessageByUids('onPlayerDataChange', resResult(STATUS.SUCCESS, {
            gold: role.gold, coin: role.coin
        }), uids);
    }

    return true;
}

// TODO: sid 在方法内部获取，且不一定存在
export async function addItems(roleId: string, roleName: string, sid: string, goods: Array<ItemInter>) {
    let uids = [{ uid: roleId, sid }];
    let { items, equips, gold, coin, ap, skins, figures } = sortItems(goods, HANDLE_REWARD_TYPE.RECEIVE);
    let showItems: { id: number, seqId?: number, count: number, isBag?: boolean }[] = [];
    let role = await RoleModel.findByRoleId(roleId);
    // 1. 装备处理
    if(equips.length > 0) {
        let { equipCount = 0 } = role;
        let incEquips = equips, mailEquips: { id?: number, hid?: number, seqId?: number }[] = [];
        if(equips.length + equipCount > BAG.BAG_EQUIP_UPLIMITED) { // 装备上限
            let inc = BAG.BAG_EQUIP_UPLIMITED - equipCount;
            if(inc < 0) inc = 0;
            incEquips = equips.slice(0, inc);
            mailEquips = equips.slice(inc);
        }

        // 直接加的
        let { equips: equipInfos, pushMessages } = await addEquips(roleId, roleName, <{id: number, hid?: number}[]>incEquips);
        for (let equip of equipInfos) {
            showItems.push({ seqId: equip.seqId, id: equip.id, count: 1, isBag: true });
        }
        for(let equip of combineItems(mailEquips)) {
            showItems.push({ id: equip.id, count: equip.count, isBag: false });
        }
        //装备推送
        if (!!equipInfos.length)
            pinus.app.get('channelService').pushMessageByUids('onEquipAdd', resResult(STATUS.SUCCESS, { equipInfos }), uids);
        pushTaskUpdate(roleId, sid, pushMessages);
        //统计装备
        if (equipInfos.length > 0) {
            let { serverId } = await RoleModel.findByRoleId(roleId);
            await checkActivityTask(serverId, sid, roleId, TASK_TYPE.EQUIP_QUALITY_COUNT, equipInfos.length, { equips: equipInfos.map(obj => { return { quality: obj.quality } }) })
        }
        // 发邮件的
        if(mailEquips.length > 0) {
            await sendMailByContent(MAIL_TYPE.EQUIP_OVER, roleId, { goods: combineItems(mailEquips) });
        }
    }

    // 2. 道具处理
    if(items.length > 0) {
        let { items: itemInfos } = await addBags(roleId, roleName, items);
        for (let item of items) {
            showItems.push({ id: item.id, count: item.count });
        }
        //背包除去装备推送 
        if (!!itemInfos.length)
            pinus.app.get('channelService').pushMessageByUids('onItemUpdate', resResult(STATUS.SUCCESS, { goods: itemInfos }), uids);    
        
    }

    // 3. 货币推送
    if(gold.length > 0 || coin.length > 0 || ap > 0) {
        let { ap: addAp } = await setAp(roleId, role.lv, ap, sid);
        let incCoin = coin.reduce((pre, cur) => pre + cur, 0);
        role = await RoleModel.increaseGoldAndCoin(roleId, gold, incCoin);
        pinus.app.get('channelService').pushMessageByUids('onPlayerDataChange', resResult(STATUS.SUCCESS, {
            gold: role.gold, coin: role.coin, ap: addAp
        }), uids);
        if(gold.length > 0) {
            gold.forEach(({ count }) => {
                showItems.push(getGoldObject(count));
            });
        }
        if(coin.length > 0) {
            coin.forEach(count => {
                showItems.push(getCoinObject(count));
            });
        }
        if(ap > 0) {
            showItems.push(getApObject(ap));
        }
    }

    // 4. 皮肤处理
    let figureInfos:{ heads: Figure[], frames: Figure[], spines: Figure[] }[] = []; // 头像变化推送信息
    if(skins.length > 0) {
        let heroskins: {skins: HeroSkin[], hid: number}[] = []; // 皮肤推送信息
        let skinInfos: {id: number, hid: number }[] = [];
        let calAllHeroResult = undefined; // 全局战力变化推送
    
        for (let skinId of skins) {//皮肤推送
            let result = await addSkin(roleId, roleName, skinId, false);
            if (!!result) {
                showItems.push({ id: skinId, count: 1 });
                figureInfos.push(result.figureInfo);
                if(result.hero) {
                    heroskins.push({ skins: result.hero.skins, hid: result.hero.hid });
                    if(result.calAllHeroResult) calAllHeroResult = result.calAllHeroResult;
                }
                skinInfos.push({ id: skinId, hid: result.hero?.hid||0 })
            }
        }
        if (!!skinInfos.length) {
            pushHeroSkinMsg(heroskins, skinInfos, uids);  // 推送onHeroSkinChange
        }
        // 推送全局加成信息
        if(calAllHeroResult) await pushCalAllHeroCe(roleId, sid, calAllHeroResult);
    } 

    // 5. 获得头像和相框等
    if(figures.length > 0) {
        let figureInfo = await addFigure(roleId, figures);
        if(figureInfo) figureInfos.push(figureInfo);
        for (let id of figures) {//皮肤推送        
            showItems.push({ id, count: 1 });
        }
    }

    // 获得头像或相框或形象推送
    if(!!figureInfos && figureInfos.length > 0) {
        for(let figureInfo of figureInfos) {
            pinus.app.get('channelService').pushMessageByUids('onHeadChange', resResult(STATUS.SUCCESS, { ...figureInfo }), uids);
        }
    }

    return showItems;
}

export function combineItems(items: { id?: number, count?: number }[]) {
    let result: { id: number, count: number }[] = [];
    for(let { id, count = 1 } of items) {
        let index = result.findIndex(cur => cur.id == id);
        if(index == -1) {
            result.push({ id, count });
        } else {
            result[index].count += count;
        }
    }
    return result;
}

function sortItems(goods: ItemInter[], handleType: HANDLE_REWARD_TYPE) {
    let items: { id: number, count: number }[] = [];  // 可叠加道具
    let equips: { seqId?: number, id?: number, hid?: number }[] = []; // 不可叠加装备
    let gold: { count: number, isPay: boolean }[] = []; // 金币
    let coin: number[] = [];
    let ap: number = 0;
    let skins: number[] = [];
    let figures: number[] = [];

    for(let good of goods) {
        if(good.count == 0) continue;
        let dicGood = gameData.goods.get(good.id);
        if(!dicGood) {
            errlogger.error(`物品 ${good.id} 未配置`);
            continue;
        }
        let dicItid = ITID.get(dicGood.itid);
        if(!dicItid) {
            errlogger.error(`itid ${dicGood.itid} 未配置`);
            continue;
        }
        let { type, table, isCurrency } = dicItid;
        if(table == ITEM_TABLE.EQUIP) { // 装备
            if(handleType == HANDLE_REWARD_TYPE.RECEIVE) {
                for(let i = 0; i < good.count; i++) {
                    equips.push({ id: good.id, hid: good.hid })
                }
            } else {
                if(!!good.seqId) {
                    equips.push({ seqId: good.seqId });
                }
            }
        } else if (table == ITEM_TABLE.ITEM) { // 可叠加道具
            let index = items.findIndex(cur => cur.id == good.id);
            if(index > 0) {
                items[index].count += good.count;
            } else {
                items.push({ id: good.id, count: good.count });
            }
        } else if (table == ITEM_TABLE.SKIN) { // 皮肤，不可重复获得，不可删
            if(handleType == HANDLE_REWARD_TYPE.RECEIVE) {
                let index = skins.indexOf(good.id);
                if (index == -1) {
                    skins.push(good.id);
                }
            }
        } else if (table == ITEM_TABLE.ROLE) {
            if(isCurrency) { // 3种货币
                let dicCurrency = CURRENCY.get(good.id);
                if(dicCurrency) {
                    if(dicCurrency.type == CURRENCY_TYPE.GOLD) { // 金币，区分付费和免费，默认免费
                        let index = gold.findIndex(cur => cur.isPay == !!good.isPay);
                        if(index > 0) {
                            gold[index].count += good.count;
                        } else {
                            gold.push({ count: good.count, isPay: !!good.isPay });
                        }
                    } else if (dicCurrency.type == CURRENCY_TYPE.COIN) { // 铜钱
                        coin.push(good.count);
                    } else if (dicCurrency.type == CURRENCY_TYPE.ACTION_POINT) { // 体力
                        ap += good.count;
                    }
                }
            } else { 
                if (type == CONSUME_TYPE.HEAD || type == CONSUME_TYPE.FRAME || type == CONSUME_TYPE.SPINE) { // 头像等，不可重复获得，不可删
                    let index = figures.indexOf(good.id);
                    if (index == -1) {
                        figures.push(good.id);
                    }
                }
            }
        }

    }

    return { items, equips, gold, coin, ap, skins, figures }
}

export async function decreaseItems(roleId: string, sid: string, bags: Array<{ id: number, count: number, ratio?: number }>) {
    let uids = [{ uid: roleId, sid }];
    if (bags.length > 0) {
        let { hasError, result } = await ItemModel.decreaseItems(roleId, bags);
        if (hasError) return true;
        pinus.app.get('channelService').pushMessageByUids('onItemUpdate', resResult(STATUS.SUCCESS, { goods: result }), uids);
    }
    return false;
}


export async function checkGoods(roleId: string, goodIds: Array<number>) {
    let equipIds: Array<number> = [];
    let itemIds: Array<number> = [];
    let hids: Array<number> = [];
    goodIds = uniq(goodIds);
    for (let goodId of goodIds) {
        let goodInfo = gameData.goods.get(goodId);
        if (!!goodInfo) {
            let { table } = ITID.get(goodInfo.itid);
            if (table == ITEM_TABLE.EQUIP) {
                equipIds.push(goodId);
            } else if (table == ITEM_TABLE.ITEM) {
                itemIds.push(goodId);
            }
        }
    }

    //检查装备是否存在
    if (!!equipIds.length) {
        let resEquips = await EquipModel.getEquipsByIds(roleId, equipIds);
        resEquips = uniq(resEquips, function (resEquip) {
            return resEquip.id;
        });
        if (resEquips.length < equipIds.length)
            return false;
    }
    //检查并修改道具
    if (itemIds.length > 0) {
        let items = await ItemModel.findbyRoleAndIds(roleId, itemIds);
        if (items.length < itemIds.length)
            return false;
    }
    return true;
}

export async function checkHeroes(roleId: string, hids: number[]) {
    if (!!hids.length) {
        let heros = await HeroModel.findByHidRange(hids, roleId);
        if (heros.length < hids.length)
            return false;
    }
    return true
}

export async function unlockFigure(sid: string, roleId: string, conditions: { type: number, paramHid?: number, paramFavourLv?: number, paramSkinId?: number }[], role?: RoleType) {
    let figureInfo = await pubUnlockFigure(roleId, conditions, role);
    await pushFigureUpdate(roleId, sid, figureInfo);
}

export async function pushFigureUpdate(roleId: string, sid: string, figureInfo: { heads: Figure[], frames: Figure[], spines: Figure[] }) {
    if (!!figureInfo && (figureInfo.heads.length > 0 || figureInfo.frames.length > 0 || figureInfo.spines.length > 0)) {
        let uids = [{ uid: roleId, sid }];
        pinus.app.get('channelService').pushMessageByUids('onHeadChange', resResult(STATUS.SUCCESS, { ...figureInfo }), uids);
    }
}

/**
 * 创建多个武将
 * @param roleId 
 * @param sid 
 * @param serverId 
 * @param heroInfo 
 */
export async function createHeroes(roleId: string, roleName: string, sid: string, serverId: number, heroInfo: CreateHeroParam[]) {
    let hids = heroInfo.map(cur => cur.hid);
    let userHeroesMap = await HeroModel.findMapByHidRange(hids, roleId);

    let infos: Map<number, { heroInfo: HeroUpdate, skinInfo: SkinUpdate }> = new Map(), pieces: ItemInter[] = [];
    for (let h of heroInfo) {
        let heroCount = h.count || 1;
        if (userHeroesMap.has(h.hid)) {
            let { pieceId, count } = transPiece(h.hid);
            pieces.push({ id: pieceId, count: count * heroCount });
        } else {
            let initInfo: { heroInfo: HeroUpdate, skinInfo: SkinUpdate };
            if(pinus.app.getServerType() == 'role') {
                initInfo = getInitHeroById(h.hid);
            } else {
                let roleServers = pinus.app.getServersByType('role');
                let server = getRandSingleEelm(roleServers);
                initInfo = await pinus.app.rpc.role.roleRemote.getInitHeroById.toServer(server.id, h.hid);
            }

            initInfo.heroInfo = { ...initInfo.heroInfo, ...h };

            infos.set(h.hid, initInfo);
            userHeroesMap.set(h.hid, null);
            if (heroCount > 1) {
                let { pieceId, count } = transPiece(h.hid);
                pieces.push({ id: pieceId, count: count * (heroCount - 1) });
            }
        }
    }

    let resultHeroes: HeroType[] = [], resultItems: RewardInter[] = [];
    if (infos.size > 0) {
        let createHero = new CreateHeroes(roleId, roleName, serverId);
        await createHero.createWithHeroInfo(infos);
        await createHero.clearTask(await getActivities())
        await createHero.pushMessage(pinus, sid);
        await createHero.updateRedisRank(Rank);
        resultHeroes = createHero.getResultHeroes();
    }

    if (pieces.length > 0) {
        let goods = await addItems(roleId, roleName, sid, pieces);
        resultItems = goods;
    }
    return { heroes: resultHeroes, goods: resultItems }
}

export async function createHero(roleId: string, roleName: string, sid: string, serverId: number, heroInfo: CreateHeroParam) {
    let result = await createHeroes(roleId, roleName, sid, serverId, [heroInfo]);
    return result;
}

/**
 * 皮肤数据变化去重、推送
 * @param heroskins 推送的皮肤
 * @param uids 玩家
 */
function pushHeroSkinMsg(heroskins: {skins: HeroSkin[], hid: number}[],  skinInfos: {id: number, hid: number }[], uids: {uid: string, sid: string}[]) {
    let pushSkinInfos: {skins: HeroSkin[], hid: number}[] = []; // 可能会有重复的
    for(let { skins, hid } of heroskins) {
        let index = pushSkinInfos.findIndex(cur => cur.hid == hid);
        if(index == -1) {
            pushSkinInfos.push({skins, hid});
        } else {
            if(skins.length > pushSkinInfos[index].skins.length) {
                pushSkinInfos[index] = {skins, hid};
            }
        }
    }
    if(pushSkinInfos.length > 0 || skinInfos.length > 0) {
        pinus.app.get('channelService').pushMessageByUids('onHeroSkinChange', resResult(STATUS.SUCCESS, { heros: heroskins, skins: skinInfos }), uids);
    }
}