package cn.axzo.workflow.core.common.utils;

import cn.axzo.workflow.common.enums.BpmnFlowNodeMode;
import cn.axzo.workflow.common.enums.BpmnFlowNodeType;
import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNode;
import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNodeProperty;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.BpmnAutoLayout;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.EndEvent;
import org.flowable.bpmn.model.ExclusiveGateway;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.FlowableListener;
import org.flowable.bpmn.model.MultiInstanceLoopCharacteristics;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.SequenceFlow;
import org.flowable.bpmn.model.ServiceTask;
import org.flowable.bpmn.model.StartEvent;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.TaskListener;
import org.flowable.engine.repository.Model;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;

import static cn.axzo.workflow.common.constant.BpmnConstants.AND_SIGN_EXPRESSION;
import static cn.axzo.workflow.common.constant.BpmnConstants.END_EVENT_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO;
import static cn.axzo.workflow.common.constant.BpmnConstants.OR_SIGN_EXPRESSION_ONE_PASS;
import static cn.axzo.workflow.core.common.code.ConvertorRespCode.CONVERTOR_META_DATA_FORMAT_ERROR;
import static cn.axzo.workflow.core.common.code.ConvertorRespCode.CONVERTOR_UNKNOW_NODE_TYPE;
import static org.flowable.bpmn.model.ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION;
import static org.flowable.engine.delegate.BaseExecutionListener.EVENTNAME_END;
import static org.flowable.engine.delegate.BaseExecutionListener.EVENTNAME_START;

/**
 * 该工具是以前枢智版本使用，后续迭代都通过 BpmnJsonConverterUtil 该工具操作
 */
@Slf4j
@Deprecated
public class BpmTransformUtil {
    public static byte[] transformBpmnJsonToXml(BpmnJsonNode bpmnJson, Model model) {
        return transformBpmnJsonToXml(bpmnJson, model, null);
    }

    public static byte[] transformBpmnJsonToXml(BpmnJsonNode bpmnJson, Model model, String category) {
        BpmnModel bpmnModel = new BpmnModel();
        Process process = new Process();
        bpmnModel.addProcess(process);

        process.setId(model.getKey());
        process.setName(model.getName());

        if (BpmnFlowNodeType.NODE_STARTER.equals(bpmnJson.getType())) {
            process.addFlowElement(createStartEvent(bpmnJson.getId(), Objects.nonNull(bpmnJson.getProperty()) ?
                    bpmnJson.getProperty().getFormKey() : ""));
        }

        List<SequenceFlow> sequenceFlows = Lists.newArrayList();
        Map<String, BpmnJsonNode> childNodeMap = new HashMap<>();

        String lastNode = null;
        try {
            lastNode = create(bpmnJson.getId(), bpmnJson.getChildren(), process, bpmnModel, sequenceFlows,
                    childNodeMap);
        } catch (WorkflowEngineException e) {
            throw e;
        } catch (Exception e) {
            throw new WorkflowEngineException(CONVERTOR_META_DATA_FORMAT_ERROR);
        }
        EndEvent endEvent = createEndEvent();
        process.addFlowElement(endEvent);
        process.addFlowElement(sequenceFlow(lastNode, endEvent.getId(), sequenceFlows, childNodeMap, process));


        if (StringUtils.isNotBlank(category)) {
            bpmnModel.setTargetNamespace(category);
        }
        new BpmnAutoLayout(bpmnModel).execute();
        byte[] xmlMeta = new BpmnXMLConverter().convertToXML(bpmnModel);
        String xmlResult = new String(xmlMeta);
        return xmlMeta;

    }

    private static String id(String prefix) {
        return prefix + "_" + UUID.randomUUID().toString().replace("-", "").toLowerCase();
    }

    private static ServiceTask serviceTask(String name) {
        ServiceTask serviceTask = new ServiceTask();
        serviceTask.setName(name);
        return serviceTask;
    }

    public static SequenceFlow sequenceFlow(String from, String to, List<SequenceFlow> sequenceFlows, Map<String,
            BpmnJsonNode> childNodeMap, Process process) {
        SequenceFlow flow = new SequenceFlow();
        String sequenceFlowId = id("sequenceFlow");
        if (process.getFlowElement(from) != null && process.getFlowElement(from) instanceof ExclusiveGateway) {
            BpmnJsonNode childNode = childNodeMap.get(to);
            if (childNode != null) {
                String parentId = childNode.getParentId();
                if (StringUtils.isNotBlank(parentId)) {
                    BpmnJsonNode parentNode = childNodeMap.get(parentId);
                    if (parentNode != null) {
                        if (BpmnFlowNodeType.NODE_CONDITION.equals(parentNode.getType())) {
                            sequenceFlowId = parentNode.getId();
                            flow.setName(parentNode.getName());
                            if (!ObjectUtils.isEmpty(parentNode.getProperty()) && !Boolean.TRUE.equals(parentNode.getProperty().getDefaultBranch())) {
                                //解析条件表达式
                                //                                StringBuffer conditionExpression = new StringBuffer();
                                //                                conditionExpression.append("${ ")
                                //                                        .append("var:eq('")
                                //                                        .append(parentNode.getProperty()
                                //                                        .getConditionBranchKey())
                                //                                        .append("', ")
                                //                                        .append(parentNode.getProperty()
                                //                                        .getConditionBranchValue())
                                //                                        .append(")");
                                //                                conditionExpression.append(" }");
                                //
                                //                                flow.setConditionExpression(conditionExpression
                                //                                .toString());
                            }
                        }
                    }
                }
            }
        }
        flow.setId(sequenceFlowId);
        flow.setSourceRef(from);
        flow.setTargetRef(to);
        sequenceFlows.add(flow);
        return flow;
    }

    public static StartEvent createStartEvent(String id, String formKey) {
        StartEvent startEvent = new StartEvent();
        startEvent.setId(id);
        //        startEvent.setInitiator("applyUserId");
        if (Objects.nonNull(formKey) && !formKey.trim().isEmpty()) {
            startEvent.setFormKey(formKey);
        }
        return startEvent;
    }

    public static EndEvent createEndEvent() {
        EndEvent endEvent = new EndEvent();
        endEvent.setId(END_EVENT_ID);
        return endEvent;
    }


    public static String create(String fromId, BpmnJsonNode flowNode, Process process, BpmnModel bpmnModel,
                                List<SequenceFlow> sequenceFlows, Map<String, BpmnJsonNode> childNodeMap) throws InvocationTargetException, IllegalAccessException {
        String nodeType = flowNode.getType().getType();
        if (BpmnFlowNodeType.NODE_EXCLUSIVE_GATEWAY.isEqual(nodeType)) {
            return createExclusiveGatewayBuilder(fromId, flowNode, process, bpmnModel, sequenceFlows, childNodeMap);
        } else if (BpmnFlowNodeType.NODE_TASK.isEqual(nodeType)) {
            childNodeMap.put(flowNode.getId(), flowNode);
            flowNode.setIncoming(Collections.singletonList(fromId));
            String id = createTask(process, flowNode, sequenceFlows, childNodeMap);
            // 如果当前任务还有后续任务，则遍历创建后续任务
            BpmnJsonNode children = flowNode.getChildren();
            if (Objects.nonNull(children) && StringUtils.isNotBlank(children.getId())) {
                return create(id, children, process, bpmnModel, sequenceFlows, childNodeMap);
            } else {
                return id;
            }
        } else if (BpmnFlowNodeType.NODE_STARTER.isEqual(nodeType)) {
            childNodeMap.put(flowNode.getId(), flowNode);
            flowNode.setIncoming(Collections.singletonList(fromId));
            String id = createTask(process, flowNode, sequenceFlows, childNodeMap);
            // 如果当前任务还有后续任务，则遍历创建后续任务
            BpmnJsonNode children = flowNode.getChildren();
            if (Objects.nonNull(children) && StringUtils.isNotBlank(children.getId())) {
                return create(id, children, process, bpmnModel, sequenceFlows, childNodeMap);
            } else {
                return id;
            }
        } else {
            throw new WorkflowEngineException(CONVERTOR_UNKNOW_NODE_TYPE, nodeType);
        }
    }

    public static ExclusiveGateway createExclusiveGateWayEnd(String id) {
        ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
        exclusiveGateway.setId(id);
        return exclusiveGateway;
    }


    private static String createExclusiveGatewayBuilder(String formId, BpmnJsonNode flowNode, Process process,
                                                        BpmnModel bpmnModel, List<SequenceFlow> sequenceFlows,
                                                        Map<String, BpmnJsonNode> childNodeMap) throws InvocationTargetException, IllegalAccessException {
        childNodeMap.put(flowNode.getId(), flowNode);
        String name = flowNode.getName();
        String exclusiveGatewayId = flowNode.getId();
        ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
        exclusiveGateway.setId(exclusiveGatewayId);
        exclusiveGateway.setName(name);
        process.addFlowElement(exclusiveGateway);
        process.addFlowElement(sequenceFlow(formId, exclusiveGatewayId, sequenceFlows, childNodeMap, process));

        if (Objects.isNull(flowNode.getBranches()) && Objects.isNull(flowNode.getChildren())) {
            return exclusiveGatewayId;
        }
        List<BpmnJsonNode> branches = flowNode.getBranches();
        List<String> incoming = Lists.newArrayListWithCapacity(branches.size());
        List<Map> conditions = Lists.newCopyOnWriteArrayList();
        for (BpmnJsonNode element : branches) {
            if (!ObjectUtils.isEmpty(element.getProperty())) {
                Boolean typeElse = element.getProperty().getDefaultBranch();
                if (Boolean.TRUE.equals(typeElse)) {
                    exclusiveGateway.setDefaultFlow(element.getId());
                }
            }

            childNodeMap.put(element.getId(), element);
            BpmnJsonNode children = element.getChildren();
            String nodeName = element.getName();

            if (!ObjectUtils.isEmpty(element.getProperty()) && (Objects.isNull(children) || StringUtils.isBlank(children.getId()))) {

                incoming.add(exclusiveGatewayId);
                Map condition = new HashMap();

                //解析条件表达式
                //                StringBuffer conditionExpression = new StringBuffer();
                //                conditionExpression.append("${ ");
                //                conditionExpression.append("var:eq('" + element.getProperty().getConditionBranchKey
                //                () + "', " + element.getProperty().getConditionBranchValue() + ") ");
                //                conditionExpression.append(" }");
                //                condition.put("nodeName", nodeName);
                //                condition.put("expression", conditionExpression.toString());

                conditions.add(condition);
                continue;
            }
            // 只生成一个任务，同时设置当前任务的条件
            children.setIncoming(Collections.singletonList(exclusiveGatewayId));
            String identifier = create(exclusiveGatewayId, children, process, bpmnModel, sequenceFlows, childNodeMap);
            List<SequenceFlow> flows = sequenceFlows.stream().filter(flow -> StringUtils.equals(exclusiveGatewayId,
                            flow.getSourceRef()))
                    .collect(Collectors.toList());
            flows.stream().forEach(
                    e -> {
                        if (StringUtils.isBlank(e.getName()) && StringUtils.isNotBlank(nodeName)) {
                            e.setName(nodeName);
                        }
                        // 设置条件表达式
                        //                        if (Objects.isNull(e.getConditionExpression()) && StringUtils
                        //                        .isNotBlank(expression)) {
                        //                            e.setConditionExpression(expression);
                        //                        }
                    }
            );
            if (Objects.nonNull(identifier)) {
                incoming.add(identifier);
            }
        }


        BpmnJsonNode childNode = flowNode.getChildren();

        if (Objects.nonNull(childNode) && StringUtils.isNotBlank(childNode.getId())) {
            String parentId = childNode.getParentId();
            BpmnJsonNode parentChildNode = childNodeMap.get(parentId);
            if (BpmnFlowNodeType.NODE_EXCLUSIVE_GATEWAY.equals(parentChildNode.getType())) {
                String endExId = parentChildNode.getId() + "end";
                process.addFlowElement(createExclusiveGateWayEnd(endExId));
                if (incoming == null || incoming.isEmpty()) {
                    return create(exclusiveGatewayId, childNode, process, bpmnModel, sequenceFlows,
                            childNodeMap);
                } else {
                    // 所有 service task 连接 end exclusive gateway
                    childNode.setIncoming(incoming);
                    FlowElement flowElement = bpmnModel.getFlowElement(incoming.get(0));
                    // 1.0 先进行边连接, 暂存 nextNode
                    BpmnJsonNode nextNode = childNode.getChildren();
                    childNode.setChildren(null);
                    String identifier = endExId;
                    for (int i = 0; i < incoming.size(); i++) {
                        process.addFlowElement(sequenceFlow(incoming.get(i), identifier, sequenceFlows, childNodeMap,
                                process));
                    }

                    //  针对 gateway 空任务分支 添加条件表达式
                    if (!conditions.isEmpty()) {
                        FlowElement flowElement1 = bpmnModel.getFlowElement(identifier);
                        // 获取从 gateway 到目标节点 未设置条件表达式的节点
                        List<SequenceFlow> flows = sequenceFlows.stream().filter(
                                        flow -> StringUtils.equals(flowElement1.getId(), flow.getTargetRef()))
                                .filter(
                                        flow -> StringUtils.equals(flow.getSourceRef(), exclusiveGatewayId))
                                .collect(Collectors.toList());
                        flows.stream().forEach(sequenceFlow -> {
                            if (!conditions.isEmpty()) {
                                Map condition = conditions.get(0);
                                String nodeName = (String) condition.get("nodeName");
                                String expression = (String) condition.get("expression");

                                if (StringUtils.isBlank(sequenceFlow.getName()) && StringUtils
                                        .isNotBlank(nodeName)) {
                                    sequenceFlow.setName(nodeName);
                                }
                                // 设置条件表达式
                                if (Objects.isNull(sequenceFlow.getConditionExpression())
                                        && StringUtils.isNotBlank(expression)) {
                                    sequenceFlow.setConditionExpression(expression);
                                }
                                conditions.remove(0);
                            }
                        });

                    }

                    // 1.1 边连接完成后，在进行 nextNode 创建
                    if (StringUtils.isNotBlank(childNode.getId())) {
                        return create(identifier, childNode, process, bpmnModel, sequenceFlows,
                                childNodeMap);
                    } else {
                        return identifier;
                    }
                }
            }
        }
        return exclusiveGatewayId;
    }


    private static String createTask(Process process, BpmnJsonNode flowNode, List<SequenceFlow> sequenceFlows,
                                     Map<String, BpmnJsonNode> childNodeMap) {
        List<String> incoming = flowNode.getIncoming();
        // 自动生成id
        //        String id = id("serviceTask");
        String id = flowNode.getId();
        if (incoming != null && !incoming.isEmpty()) {
            UserTask userTask = new UserTask();
            userTask.setName(flowNode.getName());
            userTask.setId(id);
            process.addFlowElement(userTask);
            process.addFlowElement(sequenceFlow(incoming.get(0), id, sequenceFlows, childNodeMap, process));

            FlowableListener createTaskListener = new FlowableListener();
            createTaskListener.setEvent(TaskListener.EVENTNAME_ALL_EVENTS);
            createTaskListener.setImplementationType(IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);

            createTaskListener.setImplementation("${engineTaskEventListener}");
            List<FlowableListener> taskListeners = new ArrayList<>();
            taskListeners.add(createTaskListener);
            userTask.setTaskListeners(taskListeners);
            if ("root".equalsIgnoreCase(id)) {
            } else {
                List<FlowableListener> executionListeners = new ArrayList<>();
                createExecutionListener(EVENTNAME_START, executionListeners);
                // End 事件,主要想用于多实例节点保留一条历史的任务信息, 后续研究过程中, 发现通过更优的解决方案, 所以注释掉下面一行
                // createExecutionListener(EVENTNAME_END, executionListeners);
                userTask.setExecutionListeners(executionListeners);

                if (!ObjectUtils.isEmpty(flowNode.getProperty()) && Boolean.TRUE.equals(flowNode.getProperty().getIsMultiTask())) {
                    BpmnJsonNodeProperty property = flowNode.getProperty();
                    BpmnFlowNodeMode mode = property.getMultiMode();

                    MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics =
                            new MultiInstanceLoopCharacteristics();
                    // 审批人集合参数
                    multiInstanceLoopCharacteristics.setInputDataItem(INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO + userTask.getId());
                    // 迭代集合
                    multiInstanceLoopCharacteristics.setElementVariable("assigneeName");
                    // 并行
                    multiInstanceLoopCharacteristics.setSequential(false);
                    userTask.setAssignee("${assigneeName}");
                    // 设置多实例属性
                    userTask.setLoopCharacteristics(multiInstanceLoopCharacteristics);
                    if (BpmnFlowNodeMode.OR.getType().equals(mode.getType())) {
                        multiInstanceLoopCharacteristics.setCompletionCondition(OR_SIGN_EXPRESSION_ONE_PASS);
                    } else if (BpmnFlowNodeMode.AND.getType().equals(mode.getType())) {
                        multiInstanceLoopCharacteristics.setCompletionCondition(AND_SIGN_EXPRESSION);
                    }
                }
                // 设置审批人为空时,允许自动通过
                //                if (!ObjectUtils.isEmpty(flowNode.getProperty()) && flowNode.getProperty()
                //                .getAllowSkip()) {
                //                    userTask.setSkipExpression("${" + BPM_ALLOW_SKIP_USER_TASK + "}");
                //                }
                if (!ObjectUtils.isEmpty(flowNode.getProperty()) && StringUtils.isNotBlank(flowNode.getProperty().getFormKey())) {
                    userTask.setFormKey(flowNode.getProperty().getFormKey());
                }
            }
        }
        return id;
    }

    /**
     * 根据 Execution 事件类型, 创建 Execution 监听器
     *
     * @param eventName @see ExecutionListener
     * @return
     */
    private static void createExecutionListener(String eventName, List<FlowableListener> listeners) {
        FlowableListener executionListener = new FlowableListener();
        executionListener.setImplementationType(IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
        executionListener.setEvent(eventName);
        switch (eventName) {
            case EVENTNAME_START:
                executionListener.setImplementation("${engineExecutionStartListener}");
                break;
            case EVENTNAME_END:
                executionListener.setImplementation("${engineExecutionEndListener}");
                break;
            default:
                return;
        }
        listeners.add(executionListener);
    }


}
