/*
 * @Author: baozhoutao@steedos.com
 * @Date: 2022-09-15 13:09:51
 * @LastEditors: baozhoutao@steedos.com
 * @LastEditTime: 2023-03-14 11:59:35
 * @Description: 
 */
const express = require("express");
const router = express.Router();
const { requireAuthentication } = require("@steedos/auth");
const _ = require('lodash');
const objectql = require('@steedos/objectql');
const UUFlowManager = require('../manager/uuflow_manager');
const HandlersManager = require('../manager/handlers_manager');
const WorkflowManager = require('../manager/workflow_manager');

const getFlowVersion = (flow, flowVersionId) => {
    if (flow.current._id === flowVersionId) {
        return flow.current;
    } else {
        return _.find(flow.historys, (history) => {
            return history._id === flowVersionId;
        });
    }
}

const getStep = (flow, flowVersionId, stepId) => {
    const flowVersion = getFlowVersion(flow, flowVersionId);
    const step = _.find(flowVersion.steps, (step) => {
        step.id = step._id;
        return step._id === stepId
    });
    return step;
}

const getSteps = (flow, flowVersionId) => {
    const flowVersion = getFlowVersion(flow, flowVersionId);
    const steps = _.map(flowVersion.steps, (step) => {
        step.id = step._id;
        return step;
    });
    return steps;
}

const getStepsById = (flow, flowVersionId, stepIds) => {
    const flowVersion = getFlowVersion(flow, flowVersionId);
    const steps = _.filter(flowVersion.steps, (step) => {
        step.id = step._id;
        return _.includes(stepIds, step.id)
    });
    return steps;
}

const isSkipStep = function (instance, step) {
    return _.includes(instance.skip_steps, step._id)
}

const getNextSteps = async (flow, flowVersionId, instance, currentStep, judge, autoFormDoc, fields, showSkipStep, userId) => {
    if (!currentStep)
        return;

    if (!instance) {
        return [];
    }

    let nextSteps = [];
    const lines = currentStep.lines;

    switch (currentStep.step_type) {
        case 'condition': //条件
            const stepIds = await UUFlowManager.getNextSteps(instance, flow, currentStep, judge, autoFormDoc);
            nextSteps = getStepsById(flow, flowVersionId, stepIds);
            if (!nextSteps.length)
                throw new Error('未能根据条件找到下一步');
            break;
        case 'end': //结束
            return nextSteps;
        case 'sign': //审批
            if (judge == 'approved') { //核准
                for (const line of lines) {
                    if (line.state == "approved") {
                        const step = getStep(flow, flowVersionId, line.to_step);
                        nextSteps.push(step);
                    }
                }
            } else if (judge == "rejected") { //驳回
                lines.forEach(function (line) {
                    if (line.state == "rejected") {
                        const rejected_step = getStep(flow, flowVersionId, line.to_step);
                        // 驳回时去除掉条件节点
                        if (rejected_step && rejected_step.step_type != "condition")
                            nextSteps.push(rejected_step);
                    }
                });

                const traces = instance.traces;

                traces.forEach(function (trace) {
                    if (trace.is_finished == true) {
                        const finished_step = getStep(flow, flowVersionId, trace.step);
                        if (finished_step.step_type != 'condition' && currentStep.id != finished_step.id)
                            nextSteps.push(finished_step);
                    }
                });

                //驳回时支持结束步骤
                const flow_steps = getSteps(flow, flowVersionId);
                const end_step = _.find(flow_steps, { step_type: "end" });

                nextSteps.push(end_step);
            }
            break;
        default: //start：开始、submit：填写、counterSign：会签
            if (currentStep.step_type === 'counterSign' && currentStep.oneClickRejection && judge === "rejected") {
                lines.forEach(function (line) {
                    if (line.state == "rejected") {
                        const rejected_step = getStep(flow, flowVersionId, line.to_step);
                        // 驳回时去除掉条件节点
                        if (rejected_step && rejected_step.step_type != "condition")
                            nextSteps.push(rejected_step);
                    }
                });

                const traces = instance.traces;

                traces.forEach(function (trace) {
                    if (trace.is_finished == true) {
                        const finished_step = getStep(flow, flowVersionId, trace.step);
                        if (finished_step.step_type != 'condition' && currentStep.id != finished_step.id)
                            nextSteps.push(finished_step);
                    }
                });

                //驳回时支持结束步骤
                const flow_steps = getSteps(flow, flowVersionId);
                const end_step = _.find(flow_steps, { step_type: "end" });

                nextSteps.push(end_step);
            } else {
                lines.forEach(function (line) {
                    if (line.state == "submitted") {
                        const submitted_step = getStep(flow, flowVersionId, line.to_step);
                        if (submitted_step)
                            nextSteps.push(submitted_step);
                    }
                });
            }
            break;
    }

    //去除重复
    nextSteps = _.compact(_.uniqBy(nextSteps, 'id'));

    const conditionResults = await Promise.all(nextSteps.map(async (nextStep) => {
        if (nextStep.step_type == "condition") {
            let currentJudge = judge;
            if (!currentJudge && nextStep.step_type == 'sign') {
                currentJudge = 'approved';
            }
            return await getNextSteps(flow, flowVersionId, instance, nextStep, currentJudge, autoFormDoc, fields, showSkipStep, userId);
        }
        return [];
    }));
    const condition_next_steps = _.flatten(conditionResults);

    nextSteps = nextSteps.concat(condition_next_steps);

    const skipStepPromises = nextSteps.map(async (nextStep) => {
        if (nextStep.step_type != "condition") {
            if (!showSkipStep && isSkipStep(instance, nextStep)) {
                let currentJudge = judge;
                if (!currentJudge && nextStep.step_type == 'sign') {
                    currentJudge = 'approved';
                }
                let nextStepsAfterSkipStep = await getNextSteps(flow, flowVersionId, instance, nextStep, currentJudge, autoFormDoc, fields, showSkipStep, userId);
                // 后续步骤不应包含当前步骤
                return nextStepsAfterSkipStep.filter(s => currentStep.id != s.id);
            } else {
                return [nextStep];
            }
        }
        return [];
    });

    const nestedSteps = await Promise.all(skipStepPromises);
    let rev_nextSteps = _.flatten(nestedSteps);

    //去除重复
    rev_nextSteps = _.compact(_.uniqBy(rev_nextSteps, 'id'));

    // 会签节点，如果下一步有多个 则清空下一步
    if (currentStep.step_type == "counterSign" && rev_nextSteps.length > 1 && !currentStep.oneClickRejection) {
        rev_nextSteps = [];
    }

    // 如果步骤的 always_enter_step (始终进入此步骤（默认选中，历史流程如果未配置，也是选中）) 值为false，那么需要运行脚本 enter_step_condition (步骤条件)
    // 如结果中包含条件节点则需要继续计算条件节点下一步
    const needRemoveSteps = {}, needAddSteps = {};
    await Promise.all(rev_nextSteps.map(step => 
         caculateNextStepsByEnterStepCondition(flow, flowVersionId, instance, step, autoFormDoc, fields, needRemoveSteps, needAddSteps, userId)
    ));
    if (!_.isEmpty(needRemoveSteps) && !_.isEmpty(needAddSteps)) {
        for (const id in needAddSteps) {
            if (Object.hasOwnProperty.call(needAddSteps, id)) {
                const index = rev_nextSteps.findIndex(item => item.id == id)
                if (-1 === index) {
                    rev_nextSteps.push(needAddSteps[id])
                }
            }
        }
        for (const id in needRemoveSteps) {
            if (Object.hasOwnProperty.call(needRemoveSteps, id)) {
                const index = rev_nextSteps.findIndex(item => item.id == id)
                if (-1 != index) {
                    rev_nextSteps.splice(index, 1)
                }
            }
        }
        var conditionNextSteps = new Array();
            
        for (const [idx, nextStep] of rev_nextSteps.entries()) {
            if (nextStep.step_type == "condition") {
                if(!judge && nextStep.step_type == 'sign'){
                    judge = 'approved'
                }
                conditionNextSteps = conditionNextSteps.concat(await getNextSteps(instance, nextStep, judge, autoFormDoc, fields, showSkipStep, userId));
                // 移除条件节点
                rev_nextSteps.splice(idx, 1)
            }
        }
        rev_nextSteps = rev_nextSteps.concat(conditionNextSteps);
        //去除重复
        rev_nextSteps = _.compact(_.uniqBy(rev_nextSteps, 'id'));
    }

    return rev_nextSteps;
}

async function caculateNextStepsByEnterStepCondition(flow, flowVersionId, instance, step, autoFormDoc, fields, needRemoveSteps = {}, needAddSteps = {}, userId) {
    if (false === step.always_enter_step) {
        const stepId = step.id;
        // 获取step处理人
        const users = await HandlersManager.getHandlers(instance._id, stepId, userId);
        const spaceId = instance.space;
        const approvers = await Promise.all(users.map(user => WorkflowManager.getFormulaUserObject(spaceId, user)));

        let enterStepCondition = step.enter_step_condition;
        const fieldValues = await UUFlowManager.initFormulaValues(instance, autoFormDoc);
        fieldValues.step = {
            ...step,
            approvers
        };
        fieldValues.loginUserId = userId; // 当前登录用户
        if (!UUFlowManager.isAmisFormula(enterStepCondition)) {
            throw new Error('Not Amis Formula, Contact Admin Please.')
        }
        let result = UUFlowManager.calculateConditionWithAmis(fieldValues, enterStepCondition);
        if (false === result) {
            needRemoveSteps[step.id] = step;
            if (step.lines && step.lines.length > 0) {
                const toStepIds = step.lines.map(function(s){ return s.to_step});
                await Promise.all(toStepIds.map(async (stepId) => {
                    const s = getStep(flow, flowVersionId, stepId);
                    if (s) {
                        await caculateNextStepsByEnterStepCondition(flow, flowVersionId, instance, s, autoFormDoc, fields, needRemoveSteps, needAddSteps, userId);
                    }
                }));
            }
        } else {
            needAddSteps[step.id] = step;
        }
    } else {
        needAddSteps[step.id] = step;
    }
}


router.post('/api/workflow/v2/nextStep', requireAuthentication, async function (req, res) {
    try {
        const userSession = req.user;
        const { userId } = userSession;
        const { instanceId, flowId, step, judge, values, flowVersionId } = req.body;
        const flow = await objectql.getObject('flows').findOne(flowId);
        const instance = await objectql.getObject('instances').findOne(instanceId);
        
        const resNextSteps = await getNextSteps(flow, flowVersionId, instance, step, judge, values, null, null, userId);
        res.status(200).send({
            'nextSteps': resNextSteps
        });
    } catch (error) {
        console.error(error);
        res.status(200).send({
            error: error.message
        });
    }
});

const calcSteps = async function(instance, flow, flowVersionId, formFields, values, step, track){
	if (!step) {
		step = getStep(flow, flowVersionId, instance.traces[0].step);
	}
	if (!track) {
		track = []
	}

	var _steps = [];
	var judge = 'approved';
	
	_steps.push(step);
	try {
        var nextSteps = await getNextSteps(flow, flowVersionId, instance, step, judge, values, null, null, instance.submitter);
        const recursiveResults = await Promise.all(nextSteps.map(async (nextStep) => {
            try {
                if (!_.includes(track, nextStep._id)) {
                    return await calcSteps(instance, flow, flowVersionId, formFields, values, nextStep, track.concat(_.map(_steps, '_id')));
                }
            } catch (e) {
                console.log(e)
            }
            return [];
        }));
        _steps = _steps.concat(_.flatten(recursiveResults));
    } catch (error) {
        console.log(error)
    }
	_steps = _.compact(_.uniqBy(_steps, 'id'));
	return _steps;
}

router.post('/api/workflow/v2/nextSteps', requireAuthentication, async function (req, res) {
    try {
        const userSession = req.user;
        const { instanceId, flowId, values, flowVersionId } = req.body;
        const flow = await objectql.getObject('flows').findOne(flowId);
        const instance = await objectql.getObject('instances').findOne(instanceId);
        const form = await objectql.getObject('forms').findOne(instance.form);
        const { fields } = await UUFlowManager.getFormVersion(form, instance.form_version);
        const resNextSteps = await calcSteps(instance, flow, flowVersionId, fields, values);
        res.status(200).send({
            'nextSteps': resNextSteps
        });
    } catch (error) {
        console.error(error);
        res.status(200).send({
            error: error.message
        });
    }
})

router.get('/api/workflow/v2/get_instance_steps/:instanceId', requireAuthentication, async function (req, res) {
    try {
        const { instanceId } = req.params;
        if(instanceId === 'none'){
            return res.status(200).send({ status: 0 });
        }
        const instance = await objectql.getObject('instances').findOne(instanceId);
        if(!instance){
            return res.status(200).send({
                error: 'not find instance'
            });
        }
        return res.status(200).send({ status: 0, data: {
            step_approve: instance.step_approve,
            skip_steps: instance.skip_steps
        } });
    } catch (error) {
        console.error(error);
        res.status(200).send({
            error: error.message
        });
    }
})

router.post('/api/workflow/v2/set_instance_steps', requireAuthentication, async function (req, res) {
    try {
        const userSession = req.user;
        const { instanceId, stepId, selected, handler } = req.body;
        if(instanceId === 'none'){
            return res.status(200).send({ status: 0 });
        }
        const instance = await objectql.getObject('instances').findOne(instanceId);
        if(!instance){
            return res.status(200).send({
                error: 'not find instance'
            });
        }
        if(instance.state != 'draft'){
            return res.status(200).send({
                error: 'not allow'
            });
        }
        const doc = {
            ['step_approve.' + stepId]: handler
        };
        if(selected){
            doc.$pull = {
                skip_steps: stepId
            }
        }else{
            doc.$push = {
                skip_steps: stepId
            }
        }
        await objectql.getObject('instances').directUpdate(instanceId, doc);

        return res.status(200).send({ status: 0 });
    } catch (error) {
        console.error(error);
        res.status(200).send({
            error: error.message
        });
    }
})

exports.default = router;