package cn.axzo.workflow.core.service.support;

import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.service.support.forecast.AbstractForecast;
import cn.axzo.workflow.core.service.support.forecast.Forecast;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.StartEvent;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.ResolvableType;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Nullable;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS;


/**
 * 审批实例的运行节点预测/推断
 * <p>
 * 一个流程定义中所有的节点都是 FlowElement 类型,而 FlowElement 是引擎中的一个基类, 如果要完整推测审批流程执行顺序不亚于重写一套流程执行引擎
 * 所以这里仅按照业务所需的节点类型进行处理.并且每个节点都未处理出口是多个的情况,目前仅支持单出口!
 * <p>
 * 全量的接入类型如下:
 * <pre>
 * FlowElement (org.flowable.bpmn.model)
 * |-- SequenceFlow (org.flowable.bpmn.model)
 * |-- DataStoreReference (org.flowable.bpmn.model)
 * |-- DataObject (org.flowable.bpmn.model)
 *     |-- ValuedDataObject (org.flowable.bpmn.model)
 *         |-- StringDataObject (org.flowable.bpmn.model)
 *         |-- LongDataObject (org.flowable.bpmn.model)
 *         |-- BooleanDataObject (org.flowable.bpmn.model)
 *         |-- DoubleDataObject (org.flowable.bpmn.model)
 *         |-- JsonDataObject (org.flowable.bpmn.model)
 *         |-- DateDataObject (org.flowable.bpmn.model)
 *         |-- IntegerDataObject (org.flowable.bpmn.model)
 * |-- FlowNode (org.flowable.bpmn.model)
 *     |-- Activity (org.flowable.bpmn.model)
 *         |-- CallActivity (org.flowable.bpmn.model)
 *         |-- SubProcess (org.flowable.bpmn.model)
 *         |-- Transaction (org.flowable.bpmn.model)
 *         |-- EventSubProcess (org.flowable.bpmn.model)
 *         |-- AdhocSubProcess (org.flowable.bpmn.model)
 *         |-- Task (org.flowable.bpmn.model)
 *              |-- ScriptTask (org.flowable.bpmn.model)
 *              |-- ManualTask (org.flowable.bpmn.model)
 *              |-- ReceiveTask (org.flowable.bpmn.model)
 *              |-- BusinessRuleTask (org.flowable.bpmn.model)
 *              |-- TaskWithFieldExtensions (org.flowable.bpmn.model)
 *                  |-- SendTask (org.flowable.bpmn.model)
 *                  |-- ServiceTask (org.flowable.bpmn.model)
 *                      |-- AlfrescoScriptTask (org.flowable.bpmn.model.alfresco)
 *                      |-- HttpServiceTask (org.flowable.bpmn.model)
 *                      |-- CaseServiceTask (org.flowable.bpmn.model)
 *                      |-- ExternalWorkerServiceTask (org.flowable.bpmn.model)
 *                      |-- SendEventServiceTask (org.flowable.bpmn.model)
 *                      |-- AlfrescoMailTask (org.flowable.bpmn.model.alfresco)
 *              |-- UserTask (org.flowable.bpmn.model)
 *                  |-- AlfrescoUserTask (org.flowable.bpmn.model.alfresco)
 *     |-- Gateway (org.flowable.bpmn.model)
 *         |-- ExclusiveGateway (org.flowable.bpmn.model)
 *         |-- ComplexGateway (org.flowable.bpmn.model)
 *         |-- ParallelGateway (org.flowable.bpmn.model)
 *         |-- InclusiveGateway (org.flowable.bpmn.model)
 *         |-- EventGateway (org.flowable.bpmn.model)
 *     |-- Event (org.flowable.bpmn.model)
 *         |-- EndEvent (org.flowable.bpmn.model)
 *         |-- BoundaryEvent (org.flowable.bpmn.model)
 *         |-- ThrowEvent (org.flowable.bpmn.model)
 *         |-- StartEvent (org.flowable.bpmn.model)
 *             |-- AlfrescoStartEvent (org.flowable.bpmn.model.alfresco)
 *         |-- IntermediateCatchEvent (org.flowable.bpmn.model)
 *  </pre>
 *
 * @author wangli
 * @since 2023/10/9 17:38
 */
@Service
public class FlowNodeForecastService implements InitializingBean {

    public final static Map<Class<?>, AbstractForecast<? extends FlowElement>> FORECAST_MAP = new HashMap<>();
    @Resource
    private RepositoryService repositoryService;
    @Resource
    private HistoryService historyService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private List<Forecast<? extends FlowElement>> forecasts;

    /**
     * 执行运行中的流程预测
     * <p>
     * 已完成的流程可以直接查询流程审批记录就行
     *
     * @param processInstanceId 指定运行时的流程实例 ID
     * @param instance          外部传入流程实例 (与另外一个参数必须二选一)
     */
    public List<FlowElement> performProcessForecasting(@Nullable String processInstanceId,
                                                       @Nullable ProcessInstance instance) {
        if (Objects.nonNull(instance)) {
            processInstanceId = instance.getProcessInstanceId();
        } else if (!StringUtils.hasLength(processInstanceId)) {
            throw new WorkflowEngineException(PROCESS_INSTANCE_NOT_EXISTS);
        } else {
            instance = runtimeService.createProcessInstanceQuery()
                    .processInstanceId(processInstanceId)
                    //                    .includeProcessVariables()
                    .singleResult();
        }

        BpmnModel bpmnModel = repositoryService.getBpmnModel(instance.getProcessDefinitionId());

        // 保持推测出来的节点执行顺序的容器
        List<FlowElement> orderedNodes = new ArrayList<>();
        // 流程定义中所有的FlowElement
        Collection<FlowElement> flowElements = bpmnModel.getMainProcess().getFlowElements();

        // 开始节点
        findStartNode(flowElements).ifPresent(startNode -> {
            addOrderFlowNodes(orderedNodes, startNode);
        });

        startForecasting(orderedNodes, processInstanceId);
        return orderedNodes;
    }

    private void startForecasting(List<FlowElement> orderNodes, String processInstanceId) {
        getLastNode(orderNodes).ifPresent(flowElement -> forecasts.forEach(i -> {
            if (i.support(flowElement)) {
                List<FlowElement> list = i.nextFlowElement(flowElement, processInstanceId);
                if (!CollectionUtils.isEmpty(list)) {
                    addOrderFlowNodes(orderNodes, list.get(0));
                    startForecasting(orderNodes, processInstanceId);
                }
            }
        }));
    }

    /**
     * 查找开始节点
     * <p>
     * 由于一个定义中, 是允许存在多个 StartEvent, 但至少有一个 StartEvent 节点. 但我们目前的业务还未用到多个开始事件, 所以未做过多的筛选动作
     *
     * @param flowElements 全量的 FlowElement
     * @return
     */
    private Optional<StartEvent> findStartNode(Collection<FlowElement> flowElements) {
        return Optional.ofNullable(flowElements.stream().filter(StartEvent.class::isInstance).findFirst()
                .map(StartEvent.class::cast)
                .orElseThrow(() -> new IllegalArgumentException("非法的参数, 正确的流程定义一定有一个 StartEvent 节点")));
    }


    private void addOrderFlowNodes(List<FlowElement> orderFlowNodes, FlowElement flowElement) {
        if (Objects.nonNull(flowElement)) {
            orderFlowNodes.add(flowElement);
        }
    }

    private Optional<FlowElement> getLastNode(List<FlowElement> orderFlowNodes) {
        if (CollectionUtils.isEmpty(orderFlowNodes)) {
            return Optional.empty();
        }
        return Optional.of(orderFlowNodes.get(orderFlowNodes.size() - 1));
    }

    @Override
    public void afterPropertiesSet() {
        forecasts.forEach(i -> {
            Class<?> rawClass = ResolvableType.forClass(i.getClass(), Forecast.class)
                    .getSuperType().getGenerics()[0].getRawClass();
            FORECAST_MAP.put(rawClass, (AbstractForecast<? extends FlowElement>) i);
        });
    }
}
