/**
 * Created by zhang on 2018/1/26.
 */

module.exports = {
    sortChildren,
    markUpdateProject:markUpdateProject,
    removeProjectMark:removeProjectMark,
    updateNodes:updateNodes,
    calcInstallationFee:calcInstallationFee,
    calcOverHeightFee: calcOverHeightFee,
    saveProperty: saveProperty,
    getDefaultColSetting: getDefaultColSetting,
    markProjectsToChange:markProjectsToChange,
    getSEIProjects:getSEIProjects,
    loadSEIProjectData:loadSEIProjectData,
    setSEILibData:setSEILibData,
    getIndexReportData:getIndexReportData,
    setDefaultItemIncrease:setDefaultItemIncrease,
    getCompareProjects:getCompareProjects,
};

let mongoose = require('mongoose');
let logger = require("../../../logs/log_helper").logger;
let  projectsModel = mongoose.model('projects');
let async_n = require("async");
let _ = require('lodash');
let ration_model = require('../models/ration');
let bill_model = require('../models/bills');
let consts = require('../models/project_consts');
let projectConsts = consts.projectConst;
let ration_glj_model = mongoose.model('ration_glj');
let rationTemplateModel = mongoose.model('ration_template');
let project_glj_model = mongoose.model('glj_list');
let ration_glj_facade = require("../../ration_glj/facade/ration_glj_facade");
let glj_calculate_facade = require("../../ration_glj/facade/glj_calculate_facade");
const uuidV1 = require('uuid/v1');
const gljUtil = require('../../../public/gljUtil');
let stdColSettingModel = mongoose.model('std_main_col_lib');
const GLJListModel = require("../../glj/models/glj_list_model");
let economicLib = mongoose.model('std_economic_lib');
let engineerFeatureLib = mongoose.model('std_engineer_feature_lib');
let engineerInfoLib = mongoose.model('std_engineer_info_lib');
let mainQuantityLib = mongoose.model('std_main_quantity_lib');
let materialLib = mongoose.model('std_material_lib');
const { fixedFlag } = require('../../../public/common_constants');
const scMathUtil = require('../../../public/scMathUtil').getUtil();
const billsLibDao = require("../../bills_lib/models/bills_lib_interfaces");

async function calcInstallationFee(data) {
    let result={};
    let projectGLJList = [];
    let billTasks  = await generateTasks(data.bills,data.useID,true);
    let rationTasks =await generateTasks(data.ration,data.useID);
    if(billTasks.length>0){
        await bill_model.model.bulkWrite(billTasks);
    }
    if(rationTasks.length>0){
        await ration_model.model.bulkWrite(rationTasks);
    }
    //如果删除定额，需要删除对应的工料机
    if(data.ration.delete.length>0){
        let rationIDS = _.map(data.ration.delete,'ID');
        await ration_glj_model.deleteMany({projectID: data.ration.delete[0].projectID, rationID: {"$in": rationIDS}});//删除定额工料机
    }

    let rationGLJTasks = [];
    let updateList = [];
    if(data.ration.update.length>0){//如果有需要更新的定额工料机
        for(let ur of data.ration.update){
            for(let g of ur.glj){
                let gTasks = {
                    updateOne:{
                        filter:{
                            ID:g.ID,
                            projectID:g.projectID
                        },
                        update :{
                            quantity:g.quantity,
                            rationItemQuantity:g.rationItemQuantity
                        }
                    }
                };
                rationGLJTasks.push(gTasks);
                updateList.push(g);
            }
        }
    }
    if(rationGLJTasks.length>0){
       await ration_glj_model.bulkWrite(rationGLJTasks);
    }

    let newGljList = [];
    if(data.ration.add.length>0){//新增的安装子目要增加对应的工料机
        for(let nr of data.ration.add){
            for(let tkey in nr.glj){
                let [newRecode,projectGLJ] = await createRationGLJData(nr.glj[tkey])
                newGljList.push(newRecode);
                projectGLJList.push(projectGLJ);
            }
        }
    }
    if(newGljList.length>0){
        await ration_glj_model.insertMany(newGljList);
    }
    result.update = updateList;
    result.add = newGljList;
    result.projectGLJList = projectGLJList;
    result.updateData = data;
    return result;
}

async function createRationGLJData(glj) {
    glj.ID = uuidV1();
    let [info,projectGLJ ] = await ration_glj_facade.getInfoFromProjectGLJ(glj);
    let newRecode  = ration_glj_facade.createNewRecord(info);
    return [newRecode,projectGLJ];
}





async function generateTasks(data,userID,isBills=false) {
    let tasks=[];
    let deleteInfo={deleted: true, deleteDateTime: new Date(), deleteBy: userID};
    if(data.delete && data.delete.length > 0){
        for(let bd of data.delete){
            //原先是假删除，现在改成真删除
            let task = {
                deleteOne:{
                    filter:{
                        ID:bd.ID,
                        projectID:bd.projectID
                    }
                }
            };
           /* let task={
                updateOne:{
                    filter:{
                        ID:bd.ID,
                        projectID:bd.projectID
                    },
                    update :{
                        deleteInfo:deleteInfo
                    }
                }
            };*/
            tasks.push(task);
        }
    }
    if(data.add && data.add.length > 0){
        for(let n_data of data.add){
          if(isBills == true){
            let stdBills = await billsLibDao.getStdBillsByCode({userId: n_data.userID, billsLibId: n_data.billsLibId, code: n_data.stdCode});
            stdBills = stdBills ? stdBills._doc : null;
            if (stdBills) {
                // 获取项目清单所需要的数据
                const projectBillsData = billsLibDao.getDataToProjectBills(stdBills);
                n_data.jobContent = projectBillsData.jobContent;
                n_data.itemCharacter = projectBillsData.itemCharacter;
                n_data.itemCharacterText = projectBillsData.itemCharacterText;
                n_data.jobContentText = projectBillsData.ijobContentText;
            } else {
                delete n_data.billsLibId;
            }
            delete n_data.userID;
            delete n_data.stdCode;
          }
            let task = {
                insertOne :{
                    document:n_data
                }
            };
            tasks.push(task);
        }
    }
    return tasks;
}

/**
 * 计取超高降效费，可能包含更新清单、更新定额、新增清单、新增定额及其子数据、删除定额及其子数据
 * @param {Object} data - {updateData: {ration: [],..}, addData: {ration: [],...}, deleteData: {ration: [],...}}
 * @return {Object} - {bills: Array, rationGLJ: Array}
 */
async function calcOverHeightFee(data) {
    // 更新数据
    async function update({ project, bills, ration }) {
        const tasks = [];
        // 更新项目
        if (project && project.ID) {
            const projectTask = projectsModel.update({ ID: project.ID }, 
                { 'property.overHeightOption': project.overHeightOption, 'property.overHeightSpecificID': project.overHeightSpecificID });
            tasks.push(projectTask);
        }
        // 更新清单和定额的超高降效列
        const models = [];
        if (bills.length) {
            models.push({ model: bill_model.model, items: bills });
        }
        if (ration.length) {
            models.push({ model: ration_model.model, items: ration });
        }
        models.forEach(modelData => {
            const bulkTask = modelData.items.map(item => (
                {
                    updateOne: {
                        filter: { ID: item.ID },
                        update: { overHeight: item.overHeight }
                    }
                }));
            const task = modelData.model.bulkWrite(bulkTask);
            tasks.push(task);;
        });
        if (!tasks.length) {
            return;
        }
        await Promise.all(tasks);
    }
    // 插入数据
    async function add({ bills, ration, rationGLJ}) {
        const tasks = [];
        // 匹配标准清单，加上工作内容等数据
        for (const billsItem of bills) {
            let stdBills = await billsLibDao.getStdBillsByCode(billsItem);
            stdBills = stdBills ? stdBills._doc : null;
            if (stdBills) {
                // 获取项目清单所需要的数据
                const projectBillsData = billsLibDao.getDataToProjectBills(stdBills);
                Object.assign(billsItem, projectBillsData);
            } else {
                delete billsItem.billsLibId;
            }
            billsItem.code += '001';
        }
        // 新增清单
        if (bills.length) {
            tasks.push(bill_model.model.insertMany(bills));
        }
        // 新增定额
        if (ration.length) {
            tasks.push(ration_model.model.insertMany(ration));
        }
        // 完整的定额人材机数据
        const completeRationGLJList = [];
        // 完整的项目人材机数据
        const completeProjectGLJList = [];
        // createRationGLJData方法不能并行，涉及到项目人材机的新增，如果并行可能会重复插入数据
        for (const rGLJ of rationGLJ) {
            const [completeRGLJ, completePGLJ] = await createRationGLJData(rGLJ);
            completeRationGLJList.push(completeRGLJ);
            completeProjectGLJList.push(completePGLJ);
        }
        if (completeRationGLJList.length) {
            tasks.push(ration_glj_model.insertMany(completeRationGLJList))
        }
        // 返回新的清单、定额人材机、项目人材机数据数据
        const rst = {
            bills,
            rationGLJ: completeRationGLJList,
            projectGLJ: completeProjectGLJList,
        }
        if (!tasks.length) {
            return rst;
        }
        await Promise.all(tasks);
        return rst;
    }
    // 删除数据
    async function del({ ration }) {
        const tasks = [];
        const rationIDList = ration.map(item => item.ID);
        if (!rationIDList.length) {
            return;
        }
        // 删除定额数据
        const rationTask = ration_model.model.deleteMany({ ID: {$in: rationIDList} });
        tasks.push(rationTask);
        // 删除定额人材机数据
        const rationGLJTask = ration_glj_model.deleteMany({ rationID: {$in: rationIDList} });
        tasks.push(rationGLJTask);
        await Promise.all(tasks);
    }
    // 处理任务
    const { updateData, addData, deleteData } = data;
    const updateTask = update(updateData);
    const addTask = add(addData);
    const delTask = del(deleteData);
    const [updateRst, addRst, delRst] = await Promise.all([updateTask, addTask, delTask]);
    return addRst;
}

async function updateNodes(datas){
    let nodeGroups = _.groupBy(datas,'type');
    let asyncTasks = [];
    let deleteRationIDs=[];
    let taskMap = {};
    taskMap[projectConsts.BILLS] = {tasks:[],model:bill_model.model};
    taskMap[projectConsts.RATION] = {tasks:[],model:ration_model.model};
    taskMap[projectConsts.RATION_GLJ] = {tasks:[],model:ration_glj_model};
    taskMap[projectConsts.PROJECTGLJ] = {tasks:[],model:project_glj_model};
    taskMap[projectConsts.PROJECT] = {tasks:[],model:projectsModel};
    taskMap[projectConsts.RATION_TEMPLATE] = {tasks:[],model:rationTemplateModel};

    for(let type in nodeGroups){
        for(let node of nodeGroups[type]){
            if(taskMap[type]){
                if(type == projectConsts.RATION){
                    //处理面积增加费的数据
                    if(node.data.hasOwnProperty("areaIncreaseFee")){
                        let areaSetting = null;
                        if(nodeGroups[projectConsts.PROJECT]){
                            for(let pn of nodeGroups[projectConsts.PROJECT]){
                                if(pn.data['property.areaSetting']) areaSetting = pn.data['property.areaSetting'];
                            }
                        }
                        let t = await glj_calculate_facade.calculateQuantity({rationID:node.data.ID},null,false,node.data.areaIncreaseFee,areaSetting);
                        if(t.glj_result && t.glj_result.length > 0){//这里要更新工料机的消耗量，修改数据库的操作已经在上一步中完成了，现在只需刷新前端即可
                           for(let gr of t.glj_result){
                               let tdata = gr.doc;
                               tdata.ID = gr.query.ID;
                               datas.push({type:projectConsts.RATION_GLJ,data:tdata});
                           }
                        }
                        node.data.adjustState = t.adjustState;
                    }
                    if(node.action == "delete") deleteRationIDs.push(node.data.ID);
                }
                if(type == projectConsts.RATION_GLJ){
                    if(node.action == "add"){//添加定額工料机的時候，要先走项目工料机的逻辑
                        let [newRecode,projectGLJ] = await createRationGLJData(node.data);
                        node.data =newRecode;
                        node.projectGLJ = projectGLJ;
                    }
                }
                taskMap[type].tasks.push(getTask(node));
            }
        }
    }

    for(let key in taskMap){
        if(taskMap[key].tasks.length> 0) asyncTasks.push(taskMap[key].model.bulkWrite(taskMap[key].tasks))
    }

    if(asyncTasks.length>0) await Promise.all(asyncTasks);
    if(deleteRationIDs.length > 0) await ration_glj_model.deleteMany({rationID: {$in: deleteRationIDs}});


    return datas;


    function getTask(node,idFiled = 'ID') {
        let task={};
        if(node.action == "add"){
            task.insertOne ={
                document:node.data
            }
        }else if(node.action =="delete"){
            task.deleteOne ={
                filter:{}
            };
            task.deleteOne.filter[idFiled] = node.data[idFiled];
        }else {
            task.updateOne = {
                filter:{},
                update :_.cloneDeep(node.data)
            };
            task.updateOne.filter[idFiled] = node.data[idFiled];//现在复制项目也重新生成一个新的ID了，所以ID是唯一的
            delete task.updateOne.update[idFiled];//防止误操作
        }

        return task;
    }
}


/*function updateNodes(datas,callback) {
    let tasks = [];
    for(let node of datas){
        tasks.push(updateOne(node))
    }
    async_n.parallel(tasks, function(err, results) {
        if (!err){
            callback(0, '', results);
        }
        else{
            console.log(err);
            callback(1, 'save project failed'+err.message, null);
        }
    });
    function updateOne(node) {
        if(node.type == projectConsts.BILLS){
            return function (asCallback) {
                bill_model.model.findOneAndUpdate({projectID: node.data.projectID, ID: node.data.ID,deleteInfo: null}, node.data,{new: true}, asCallback);
            }
        }else if(node.type ==projectConsts.RATION){
            return function (asCallback) {
                ration_model.model.findOneAndUpdate({projectID: node.data.projectID, ID: node.data.ID,deleteInfo: null}, node.data,{new: true}, asCallback);
            }
        }
    }
}*/

//data = {feeRateID:111111,projectID:1245}; type = feeRate
async function markUpdateProject(data,type) {
    let query = {deleteInfo:null};
    if(type=="feeRate"){//更改了费率
        query['property.feeFile.id'] = data.feeRateID;
    }
    if(type=="unitFile"){//更改了单价文件
        query['property.unitPriceFile.id'] = data.unitFileID;//unitPriceFile
    }
    let projects = await projectsModel.find(query);
    return await markProjectsToChange(projects,type,data.projectID);
}

async function markProjectsToChange(projects,type,extProjectID){
    let tasks=[];
    for(let p of projects){
        if(extProjectID && p.ID===extProjectID) continue;//排除当前项目
        tasks.push(generateMarkTask(type,p.ID));
    }
    return tasks.length>0 ? await projectsModel.bulkWrite(tasks):null;
}

async function removeProjectMark(projectID) {
    return await projectsModel.findOneAndUpdate({ID:projectID},{"$unset":{"changeMark":1}});
}

function generateMarkTask(value,projectID) {
    let task = {
        updateOne:{
            filter:{
                ID:projectID
            },
            update:{
                changeMark:value
            }
        }
    };
    return task
}

// {projectID: 5, propertyName: 'aaa', propertyValue: 1}
function saveProperty(data, callback){
    let obj = {};
    let pn = 'property.' + data.propertyName;
    obj[pn] = data.propertyValue;
    projectsModel.update({"ID": data.projectID}, obj, function (err) {
        if (err) {
            logger.err(pn + ' save error: ' + err);
            callback(err, null)
        } else {
            // logger.info(pn + ' saved.');
            callback('', null);
        }}
    );
}

async function getDefaultColSetting(libID){
    return await stdColSettingModel.findOne({ID: libID, deleted: false}, '-_id main_tree_col');
}

function sortChildren(lists) {
    let IDMap ={},nextMap = {}, firstNode = null,newList=[];
    for(let l of lists){
        if(l.children&&l.children.length > 0) l.children = sortChildren(l.children);//递规排序
        IDMap[l.ID] = l;
        if(l.NextSiblingID!=-1) nextMap[l.NextSiblingID] = l;
    }
    for(let t of lists){
        if(!nextMap[t.ID]){ //如果在下一节点映射没找到，则是第一个节点
            firstNode = t;
            break;
        }
    }
    if(firstNode){
        newList.push(firstNode);
        delete IDMap[firstNode.ID];
        setNext(firstNode,newList);
    }
    //容错处理，如果链断了的情况，直接添加到后面
    for(let key in IDMap){
        if(IDMap[key]) newList.push(IDMap[key])
    }
    return newList;

    function setNext(node,array) {
        if(node.NextSiblingID != -1){
            let next = IDMap[node.NextSiblingID];
            if(next){
                array.push(next);
                delete IDMap[next.ID];
                setNext(next,array);
            }
        }
    }

}

async  function getSEIProjects(projectID) {
    let project = await  projectsModel.findOne({ID:projectID});
    if(!project) throw new Error(`找不到建设项目：${projectID}`);
    let tem_e = await projectsModel.find({ParentID:project.ID});
    let engineers = [];
    for(let e of tem_e){
        if(!e.deleteInfo || !e.deleteInfo.deleted){
            let tenders =  await projectsModel.find({ParentID:e.ID});
            let children = [];
            for(let t of tenders){
                if(!t.deleteInfo || !t.deleteInfo.deleted){
                    children.push(t);
                }
            }
            e._doc.children = children;
            engineers.push(e);
        }
    }
    engineers = sortChildren(engineers);
    project._doc.children = engineers;
    return project;
}

async function loadSEIProjectData(projectID) {
    let gljListModel = new GLJListModel();
    let [gljList, mixRatioConnectData,mixRatioMap,unitPriceMap] = await gljListModel.getListByProjectId(projectID);
    let rations = await ration_model.getDataSync(projectID);
    let bills = await bill_model.getDataSync(projectID);
    let ration_gljs = await  ration_glj_model.find({projectID:projectID});
    let projectGLJs = {
        gljList : gljList,
        mixRatioConnectData:mixRatioConnectData,
        mixRatioMap:mixRatioMap,
        unitPriceMap:unitPriceMap
    };
    return{bills:bills,rations:rations,ration_gljs:ration_gljs,projectGLJs:projectGLJs}
}
//从建设项目中取工程信息默认值
async function setDefaultInfo(rootProjectID,engineerInfos){
    let rootProject = await projectsModel.findOne({ID:rootProjectID}).lean();
    if(rootProject){
        let baseInfo = _.find(rootProject.property.basicInformation,{key:"basicInfo"});
        if(!baseInfo) return;
        let baseMap = {};
        let attrs = ["工程地点","开工日期","竣工日期","编制日期"];
        for(let item of baseInfo.items){
            baseMap[item.dispName] = item;
        }
        for(let en of engineerInfos){
            if(attrs.indexOf(en.dispName) != -1){
                let key = en.dispName;
                if(key == "工程地点") key = "工程所在地";
                en.value = baseMap[key] && baseMap[key].value || '';
            }
        }
    }
}

async function setSEILibData(property){
    if(property.engineerInfoLibID){//工程信息指标
        let engineerInfo = await engineerInfoLib.findOne({'ID':property.engineerInfoLibID}).lean();
        if(engineerInfo){
            property.engineerInfos = engineerInfo.info;
            await setDefaultInfo(property.rootProjectID,property.engineerInfos);
        }
    }
    if(property.engineerFeatureLibID){//工程特征指标
        let engineerFeature = await engineerFeatureLib.findOne({'ID':property.engineerFeatureLibID});
        if(engineerFeature) property.engineerFeatures = engineerFeature.features;
    }
    if(property.materialLibID){//主要工料指标
        let material = await materialLib.findOne({'ID':property.materialLibID});
        if(material) property.materials = material.materials;
    }
    if(property.mainQuantityLibID){//主要工程量指标
        let mainQuantity = await mainQuantityLib.findOne({'ID':property.mainQuantityLibID});
        if(mainQuantity) property.mainQuantities = mainQuantity.index;
    }
    if(property.economicLibID){//主要工程量指标
        let economic = await economicLib.findOne({'ID':property.economicLibID});
        if(economic) property.economics = economic.index;
    }
}

function setDefaultItemIncrease(property) {
    property.itemIncreaseSetting = {
        isCalc : false,
        setting:[
            {"name":"在生产车间边生产边施工","base":"人工费","coe":10,"scope":{}},
            {"name":"在有害身体健康的场所内施工","base":"人工费","coe":10,"scope":{}},
            {"name":"在洞内、地下室内、库内或暗室内进行施工","base":"人工费","coe":40,"scope":{}},
            {"name":"在洞内、地下室内、库内或暗室内进行施工(模板拆除)","base":"人工费","coe":10,"scope":{}}
        ]
    }
}

async function getIndexReportData(projectID,keyArr) {
    let project = await projectsModel.findOne({ID:projectID});
    let bills = await bill_model.getDataSync(projectID);
    let result = {};

     for(let key of keyArr){
         switch (key) {
             case 'ProjectCostFields':
                 result[key] =getEngineerCostData(project.property,bills);
                 break;
             case 'ProjectEcoFields':
                 result[key] = getEconomicDatas(project.property,bills);
                 break;
             case 'ProjectLabMaterialFields':
                 result[key] = await getMainMaterialDatas(projectID,project.property);
                 break;
             case 'ProjectQtyFields':
                 result[key] = await getQuantityDatas(project.property,bills);
                 break;
             case 'ProjectInfoFields':
                 result[key] = getEngineerInfoData(project.property.engineerInfos);
                 break;
             case 'ProjectFeatureFields':
                 result[key] = getEngineerFeaturesDatas(project.property.engineerFeatures);
                 break;
         }
     }
    return result
}




function getEngineerInfoData(engineerInfos) {
    let datas = [];
    for(let info of engineerInfos){
        let d = {
            name:info.dispName,
            value:info.value
        };
        datas.push(d);
    }
    return datas;
}

function getEngineerFeaturesDatas(engineerFeatures) {
    let datas = [];
    if (engineerFeatures !== null && engineerFeatures !== undefined) {
        for(let f of engineerFeatures){
            let tem = {
                ID:f.ID,
                name:f.name,
                value:f.value,
                ParentID:f.ParentID
            }
            datas.push(tem);
        }
    }
    return datas;
}

function getQuantityDatas(property,bills) {
     return gljUtil.getQuantityDatas(property.engineerFeatures,property.mainQuantities,bills,fixedFlag,_,scMathUtil,property.decimal)
}

function getEconomicDatas(property,bills) {
   return gljUtil.getEconomicDatas(property.engineerFeatures,property.economics,bills,fixedFlag,_,scMathUtil,property.decimal)
}


function getEngineerCostData(property,bills){
    let datas = [];
    let priceIndex = gljUtil.getEngineerCostData(property,bills,fixedFlag,scMathUtil);
    for(let c of priceIndex.children){
        let tem = {
            name:c.name,
            cost:parseFloat(c.attrs[0].value),
            unitCost:parseFloat(c.attrs[1].value),
            per:parseFloat(c.attrs[2].value)
        };
        datas.push(tem);
    }
    return datas;
}

async function getMainMaterialDatas(projectID,property) {
    let gljListModel = new GLJListModel();
    let [gljList, mixRatioConnectData,mixRatioMap,unitPriceMap] = await gljListModel.getListByProjectId(projectID, property.unitPriceFile?property.unitPriceFile.id:null);
    gljList = JSON.parse(JSON.stringify(gljList));
    await calcProjectGLJQuantity(projectID,{gljList:gljList,mixRatioMap:mixRatioMap},property);
    return gljUtil.getMainMaterialDatas(property,property.engineerFeatures,property.materials,{gljList:gljList},property.calcOptions,property.decimal,false,_,scMathUtil)

}

async function calcProjectGLJQuantity(projectID,projectGLJDatas,property){
    let q_decimal = property.decimal.glj.quantity;
    let rationGLJDatas = await ration_glj_model.find({'projectID':projectID});
    let rationDatas = await ration_model.model.find({'projectID':projectID});
    gljUtil.calcProjectGLJQuantity(projectGLJDatas,rationGLJDatas,rationDatas,[],q_decimal)
}


async function getCompareProjects(userID, compilationID){
    let projects = await projectsModel.find({
        '$or': [{
            'userID': userID,
            'compilation': compilationID,
            'deleteInfo': null
        }, {'userID': userID, 'compilation': compilationID, 'deleteInfo.deleted': {'$in': [null, false]}}]
    }, 'ID ParentID NextSiblingID name projType compareID', {lean: true});
   return projects;
}