package cn.stylefeng.workflow.instance.core.actions.subflow;


import cn.hutool.core.util.ObjectUtil;
import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException;
import cn.stylefeng.workflow.api.entity.design.Node;
import cn.stylefeng.workflow.api.entity.instance.RuntimeNode;
import cn.stylefeng.workflow.api.enums.*;
import cn.stylefeng.workflow.api.pojo.InstanceContext;
import cn.stylefeng.workflow.api.pojo.InstanceVariable;
import cn.stylefeng.workflow.api.pojo.SubFlowStatusContext;
import cn.stylefeng.workflow.api.pojo.config.node.subflow.NodeSubFlow;
import cn.stylefeng.workflow.design.api.NodeSubFlowServiceApi;
import cn.stylefeng.workflow.instance.api.InstanceVariableApi;
import cn.stylefeng.workflow.instance.api.WorkflowControlApi;
import cn.stylefeng.workflow.instance.api.exceptions.InstanceExceptionEnum;
import cn.stylefeng.workflow.instance.api.node.NodeAction;
import cn.stylefeng.workflow.instance.api.pojo.NodeActionResult;
import cn.stylefeng.workflow.instance.api.pojo.request.WorkflowStartRequest;
import cn.stylefeng.workflow.instance.core.actions.subflow.factory.SubFlowStatusContextFactory;
import cn.stylefeng.workflow.instance.modular.manage.pojo.request.RuntimeNodeRequest;
import cn.stylefeng.workflow.instance.modular.manage.service.RuntimeNodeService;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
@Slf4j
public class SubFlowNodeAction extends NodeAction {

    @Resource
    private NodeSubFlowServiceApi nodeSubFlowServiceApi;

    @Resource
    private RuntimeNodeService runtimeNodeService;

    @Resource
    private WorkflowControlApi workflowControlApi;

    @Resource
    private InstanceVariableApi variableApi;

    @Override
    public NodeTypeEnum getNodeType() {
        return NodeTypeEnum.SUB_PROCESS;
    }

    @Override
    public NodeActionResult doAction(Node nodeInfo, InstanceContext instanceContext) {

        RuntimeNode runtimeNodeInfo = this.getRuntimeNodeInfo(nodeInfo, instanceContext);

        NodeSubFlow nodeSubFlowDetail = nodeSubFlowServiceApi.getNodeSubFlowDetail(nodeInfo.getNodeId());

        Map<Long, SubFlowStatusContext> currentRuntimeNodeStatusContext = this.getCurrentNodeSubFlowStatusContext(runtimeNodeInfo, nodeSubFlowDetail, instanceContext);



        boolean cycleCountFinishFlag = getCounterFinishedFlag(nodeSubFlowDetail, currentRuntimeNodeStatusContext);
        boolean deptCounterSignFinishFlag = getDeptCounterSignFinishedFlag(nodeSubFlowDetail, currentRuntimeNodeStatusContext);

        if (cycleCountFinishFlag && deptCounterSignFinishFlag) {

            runtimeNodeInfo.setNodeStatus(NodeStatusEnum.FINISH.getStatus());
            super.updateRuntimeNodeStatus(runtimeNodeInfo);

            List<Node> nextNodeList = super.calcNextNodeList(nodeInfo, instanceContext);
            return new NodeActionResult(runtimeNodeInfo, nextNodeList, instanceContext.getInstanceVariable());
        }

        this.basedDeptCounterSignStartSubFlow(currentRuntimeNodeStatusContext, instanceContext, runtimeNodeInfo, nodeSubFlowDetail);

        return new NodeActionResult(runtimeNodeInfo, null, instanceContext.getInstanceVariable());
    }

    private RuntimeNode getRuntimeNodeInfo(Node nodeInfo, InstanceContext instanceContext) {

        RuntimeNodeRequest runtimeNodeRequest = new RuntimeNodeRequest();
        runtimeNodeRequest.setInstanceId(instanceContext.getRuntimeInstance().getInstanceId());
        runtimeNodeRequest.setNodeId(nodeInfo.getNodeId());
        List<RuntimeNode> runtimeNodeList = runtimeNodeService.findList(runtimeNodeRequest);

        if (runtimeNodeList.size() > 1) {
            throw new ServiceException(InstanceExceptionEnum.RUNTIME_NODE_COUNT_ERROR);
        }

        if (ObjectUtil.isEmpty(runtimeNodeList)) {
            RuntimeNode runtimeNode = super.createRuntimeNode(nodeInfo, instanceContext);
            runtimeNode.setNodeStatus(NodeStatusEnum.RUNNING.getStatus());
            return super.saveRuntimeNode(runtimeNode);
        }

        return runtimeNodeList.get(0);
    }

    private Map<Long, SubFlowStatusContext> getCurrentNodeSubFlowStatusContext(RuntimeNode runtimeNode, NodeSubFlow nodeSubFlow, InstanceContext instanceContext) {

        Map<Long, SubFlowStatusContext> currentNodeSubFlowStatusContext = null;

        InstanceVariable currentVariables = instanceContext.getInstanceVariable();

        Map<Long, Map<Long, SubFlowStatusContext>> totalSubFlowContext = currentVariables.getSubFlowStatusContext();

        if (ObjectUtil.isEmpty(totalSubFlowContext)) {
            totalSubFlowContext = new HashMap<>();
            currentNodeSubFlowStatusContext = SubFlowStatusContextFactory.firstCreateSubFlowStatusContexts(runtimeNode, nodeSubFlow, instanceContext);
            totalSubFlowContext.put(runtimeNode.getRuNodeId(), currentNodeSubFlowStatusContext);
        }

        else {
            currentNodeSubFlowStatusContext = totalSubFlowContext.get(runtimeNode.getRuNodeId());

            Map<Long, SubFlowStatusContext> newCurrentRuntimeNodeStatusContext = new HashMap<>();

            for (Map.Entry<Long, SubFlowStatusContext> subFlowStatusContextEntry : currentNodeSubFlowStatusContext.entrySet()) {
                SubFlowStatusContext thisContext = subFlowStatusContextEntry.getValue();
                thisContext.setCycleNumberIndex(thisContext.getCycleNumberIndex() + 1);

                Long deptCounterSignOrgId = thisContext.getDeptCounterSignOrgId();
                if (deptCounterSignOrgId == null) {
                    thisContext.setSubFlowInstanceId(IdWorker.getId());
                }
                newCurrentRuntimeNodeStatusContext.put(thisContext.getSubFlowInstanceId(), thisContext);
            }

            currentNodeSubFlowStatusContext = newCurrentRuntimeNodeStatusContext;
            totalSubFlowContext.put(runtimeNode.getRuNodeId(), currentNodeSubFlowStatusContext);
        }

        currentVariables.setSubFlowStatusContext(totalSubFlowContext);
        variableApi.updateVariableJson(instanceContext);

        return currentNodeSubFlowStatusContext;
    }

    private boolean getCounterFinishedFlag(NodeSubFlow nodeSubFlowConfig, Map<Long, SubFlowStatusContext> currentRuntimeNodeStatusContext) {

        if (CycleTypeEnum.CYCLE_WHILE_TRUE.getCode().equals(nodeSubFlowConfig.getCycleType())) {
            return false;
        }

        SubFlowStatusContext subFlowStatusContext = currentRuntimeNodeStatusContext.values().iterator().next();

        if (subFlowStatusContext.getCycleNumberIndex() >= nodeSubFlowConfig.getCycleNumber()) {
            return true;
        }

        return false;
    }

    private boolean getDeptCounterSignFinishedFlag(NodeSubFlow nodeSubFlowConfig, Map<Long, SubFlowStatusContext> currentRuntimeNodeStatusContext) {

        if (Boolean.FALSE.equals(nodeSubFlowConfig.getDeptCounterSignFlag())) {
            return true;
        }

        for (Map.Entry<Long, SubFlowStatusContext> entry : currentRuntimeNodeStatusContext.entrySet()) {
            SubFlowStatusContext subFlowStatusContext = entry.getValue();

            if (InstanceStatusEnum.PROCESSING.getStatus().equals(subFlowStatusContext.getSubFlowInstanceStatus())) {
                return false;
            }
        }

        return true;
    }

    private void basedDeptCounterSignStartSubFlow(Map<Long, SubFlowStatusContext> subFlowContext, InstanceContext instanceContext, RuntimeNode runtimeNodeInfo, NodeSubFlow nodeSubFlowDetail) {


        for (Map.Entry<Long, SubFlowStatusContext> subFlowStatusContextEntry : subFlowContext.entrySet()) {

            Long subFlowInstanceId = subFlowStatusContextEntry.getKey();
            SubFlowStatusContext subFlowStatusContext = subFlowStatusContextEntry.getValue();

            if (subFlowStatusContext.getDeptCounterSignOrgId() != null && subFlowStatusContext.getCycleNumberIndex() > 0) {
                break;
            }

            startSingleSubFlow(subFlowStatusContext, instanceContext, runtimeNodeInfo, nodeSubFlowDetail);
        }

    }

    private void startSingleSubFlow(SubFlowStatusContext subFlowStatusContext, InstanceContext instanceContext, RuntimeNode runtimeNodeInfo, NodeSubFlow nodeSubFlowDetail) {

        WorkflowStartRequest workflowStartRequest = new WorkflowStartRequest();

        workflowStartRequest.setFlowInstanceId(subFlowStatusContext.getSubFlowInstanceId());

        workflowStartRequest.setApplicant(subFlowStatusContext.getSubFlowApplicantId());

        workflowStartRequest.setApplicantOrgId(subFlowStatusContext.getDeptCounterSignOrgId());

        workflowStartRequest.setParentInstanceId(instanceContext.getRuntimeInstance().getInstanceId());
        workflowStartRequest.setParentNodeId(runtimeNodeInfo.getNodeId());
        workflowStartRequest.setParamSendParentFlag(nodeSubFlowDetail.getParamSendParentFlag());

        workflowStartRequest.setDefinitionId(nodeSubFlowDetail.getSubDefinitionId());
        workflowStartRequest.setWorkflowStartType(WorkflowStartTypeEnum.API_START.getCode());

        if (Boolean.TRUE.equals(nodeSubFlowDetail.getParamSendSubFlag())) {
            InstanceVariable current = instanceContext.getInstanceVariable();

            InstanceVariable tempInstanceVariable = new InstanceVariable();
            tempInstanceVariable.setFormData(current.getFormData());
            tempInstanceVariable.setCustomVueFormData(current.getCustomVueFormData());
            workflowStartRequest.setInstanceVariable(tempInstanceVariable);
        } else {
            workflowStartRequest.setInstanceVariable(new InstanceVariable());
        }

        workflowControlApi.start(workflowStartRequest);
    }

}
