import { Application, BackendSession, HandlerService, } from "pinus";
import { STATUS, HERO_SYSTEM_TYPE, ITEM_CHANGE_REASON, TASK_TYPE } from "../../../consts";
import { ArtifactModel, ArtifactModelType, ArtifactModelUpdate } from "../../../db/Artifact";
import { HeroModel } from "../../../db/Hero";
import { ArtifactParam } from "../../../domain/roleField/hero";
import { gameData, getArtifactByGidAndType, getArtifactStageZero, getArtifactWithQuality, getDicArtifactLvByPlanId, getNextArtifact } from "../../../pubUtils/data";
import { ARTIFACT, BAG } from "../../../pubUtils/dicParam";
import { ItemInter } from "../../../pubUtils/interface";

import { resResult, parseGoodStr, arrToMap, genCode } from "../../../pubUtils/util";
import { checkArtifactCanCompose, getRebuildConsume, hasArtifactStrength } from "../../../services/equipService";
import { calculateCeWithHero } from "../../../services/playerCeService";
import { CheckMeterial } from "../../../services/role/checkMaterial";
import { addItems, handleCost } from "../../../services/role/rewardService";
import { combineItems } from "../../../services/role/util";
import { checkTask } from "../../../services/task/taskService";

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

export class ArtifactHandler {

    constructor(private app: Application) {
    }

    public async putOn(msg: { seqId: number, hid: number }, session: BackendSession) {
        let roleId: string = session.get('roleId');
        let sid: string = session.get('sid');
        let serverId: number = session.get('serverId');

        let { seqId, hid } = msg;
        let hero = await HeroModel.findByHidAndRole(hid, roleId);
        if (!hero) return resResult(STATUS.HERO_NOT_FIND);

        let chosenArtifact = await ArtifactModel.findbySeqId(roleId, seqId);
        if(!chosenArtifact) return resResult(STATUS.ARTIFACT_IS_NOT_FIND);

        let artifacts: ArtifactModelType[] = [];
        let artifactOfHero = hero.artifact? await ArtifactModel.findbySeqId(roleId, hero.artifact): null; // 原本自己的天晶石
        if(chosenArtifact.hid != 0) { // 如果天晶石原本镶嵌在其他武将身上，把自己的给他
            let heroOfChosenArtifact = await HeroModel.findByHidAndRole(chosenArtifact.hid, roleId); // 我想要的宝物的原持有者
            if(heroOfChosenArtifact) {
                await calculateCeWithHero(HERO_SYSTEM_TYPE.PUT_ARTIFACT, roleId, serverId, sid, heroOfChosenArtifact.hid, { artifact: hero.artifact||0 }, { artifact: artifactOfHero, job: heroOfChosenArtifact.job, skinId: heroOfChosenArtifact.skinId }); // 把我的换给他
            }
        }
        if(artifactOfHero) { // 更新自己的天晶石
            artifactOfHero = await ArtifactModel.putOnOrOff(roleId, artifactOfHero.seqId, chosenArtifact.hid||0);
            if(artifactOfHero) artifacts.push(artifactOfHero);
        }
        chosenArtifact = await ArtifactModel.putOnOrOff(roleId, seqId, hid);
        if(chosenArtifact) artifacts.push(chosenArtifact);
        await calculateCeWithHero(HERO_SYSTEM_TYPE.PUT_ARTIFACT, roleId, serverId, sid, hid, { artifact: seqId }, { artifact: chosenArtifact, job: hero.job, skinId: hero.skinId }); // 把我的换给他
        await checkTask(serverId, roleId, sid, TASK_TYPE.ARTIFACT_QUALITY_EQUIP, { artifacts });

        return resResult(STATUS.SUCCESS, { artifacts: artifacts.map(artifact => new ArtifactParam(artifact)) });
    }

    public async putOff(msg: { seqId: number }, session: BackendSession) {
        let roleId: string = session.get('roleId');
        let sid: string = session.get('sid');
        let serverId: number = session.get('serverId');

        let { seqId } = msg;

        let chosenArtifact = await ArtifactModel.findbySeqId(roleId, seqId);
        if(!chosenArtifact) return resResult(STATUS.ARTIFACT_IS_NOT_FIND);

        let heroOfChosenArtifact = await HeroModel.findByHidAndRole(chosenArtifact.hid, roleId); // 我想要的宝物的原持有者
        if(!heroOfChosenArtifact) return resResult(STATUS.ARTIFACT_IS_NOT_EQUIPED);

        chosenArtifact = await ArtifactModel.putOnOrOff(roleId, seqId, 0);
        let artifacts: ArtifactParam[] = [new ArtifactParam(chosenArtifact)];

        await calculateCeWithHero(HERO_SYSTEM_TYPE.PUT_OFF_ARTIFACT, roleId, serverId, sid, heroOfChosenArtifact.hid, { artifact: 0 }); // 把我的换给他

        return resResult(STATUS.SUCCESS, { artifacts });
    }

    public async lvUp(msg: { seqId: number, isOneClick: boolean }, session: BackendSession) {
        let roleId: string = session.get('roleId');
        let sid: string = session.get('sid');
        let serverId: number = session.get('serverId');

        let { seqId, isOneClick } = msg;

        let artifact = await ArtifactModel.findbySeqId(roleId, seqId);
        if(!artifact) return resResult(STATUS.ARTIFACT_IS_NOT_FIND);

        let dicArtifact = getArtifactWithQuality(artifact.artifactId);
        if(!dicArtifact) return resResult(STATUS.DIC_DATA_NOT_FOUND);

        if(artifact.lv >= dicArtifact.maxLv) return resResult(STATUS.ARTIFACT_LV_MAX);
        let toLv = isOneClick? artifact.lv + 5: artifact.lv + 1;
        if(toLv > dicArtifact.maxLv) toLv = dicArtifact.maxLv;
        let newLv = artifact.lv;

        let check = new CheckMeterial(roleId);
        for (let lv = artifact.lv + 1; lv <= toLv; lv++) {
            let dicArtifactLv = getDicArtifactLvByPlanId(dicArtifact.lvAttrPlan, lv);
            if(!dicArtifactLv) break;
            let isEnough = await check.decrease(dicArtifactLv.consumes);
            if(!isEnough) break; // 消耗不足
            newLv = lv;
        }
        if(newLv == artifact.lv) return resResult(STATUS.ROLE_MATERIAL_NOT_ENOUGH);
        let consumes = check.getConsume();
        let result = await handleCost(roleId, sid, consumes, ITEM_CHANGE_REASON.ARTIFACT_LV);
        if (!result) return resResult(STATUS.ROLE_MATERIAL_NOT_ENOUGH);

        artifact = await ArtifactModel.updateInfoBySeqId(roleId, seqId, { lv: newLv });
        if(artifact.hid > 0) {
            await calculateCeWithHero(HERO_SYSTEM_TYPE.ARTIFACT_LV, roleId, serverId, sid, artifact.hid, {}, { artifact });
        }
        await checkTask(serverId, roleId, sid, TASK_TYPE.ARTIFACT_LV, { artifacts: [artifact] });

        return resResult(STATUS.SUCCESS, { artifact: new ArtifactParam(artifact) });
    }

    public async compose(msg: { seqId: number, material: number[], generalItems: {id: number, count: number}[] }, session: BackendSession) {
        let roleId: string = session.get('roleId');
        let sid: string = session.get('sid');
        let serverId: number = session.get('serverId');

        let { seqId, material = [], generalItems = [] } = msg;

        let seqIds = [seqId, ...material];
        let artifacts = await ArtifactModel.findbySeqIds(roleId, seqIds);
        if (artifacts.length < seqIds.length) return resResult(STATUS.ARTIFACT_IS_NOT_FIND);
        let artifactMap = arrToMap(artifacts, obj => obj.seqId);

        let originArtifact = artifactMap.get(seqId);
        if(!originArtifact) return resResult(STATUS.ARTIFACT_IS_NOT_FIND);

        let dicOriginArtifact = getArtifactWithQuality(originArtifact.artifactId);
        if(!dicOriginArtifact) return resResult(STATUS.ARTIFACT_IS_NOT_FIND);
        let dicNextArtifact = getNextArtifact(originArtifact.artifactId);
        if(!dicNextArtifact) return resResult(STATUS.DIC_DATA_NOT_FOUND);

        // 狗粮处理
        let remainCnt = dicNextArtifact.materialCnt, cost: ItemInter[] = [], delSeqIds: number[] = [];
        for(let seqId of material) {
            let artifact = artifactMap.get(seqId);
            let res = checkArtifactCanCompose(originArtifact.artifactId, artifact);
            if(res.code != 0) return resResult(res);
            cost.push({ seqId, id: artifact.id });
            delSeqIds.push(seqId);
            remainCnt--;
        }
        // 通用道具
        if(dicNextArtifact.materialGroup == 0 && remainCnt > 0) { // 可使用材料代替，不是必须同名的那种
            let dicMaterialArtifactQuality = gameData.artifactQualityById.get(dicNextArtifact.materialId);
            if(!dicMaterialArtifactQuality) return resResult(STATUS.DIC_DATA_NOT_FOUND);
            for(let { id, count } of dicMaterialArtifactQuality.generalItem) {
                let needCount = count * remainCnt;
                let chosenItem = generalItems.find(cur => cur.id == id);
                if(!chosenItem || chosenItem.count < needCount) return resResult(STATUS.ROLE_MATERIAL_NOT_ENOUGH);
                cost.push({ id, count: needCount });
            }
            remainCnt = 0;
        }
        if(remainCnt > 0) return resResult(STATUS.ROLE_MATERIAL_NOT_ENOUGH);

        let result = await handleCost(roleId, sid, cost, ITEM_CHANGE_REASON.ARTIFACT_QUALITY);
        if (!result) return resResult(STATUS.ROLE_MATERIAL_NOT_ENOUGH);

        let { quality, qualityStage, artifactId, goodId } = dicNextArtifact;
        originArtifact = await ArtifactModel.updateInfoBySeqId(roleId, seqId, { quality, qualityStage, artifactId, id: goodId });
        if(originArtifact.hid > 0) {
            let hero = await HeroModel.findByHidAndRole(originArtifact.hid, roleId);
            if (!hero) return resResult(STATUS.HERO_NOT_FIND);
            await calculateCeWithHero(HERO_SYSTEM_TYPE.ARTIFACT_QUALITY, roleId, serverId, sid, originArtifact.hid, {}, { artifact: originArtifact, job: hero.job, skinId: hero.skinId });
            await checkTask(serverId, roleId, sid, TASK_TYPE.ARTIFACT_QUALITY_EQUIP, { artifacts: [originArtifact] });
        }
        await checkTask(serverId, roleId, sid, TASK_TYPE.ARTIFACT_COMPOSE, { count: 1 });

        return resResult(STATUS.SUCCESS, { delSeqIds, artifact: new ArtifactParam(originArtifact) });
    }

    public async composeAll(msg: {}, session: BackendSession) {
        let roleId: string = session.get('roleId');
        let sid: string = session.get('sid');
        let serverId: number = session.get('serverId');

        let taskCount = 0;
        const batchCode = genCode(10), delSeqIds: number[] = [];
        for(let [_, { quality, canComposeAll }] of gameData.artifactQualityById) {
            if(!canComposeAll) continue;

            let artifacts = await ArtifactModel.findByQuality(roleId, quality);
            let used: number[] = [], materials: ItemInter[] = [], target: ArtifactModelUpdate[] = [];
            for(let artifact of artifacts) {
                if(used.indexOf(artifact.seqId) > -1) continue;
                let tmpUsed: number[] = [artifact.seqId];
                let dicNextArtifact = getNextArtifact(artifact.artifactId);
                if(!dicNextArtifact) continue;
    
                let { materialCnt, goodId: nextGid, artifactId: nextArtId, quality: nextQuality, qualityStage: nextStage } = dicNextArtifact;
                let canUseMaterial: ItemInter[] = [];
                for(let i = 0; i < materialCnt; i++) {
                    for(let _artifact of artifacts) {
                        if(used.indexOf(_artifact.seqId) > -1 || tmpUsed.indexOf(_artifact.seqId) > -1) continue;
                        let res = checkArtifactCanCompose(artifact.artifactId, _artifact);
                        if(res.code != 0) continue;
                        canUseMaterial.push({ id: _artifact.id, seqId: _artifact.seqId });
                        tmpUsed.push(_artifact.seqId);
                        break;
                    }
                }
                if(materialCnt == canUseMaterial.length) {
                    used.push(...tmpUsed);
                    materials.push(...canUseMaterial);
                    delSeqIds.push(...canUseMaterial.map(cur => cur.seqId));
                    target.push({ seqId: artifact.seqId, id: nextGid, artifactId: nextArtId, quality: nextQuality, qualityStage: nextStage });
                    taskCount++;
                }
        
            }

            let result = await handleCost(roleId, sid, materials, ITEM_CHANGE_REASON.ARTIFACT_QUALITY);
            if (!result) continue;
            for(let { seqId, quality, qualityStage, artifactId, id} of target) {
                await ArtifactModel.updateInfoBySeqId(roleId, seqId, { quality, qualityStage, artifactId, id, batchCode });
            }
        }
        let artifacts = await ArtifactModel.findByBatchCode(batchCode);
        await checkTask(serverId, roleId, sid, TASK_TYPE.ARTIFACT_COMPOSE, { count: taskCount });

        return resResult(STATUS.SUCCESS, { delSeqIds, artifacts: artifacts.map(artifact => new ArtifactParam(artifact))});
    }

    public async transfer(msg: { seqId: number, type: number }, session: BackendSession) {
        let roleId: string = session.get('roleId');
        let sid: string = session.get('sid');
        let serverId: number = session.get('serverId');
        let { seqId, type } = msg;

        let artifact = await ArtifactModel.findbySeqId(roleId, seqId);
        if(!artifact) return resResult(STATUS.ARTIFACT_IS_NOT_FIND);

        let dicArtifact = gameData.artifact.get(artifact.artifactId);
        if(!dicArtifact) return resResult(STATUS.DIC_DATA_NOT_FOUND);
        if(dicArtifact.type == type) return resResult(STATUS.ARTIFACT_TYPE_SAME);

        let dicTargetArtifact = getArtifactByGidAndType(dicArtifact.goodId, type);
        if(!dicTargetArtifact) return resResult(STATUS.ARTIFACT_TYPE_ERR);

        let consume = parseGoodStr(ARTIFACT.TRANSFER_COST);
        let result = await handleCost(roleId, sid, consume, ITEM_CHANGE_REASON.ARTIFACT_TRANSFER);
        if (!result) return resResult(STATUS.ROLE_MATERIAL_NOT_ENOUGH);


        let { artifactId, goodId } = dicTargetArtifact;
        artifact = await ArtifactModel.updateInfoBySeqId(roleId, seqId, { artifactId, id: goodId });
        if(artifact.hid > 0) {
            let hero = await HeroModel.findByHidAndRole(artifact.hid, roleId);
            if (!hero) return resResult(STATUS.HERO_NOT_FIND);
            await calculateCeWithHero(HERO_SYSTEM_TYPE.ARTIFACT_TRANSFER, roleId, serverId, sid, artifact.hid, {}, { artifact, job: hero.job, skinId: hero.skinId });
        }

        return resResult(STATUS.SUCCESS, { artifact: new ArtifactParam(artifact) });
    }

    public async previewRebuild(msg: { seqIds: number[] }, session: BackendSession) {
        let roleId: string = session.get('roleId');
        let { seqIds } = msg;

        let artifacts = await ArtifactModel.findbySeqIds(roleId, seqIds);
        if (artifacts.length < seqIds.length) return resResult(STATUS.ARTIFACT_IS_NOT_FIND);

        let consumes = getRebuildConsume(artifacts);

        return resResult(STATUS.SUCCESS, { consumes });
    }

    public async rebuild(msg: { seqIds: number[] }, session: BackendSession) {
        let roleId: string = session.get('roleId');
        let roleName: string = session.get('roleName');
        let serverId: number = session.get('serverId');
        let sid: string = session.get('sid');
        let { seqIds } = msg;

        let artifacts = await ArtifactModel.findbySeqIds(roleId, seqIds);
        if (artifacts.length < seqIds.length) return resResult(STATUS.ARTIFACT_IS_NOT_FIND);

        let rebuildConsumes = getRebuildConsume(artifacts);
        if(rebuildConsumes.length <= 0) return resResult(STATUS.ARTIFACT_HAS_NO_STRENGTH);

        let consume = parseGoodStr(ARTIFACT.REBUILD_COST);
        let result = await handleCost(roleId, sid, consume, ITEM_CHANGE_REASON.ARTIFACT_REBUILD);
        if (!result) return resResult(STATUS.ROLE_MATERIAL_NOT_ENOUGH);

        let returnArtifact: ArtifactParam[] = [];
        for(let artifact of artifacts) {
            let dicArtifactZero = getArtifactStageZero(artifact.artifactId);
            if(!dicArtifactZero) continue;
            let { artifactId, goodId, quality, qualityStage } = dicArtifactZero;
            let result = await ArtifactModel.updateInfoBySeqId(roleId, artifact.seqId, { artifactId, id: goodId, quality, qualityStage, lv: 0 });
            if(result && result.hid > 0) {
                let hero = await HeroModel.findByHidAndRole(result.hid, roleId);
                await calculateCeWithHero(HERO_SYSTEM_TYPE.ARTIFACT_REBUILD, roleId, serverId, sid, result.hid, {}, { artifact: result, skinId: hero.skinId, job: hero.job });
            }
            returnArtifact.push(new ArtifactParam(result));
        }
        let goods = await addItems(roleId, roleName, sid, rebuildConsumes, ITEM_CHANGE_REASON.ARTIFACT_REBUILD);
        return resResult(STATUS.SUCCESS, { artifacts: returnArtifact, goods });
    }

    public async decompose(msg: { seqIds: number[] }, session: BackendSession) {
        let roleId: string = session.get('roleId');
        let sid: string = session.get('sid');
        let roleName: string = session.get('roleName');

        let { seqIds } = msg;
        if(seqIds.length > BAG.BAG_ARTIFACT_DECOMPOSE_UPLIMITED) {
            return resResult(STATUS.EQUIP_DECOMPOSE_IS_UPLIMIT);
        }
        let artifacts = await ArtifactModel.findbySeqIds(roleId, seqIds);
        if (artifacts.length < seqIds.length) return resResult(STATUS.ARTIFACT_IS_NOT_FIND);

        let cost: ItemInter[] = [], add: ItemInter[] = [], delSeqIds: number[] = [];
        for(let artifact of artifacts) {
            if(artifact.hid > 0 || hasArtifactStrength(artifact)) return resResult(STATUS.ARTIFACT_CANNOT_DECOMPOSE);
            let dicArtifact = getArtifactWithQuality(artifact.artifactId);
            if(!dicArtifact || !dicArtifact.canDecompose) return resResult(STATUS.ARTIFACT_CANNOT_DECOMPOSE);
            let dicZeroArtifact = getArtifactStageZero(artifact.artifactId);
            if(!dicZeroArtifact) continue;
            add.push(...dicZeroArtifact.generalItem);

            cost.push({ seqId: artifact.seqId, id: artifact.id, count: 1 });
            delSeqIds.push(artifact.seqId);
        }

        let costResult = await handleCost(roleId, sid, cost, ITEM_CHANGE_REASON.EQUIP_DECOMPOSE); // 删掉装备
        if(!costResult) return resResult(STATUS.BATTLE_CONSUMES_NOT_ENOUGH);

        let result = await addItems(roleId, roleName, sid, add, ITEM_CHANGE_REASON.EQUIP_DECOMPOSE);
        return resResult(STATUS.SUCCESS, { delSeqIds, goods: combineItems(result) });
    }
}