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


import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.stylefeng.roses.kernel.rule.constants.TreeConstants;
import cn.stylefeng.workflow.api.NodeApi;
import cn.stylefeng.workflow.api.entity.design.Node;
import cn.stylefeng.workflow.api.entity.instance.RuntimeInstance;
import cn.stylefeng.workflow.api.entity.instance.RuntimeNode;
import cn.stylefeng.workflow.api.enums.InstanceStatusEnum;
import cn.stylefeng.workflow.api.enums.ModelEventTypeEnum;
import cn.stylefeng.workflow.api.enums.NodeTypeEnum;
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.event.api.EventPublishApi;
import cn.stylefeng.workflow.instance.api.InstanceVariableApi;
import cn.stylefeng.workflow.instance.api.RuntimeNodeApi;
import cn.stylefeng.workflow.instance.api.node.NodeAction;
import cn.stylefeng.workflow.instance.api.pojo.NodeActionResult;
import cn.stylefeng.workflow.instance.modular.manage.service.RuntimeInstanceService;
import cn.stylefeng.workflow.instance.modular.manage.service.RuntimeTimerService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.Map;

@Service
public class EndNodeAction extends NodeAction {

    @Resource
    private RuntimeInstanceService runtimeInstanceService;

    @Resource
    private RuntimeTimerService runtimeTimerService;

    @Resource
    private InstanceVariableApi instanceVariableApi;

    @Resource
    private NodeApi nodeApi;

    @Resource
    private EventPublishApi eventPublishApi;

    @Resource
    private RuntimeNodeApi runtimeNodeApi;

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

    @Override
    @Transactional(rollbackFor = Exception.class)
    public NodeActionResult doAction(Node nodeInfo, InstanceContext instanceContext) {

        RuntimeNode runtimeNode = super.createAndSaveRuntimeNode(nodeInfo, instanceContext);

        RuntimeInstance runtimeInstance = instanceContext.getRuntimeInstance();
        runtimeInstance.setInstanceStatus(InstanceStatusEnum.FINISH.getStatus());
        runtimeInstance.setCompletionTime(new Date());
        runtimeInstanceService.updateById(runtimeInstance);

        eventPublishApi.publishModelEvent(ModelEventTypeEnum.END, instanceContext);

        runtimeTimerService.removeAllInstanceTimer(instanceContext.getRuntimeInstance().getInstanceId());

        Long parentInstanceId = instanceContext.getRuntimeInstance().getParentInstanceId();

        if (parentInstanceId == null || TreeConstants.DEFAULT_PARENT_ID.equals(parentInstanceId)) {
            return new NodeActionResult(runtimeNode, null, instanceContext.getInstanceVariable());
        }

        else {
            return processParentInstanceResult(parentInstanceId, instanceContext);
        }
    }

    private NodeActionResult processParentInstanceResult(Long parentInstanceId, InstanceContext currentInstanceContext) {

        InstanceVariable currentEndInstanceVariable = currentInstanceContext.getInstanceVariable();

        InstanceVariable parentInstanceVariables = instanceVariableApi.getInstanceVariableValue(parentInstanceId);

        Boolean paramSendParentFlag = currentInstanceContext.getRuntimeInstance().getParamSendParentFlag();

        if (Boolean.TRUE.equals(paramSendParentFlag)) {

            this.mergeInstanceVariable(currentEndInstanceVariable, parentInstanceVariables);
        }

        this.updateSubFlowInstanceStatus(parentInstanceId, currentInstanceContext, parentInstanceVariables);

        instanceVariableApi.updateInstanceVariable(parentInstanceId, parentInstanceVariables);

        Long parentNodeId = currentInstanceContext.getRuntimeInstance().getParentNodeId();
        Node parentNodeInfo = nodeApi.getNodeInfo(parentNodeId);

        NodeActionResult nodeActionResult = new NodeActionResult(null, ListUtil.list(false, parentNodeInfo), currentEndInstanceVariable);
        nodeActionResult.setChangeInstancePath(true);
        nodeActionResult.setParentInstanceId(parentInstanceId);

        return nodeActionResult;
    }

    private void updateSubFlowInstanceStatus(Long parentInstanceId, InstanceContext currentInstanceContext, InstanceVariable parentInstanceVariables) {

        Long parentNodeId = currentInstanceContext.getRuntimeInstance().getParentNodeId();
        RuntimeNode parentRuntimeNodeId = runtimeNodeApi.getRuntimeNodeByNodeId(parentInstanceId, parentNodeId);

        Map<Long, Map<Long, SubFlowStatusContext>> subFlowStatusContext = parentInstanceVariables.getSubFlowStatusContext();
        Map<Long, SubFlowStatusContext> longSubFlowStatusContextMap = subFlowStatusContext.get(parentRuntimeNodeId.getRuNodeId());
        SubFlowStatusContext parentSubFlowContext = longSubFlowStatusContextMap.get(currentInstanceContext.getRuntimeInstance().getInstanceId());
        parentSubFlowContext.setSubFlowInstanceStatus(InstanceStatusEnum.FINISH.getStatus());
    }

    private void mergeInstanceVariable(InstanceVariable currentEndInstanceVariable, InstanceVariable parentInstanceVariable) {

        Map<String, Map<String, Object>> currentFormData = currentEndInstanceVariable.getFormData();
        Map<String, Map<String, Object>> parentFormData = parentInstanceVariable.getFormData();
        if (ObjectUtil.isNotEmpty(currentFormData) && ObjectUtil.isNotEmpty(parentFormData)) {
            for (Map.Entry<String, Map<String, Object>> entry : currentFormData.entrySet()) {
                String currentFormDefinitionId = entry.getKey();
                Map<String, Object> currentData = currentFormData.get(currentFormDefinitionId);
                Map<String, Object> parentFormDefinitionData = parentFormData.get(currentFormDefinitionId);
                if (ObjectUtil.isNotEmpty(currentData) && ObjectUtil.isNotEmpty(parentFormDefinitionData)) {
                    parentFormDefinitionData.putAll(currentData);
                }
            }
        }

        Map<String, Object> customVueFormData = currentEndInstanceVariable.getCustomVueFormData();
        Map<String, Object> parentCustomVueFormData = parentInstanceVariable.getCustomVueFormData();
        if (ObjectUtil.isNotEmpty(customVueFormData) && ObjectUtil.isNotEmpty(parentCustomVueFormData)) {
            parentCustomVueFormData.putAll(customVueFormData);
        }

    }

}
