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

import cn.axzo.workflow.common.constant.BpmnConstants;
import cn.axzo.workflow.common.enums.BpmnFlowNodeMode;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAdminPageReqVO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCancelDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCarbonCopyDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateWithFormDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceMyPageReqVO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.HistoricProcessInstanceSearchDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.common.model.request.category.CategorySearchDTO;
import cn.axzo.workflow.common.model.response.BpmPageResult;
import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessDefinitionVO;
import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceAdminPageItemVO;
import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstancePageItemVO;
import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceVO;
import cn.axzo.workflow.common.model.response.bpmn.process.HistoricProcessInstanceVO;
import cn.axzo.workflow.common.model.response.bpmn.process.ProcessNodeDetailVO;
import cn.axzo.workflow.common.model.response.category.CategoryItemVO;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.common.utils.BpmnCollectionUtils;
import cn.axzo.workflow.core.engine.cmd.CustomAbortProcessInstanceAsyncCmd;
import cn.axzo.workflow.core.engine.cmd.CustomAbortProcessInstanceCmd;
import cn.axzo.workflow.core.engine.cmd.CustomCancelProcessInstanceAsyncCmd;
import cn.axzo.workflow.core.engine.cmd.CustomCancelProcessInstanceCmd;
import cn.axzo.workflow.core.engine.cmd.CustomCarbonCopyUserSelectorCmd;
import cn.axzo.workflow.core.engine.cmd.CustomForecastUserTaskAssigneeCmd;
import cn.axzo.workflow.core.engine.listener.EngineExecutionStartListener;
import cn.axzo.workflow.core.service.BpmnProcessDefinitionService;
import cn.axzo.workflow.core.service.BpmnProcessInstanceService;
import cn.axzo.workflow.core.service.CategoryService;
import cn.axzo.workflow.core.service.ExtAxHiTaskInstService;
import cn.axzo.workflow.core.service.converter.BpmnHistoricProcessInstanceConverter;
import cn.axzo.workflow.core.service.converter.BpmnHistoricTaskInstanceConverter;
import cn.axzo.workflow.core.service.converter.BpmnProcessInstanceAdminPageItemConverter;
import cn.axzo.workflow.core.service.converter.BpmnProcessInstanceConverter;
import cn.axzo.workflow.core.service.converter.BpmnProcessInstancePageItemConverter;
import cn.axzo.workflow.core.service.support.FlowNodeForecastService;
import cn.axzo.workflow.core.service.support.ProcessGraphicService;
import cn.azxo.framework.common.utils.StringUtils;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.ReceiveTask;
import org.flowable.bpmn.model.ServiceTask;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.common.engine.impl.interceptor.CommandExecutor;
import org.flowable.engine.HistoryService;
import org.flowable.engine.ManagementService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.history.NativeHistoricProcessInstanceQuery;
import org.flowable.engine.impl.bpmn.behavior.MultiInstanceActivityBehavior;
import org.flowable.engine.impl.persistence.entity.ActivityInstanceEntity;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ActivityInstance;
import org.flowable.engine.runtime.NativeActivityInstanceQuery;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.runtime.ProcessInstanceBuilder;
import org.flowable.engine.runtime.ProcessInstanceQuery;
import org.flowable.form.api.FormInfo;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.task.api.Task;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Nullable;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import static cn.axzo.workflow.common.constant.BpmnConstants.AND_SIGN_EXPRESSION;
import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_ORG_RELATION;
import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_MODEL_CATEGORY;
import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_121;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_AGENT;
import static cn.axzo.workflow.common.constant.BpmnConstants.OLD_INTERNAL_INITIATOR;
import static cn.axzo.workflow.common.constant.BpmnConstants.PENDING_TEMPLATE_VARIABLE;
import static cn.axzo.workflow.common.constant.BpmnConstants.WORKFLOW_ENGINE_VERSION;
import static cn.axzo.workflow.common.enums.ApprovalMethodEnum.autoPassed;
import static cn.axzo.workflow.common.enums.ApprovalMethodEnum.autoRejection;
import static cn.axzo.workflow.common.enums.ApprovalMethodEnum.human;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.AND;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.EXCEPTIONAL;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.GENERAL;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.OR;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING;
import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS;
import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.RUNNING_INSTANCE_ONLY_FORECAST;
import static cn.axzo.workflow.core.common.code.BpmnProcessDefinitionRespCode.PROCESS_DEFINITION_ID_NOT_EXISTS;
import static cn.axzo.workflow.core.common.code.BpmnProcessDefinitionRespCode.PROCESS_DEFINITION_IS_SUSPENDED;
import static cn.axzo.workflow.core.common.code.BpmnProcessDefinitionRespCode.PROCESS_DEFINITION_KEY_NOT_EXISTS;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApprovalMethod;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverEmptyHandleType;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getButtonConfig;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getCarbonCopyConfigs;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getNodeType;
import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.countSql;
import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.sqlConnectors;

@Service
@Slf4j
public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceService {

    @Resource
    private RepositoryService repositoryService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private TaskService taskService;
    @Resource
    private BpmnProcessDefinitionService processDefinitionService;
    @Resource
    private HistoryService historyService;
    @Resource
    private BpmnProcessInstanceAdminPageItemConverter instanceAdminPageItemConverter;
    @Resource
    private BpmnProcessInstancePageItemConverter instancePageItemConverter;
    @Resource
    private BpmnProcessInstanceConverter instanceConverter;
    @Resource
    private BpmnHistoricProcessInstanceConverter historicProcessInstanceConverter;
    @Resource
    private ManagementService managementService;
    @Resource
    private ProcessGraphicService graphicService;
    @Resource
    private FlowNodeForecastService forecastService;
    @Resource
    private EngineExecutionStartListener engineExecutionStartListener;
    @Resource
    private SpringProcessEngineConfiguration springProcessEngineConfiguration;
    @Resource
    private ExtAxHiTaskInstService extAxHiTaskInstService;
    @Resource
    private CategoryService categoryService;
    @Resource
    private BpmnHistoricTaskInstanceConverter historicTaskInstanceConverter;
    @Resource
    private String serviceVersion;

    @Override
    public HistoricProcessInstance getProcessInstanceByBusinessKey(String businessKey, @Nullable String tenantId,
                                                                   @Nullable Boolean hasVariable) {
        HistoricProcessInstanceQuery query =
                historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(businessKey);
        if (Boolean.TRUE.equals(hasVariable)) {
            query.includeProcessVariables();
        }
        if (Objects.nonNull(tenantId)) {
            query.processInstanceTenantId(tenantId);
        }
        return query.singleResult();
    }

    @Override
    public BpmPageResult<BpmnProcessInstancePageItemVO> getMyProcessInstancePage(@Valid BpmnProcessInstanceMyPageReqVO dto) {
        HistoricProcessInstanceQuery query =
                historyService.createHistoricProcessInstanceQuery().processInstanceTenantId(dto.getTenantId());
        if (StringUtils.isNotBlank(dto.getName())) {
            query.processInstanceName(dto.getName());
        }
        if (StringUtils.isNotBlank(dto.getProcessDefinitionId())) {
            query.processDefinitionId(dto.getProcessDefinitionId());
        }
        if (StringUtils.isNotBlank(dto.getCategory())) {
            query.processDefinitionCategory(dto.getCategory());
        }
        if (StringUtils.isNotBlank(dto.getBusinessKey())) {
            query.processInstanceBusinessKey(dto.getBusinessKey());
        }
        if (StringUtils.isNotBlank(dto.getUserId())) {
            query.startedBy(dto.getUserId());
        }
        if (Objects.nonNull(dto.getBeginCreateTime())) {
            query.startedAfter(dto.getBeginCreateTime());
        }
        if (Objects.nonNull(dto.getEndCreateTime())) {
            query.startedBefore(dto.getEndCreateTime());
        }
        if (Objects.nonNull(dto.getResult())) {
            query.processInstanceBusinessStatus(dto.getResult().getStatus());
        }
        if (Objects.nonNull(dto.getTenantId())) {
            query.processInstanceTenantId(dto.getTenantId());
        }
        List<HistoricProcessInstance> instances = query.orderByProcessInstanceStartTime().desc()
                .listPage((dto.getPageNo() - 1) * dto.getPageSize(), dto.getPageSize());

        if (CollectionUtils.isEmpty(instances)) {
            return BpmPageResult.empty();
        }

        List<BpmnProcessInstancePageItemVO> vos = new ArrayList<>();
        instances.forEach(i -> {
            BpmnProcessInstancePageItemVO vo = instancePageItemConverter.toVo(i);
            // 暂不对外提供发起人姓名
            //            List<HistoricVariableInstance> list = historyService.createHistoricVariableInstanceQuery()
            //                    .processInstanceId(i.getId()).variableName(INTERNAL_START_USER_NAME).list();
            //            vo.setStartUserName((String) list.get(0).getValue());
            vos.add(vo);
        });
        return new BpmPageResult<>(vos, query.count());
    }

    @Override
    public HistoricProcessInstance getProcessInstance(String id, @Nullable String tenantId,
                                                      @Nullable Boolean hasVariable) {
        HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery().processInstanceId(id);
        if (Boolean.TRUE.equals(hasVariable)) {
            query.includeProcessVariables();
        }
        if (Objects.nonNull(tenantId)) {
            query.processInstanceTenantId(tenantId);
        }
        return query.singleResult();
    }


    public List<ProcessInstance> getProcessInstances(Set<String> ids) {
        return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list();
    }


    @Override
    public Boolean updateProcessStatus(String processDefinitionId, Integer status) {
        // 激活
        if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), status)) {
            repositoryService.activateProcessDefinitionById(processDefinitionId, false, null);
            return true;
        }
        // 挂起
        if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), status)) {
            // suspendProcessInstances = false，进行中的任务，不进行挂起。
            // 原因：只要新的流程不允许发起即可，老流程继续可以执行。
            repositoryService.suspendProcessDefinitionById(processDefinitionId, false, null);
            return true;
        }
        log.error("[updateProcessDefinitionState][流程定义({}) 修改未知状态({})]", processDefinitionId, status);
        return false;
    }


    @Override
    public String createProcessInstance(BpmnProcessInstanceCreateDTO dto) {
        BpmnProcessDefinitionVO definition = null;
        if (StringUtils.isBlank(dto.getProcessDefinitionId())) {
            definition = processDefinitionService.getActiveProcessDefinitionByKey(dto.getProcessDefinitionKey(),
                    dto.getTenantId());
            // 校验流程定义
            if (definition == null) {
                throw new WorkflowEngineException(PROCESS_DEFINITION_KEY_NOT_EXISTS, dto.getProcessDefinitionKey());
            }
            if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), definition.getSuspensionState())) {
                throw new WorkflowEngineException(PROCESS_DEFINITION_IS_SUSPENDED, dto.getProcessDefinitionKey());
            }
        }

        dto.getVariables().put(INTERNAL_INITIATOR, dto.getInitiator().toJson());
        dto.getVariables().put(BIZ_ORG_RELATION, dto.getCooperationOrg());
        dto.getVariables().put(WORKFLOW_ENGINE_VERSION, serviceVersion);
        dto.getVariables().put(PENDING_TEMPLATE_VARIABLE, dto.getPendingVariables());
        dto.getVariables().put(INTERNAL_PROCESS_AGENT,
                !Objects.isNull(definition) && StringUtils.isNotBlank(definition.getTenantId()));
        //        if (Objects.nonNull(dto.getNextApprover())) {
        //            BpmnTaskDelegateAssigner nextApprover = dto.getNextApprover();
        //            nextApprover.setTenantId(Objects.nonNull(nextApprover.getTenantId()) ? nextApprover.getTenantId
        //            () :
        //                    dto.getInitiator().getTenantId());
        //            dto.getVariables().put(INTERNAL_SPECIFY_NEXT_APPROVER, nextApprover);
        //        }

        // 创建流程实例
        // 设置流程实例的开始人，参考 https://wenku.baidu.com/view/5538062e7a563c1ec5da50e2524de518964bd3f9.html
        Authentication.setAuthenticatedUserId(dto.getInitiator().buildAssigneeId());
        String name = StringUtils.isNotBlank(dto.getCustomProcessInstanceName()) ?
                dto.getCustomProcessInstanceName()
                : Objects.isNull(definition) ? "" : definition.getName();

        // 设置流程名字
        ProcessInstanceBuilder instanceBuilder = runtimeService.createProcessInstanceBuilder()
                .processDefinitionId(Objects.isNull(definition) ? dto.getProcessDefinitionId() : definition.getId())
                .businessKey(dto.getBusinessKey())
                .variables(dto.getVariables())
                .name(name)
                .overrideProcessDefinitionTenantId(dto.getTenantId());
        ProcessInstance instance;
        if (dto.getAsync()) {
            // 异步开始
            instance = instanceBuilder.startAsync();
        } else {
            // 同步开始
            instance = instanceBuilder.start();
        }
        Authentication.setAuthenticatedUserId(null);
        return instance.getProcessInstanceId();
    }

    @Override
    public String createProcessInstanceWithForm(BpmnProcessInstanceCreateWithFormDTO dto) {
        BpmnProcessDefinitionVO definition =
                processDefinitionService.getActiveProcessDefinitionByKey(dto.getProcessDefinitionKey(),
                        dto.getInitiator().getTenantId());
        // 校验流程定义
        if (definition == null) {
            throw new WorkflowEngineException(PROCESS_DEFINITION_KEY_NOT_EXISTS, dto.getProcessDefinitionKey());
        }
        if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), definition.getSuspensionState())) {
            throw new WorkflowEngineException(PROCESS_DEFINITION_IS_SUSPENDED, dto.getProcessDefinitionKey());
        }
        dto.getVariables().put(INTERNAL_INITIATOR, dto.getInitiator().toJson());

        // 创建流程实例
        // 设置流程实例的开始人，参考https://wenku.baidu.com/view/5538062e7a563c1ec5da50e2524de518964bd3f9.html
        Authentication.setAuthenticatedUserId(dto.getInitiator().buildAssigneeId());
        String name = StringUtils.isNotBlank(dto.getCustomProcessInstanceName()) ?
                dto.getCustomProcessInstanceName()
                : definition.getName();

        ProcessInstance instance = runtimeService.startProcessInstanceWithForm(definition.getId(),
                dto.getOutcome(), dto.getVariables(), name);
        Authentication.setAuthenticatedUserId(null);
        return instance.getProcessInstanceId();
    }

    @Override
    public FormInfo getStartFormModel(String processDefinitionId, String processInstanceId) {
        return runtimeService.getStartFormModel(processDefinitionId, processInstanceId);
    }

    @Override
    public Boolean cancelProcessInstance(BpmnProcessInstanceCancelDTO dto) {
        CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor();
        if (dto.getAsync() != null && dto.getAsync()) {
            commandExecutor.execute(new CustomCancelProcessInstanceAsyncCmd(dto));
        } else {
            commandExecutor.execute(new CustomCancelProcessInstanceCmd(dto.getProcessInstanceId(), dto.getTenantId(),
                    dto.getReason(), dto.getInitiator(), extAxHiTaskInstService));
        }
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public Boolean abortProcessInstance(BpmnProcessInstanceAbortDTO dto) {
        CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor();
        if (dto.getAsync() != null && dto.getAsync()) {
            commandExecutor.execute(new CustomAbortProcessInstanceAsyncCmd(dto));
        } else {
            commandExecutor.execute(new CustomAbortProcessInstanceCmd(dto.getProcessInstanceId(), dto.getTenantId(),
                    dto.getReason(), extAxHiTaskInstService));
        }
        return true;
    }

    @Override
    public Boolean carbonCopyProcessInstance(BpmnProcessInstanceCarbonCopyDTO dto) {
        // TODO 暴露给二方 API 的抄送功能, 需求被砍
        CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor();
        //        commandExecutor.execute(new );
        return null;
    }

    @Override
    public BpmPageResult<BpmnProcessInstanceAdminPageItemVO> getAdminProcessInstancePage(BpmnProcessInstanceAdminPageReqVO dto) {
        List<String> dictValues = new ArrayList<>();
        if (StringUtils.isNotBlank(dto.getCategory())) {
            CategorySearchDTO searchDTO = new CategorySearchDTO();
            searchDTO.setDictType(BpmnConstants.BPM_MODEL_CATEGORY);
            searchDTO.setLabel(dto.getCategory());
            dictValues.addAll(categoryService.list(searchDTO).stream().map(CategoryItemVO::getValue).distinct().collect(Collectors.toList()));
        }
        NativeHistoricProcessInstanceQuery query = historyService.createNativeHistoricProcessInstanceQuery();
        String tableName = managementService.getTableName(HistoricProcessInstance.class);
        StringBuilder baseQuerySql = new StringBuilder("SELECT RES.*, ")
                .append("DEF.KEY_           as PROC_DEF_KEY_,")
                .append("DEF.NAME_          as PROC_DEF_NAME_,")
                .append("DEF.VERSION_       as PROC_DEF_VERSION_,")
                .append("DEF.DEPLOYMENT_ID_ as DEPLOYMENT_ID_")
                .append(" FROM ").append(tableName)
                .append(" RES LEFT OUTER JOIN ACT_RE_PROCDEF DEF ON RES.PROC_DEF_ID_ = DEF.ID_")
                .append(" WHERE 1 = 1");
        if (StringUtils.isNotBlank(dto.getProcessInstanceId())) {
            List<String> ids = Lists.newArrayList(dto.getProcessInstanceId().replaceAll(" ", "").split(","));
            baseQuerySql.append(" AND RES.PROC_INST_ID_ IN (");
            for (int i = 0; i < ids.size(); i++) {
                baseQuerySql.append("#{instanceId").append(i).append("}");
                if (i < ids.size() - 1) {
                    baseQuerySql.append(",");
                }
                query.parameter("instanceId" + i, ids.get(i));
            }
            baseQuerySql.append(")");
        }
        if (StringUtils.isNotBlank(dto.getName())) {
            baseQuerySql.append(" AND RES.NAME_ LIKE #{name}");
            query.parameter("name", "%" + dto.getName() + "%");
        }
        if (!CollectionUtils.isEmpty(dictValues)) {
            baseQuerySql.append(" AND DEF.CATEGORY_ IN (");
            for (int i = 0; i < dictValues.size(); i++) {
                baseQuerySql.append("#{category").append(i).append("}");
                if (i < dictValues.size() - 1) {
                    baseQuerySql.append(",");
                }
                query.parameter("category" + i, dictValues.get(i));
            }
            baseQuerySql.append(")");
        }
        if (!CollectionUtils.isEmpty(dto.getTenantIds())) {
            baseQuerySql.append(" AND RES.TENANT_ID_ IN (");
            for (int i = 0; i < dto.getTenantIds().size(); i++) {
                baseQuerySql.append("#{tenantId").append(i).append("}");
                if (i < dto.getTenantIds().size() - 1) {
                    baseQuerySql.append(",");
                }
                query.parameter("tenantId" + i, dto.getTenantIds().get(i));
            }
            baseQuerySql.append(")");
        }
        if (StringUtils.isNotBlank(dto.getBusinessStatus())) {
            baseQuerySql.append(" AND RES.BUSINESS_STATUS_ = #{businessStatus}");
            query.parameter("businessStatus", dto.getBusinessStatus());
        }
        if (Objects.nonNull(dto.getStartTime()) && Objects.nonNull(dto.getEndTime())) {
            baseQuerySql.append(" AND RES.START_TIME_ >= #{startedAfter}");
            query.parameter("startedAfter", dto.getStartTime());
            baseQuerySql.append(" AND RES.START_TIME_ <= #{startedBefore}");
            query.parameter("startedBefore", dto.getEndTime());
        }
        baseQuerySql.append(" ORDER BY RES.START_TIME_ DESC ");

        List<HistoricProcessInstance> instances = query.sql(baseQuerySql.toString())
                .listPage((dto.getPageNo() - 1) * dto.getPageSize(),
                        dto.getPageSize());

        baseQuerySql.replace(7, 163, "count(1)");
        NativeHistoricProcessInstanceQuery countSqlQuery = query.sql(baseQuerySql.toString());
        if (CollectionUtils.isEmpty(instances)) {
            return BpmPageResult.empty();
        }

        List<String> processingInstanceIds =
                instances.stream().filter(i -> Objects.equals(PROCESSING.getStatus(), i.getBusinessStatus()))
                        .map(HistoricProcessInstance::getId).distinct().collect(Collectors.toList());
        Map<String, List<FlowElement>> instanceFlowElementMap = new HashMap<>();
        processingInstanceIds.forEach(i -> {
            // 查询每个流程推测出来的总节点数
            instanceFlowElementMap.put(i, forecastService.performProcessForecasting(i, null));
        });

        // 实例对应的当前运行到的节点
        Map<String, ActivityInstance> liveActivityMap = getInstanceCurrentActivity(processingInstanceIds, false);
        // 实例对应的最后一个已经完成的节点
        Map<String, ActivityInstance> leastEndActivityMap = getInstanceLastActivity(processingInstanceIds);

        List<String> categories = instances.stream().map(HistoricProcessInstance::getProcessDefinitionKey)
                .distinct().collect(Collectors.toList());
        Map<String, CategoryItemVO> categoryLabelMap = categoryService.listByValue(BPM_MODEL_CATEGORY, categories)
                .stream().collect(Collectors.toMap(CategoryItemVO::getValue, Function.identity(), (s, t) -> s));

        List<BpmnProcessInstanceAdminPageItemVO> vos = instanceAdminPageItemConverter.toVos(instances,
                instanceFlowElementMap, liveActivityMap, leastEndActivityMap,
                categoryLabelMap);
        return new BpmPageResult<>(vos, countSqlQuery.count());
    }

    private Map<String, ActivityInstance> getInstanceLastActivity(List<String> processInstanceIds) {
        return getInstanceCurrentActivity(processInstanceIds, true);
    }

    private Map<String, ActivityInstance> getInstanceCurrentActivity(List<String> processInstanceIds,
                                                                     boolean leastEndActivity) {
        if (CollectionUtils.isEmpty(processInstanceIds)) {
            return Collections.emptyMap();
        }

        NativeActivityInstanceQuery nativeQuery = runtimeService.createNativeActivityInstanceQuery();
        String tableName = managementService.getTableName(ActivityInstanceEntity.class);
        StringBuilder querySql = new StringBuilder();
        querySql.append("SELECT * FROM (")
                .append("SELECT *, ROW_NUMBER() OVER (PARTITION BY PROC_INST_ID_ ORDER BY START_TIME_ DESC) AS rn ")
                .append(" FROM ")
                .append(tableName)
                .append(" WHERE PROC_INST_ID_ in (");
        for (int i = 0; i < processInstanceIds.size(); i++) {
            querySql.append("#{processInstanceId_").append(i).append("}");
            if (i < processInstanceIds.size() - 1) {
                querySql.append(",");
            }
            nativeQuery.parameter("processInstanceId_" + i, processInstanceIds.get(i));
        }
        querySql.append(") and TASK_ID_ is not null ");
        if (leastEndActivity) {
            querySql.append(" and END_TIME_ is not null");
        }
        querySql.append(") as t WHERE rn = 1");
        return nativeQuery.sql(querySql.toString()).list().stream()
                .collect(Collectors.toMap(ActivityInstance::getProcessInstanceId, Function.identity(),
                        (s, t) -> s));
    }


    @Override
    public HistoricProcessInstance getHistoricProcessInstance(String id, String tenantId) {
        HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery().processInstanceId(id);
        if (StringUtils.isNotBlank(tenantId)) {
            query.processInstanceTenantId(tenantId);
        }
        return query.singleResult();
    }


    public HistoricProcessInstance getHistoricProcessInstanceByBusinessKey(String businessKey, String tenantId) {
        HistoricProcessInstanceQuery query =
                historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(businessKey);
        if (StringUtils.isNotBlank(tenantId)) {
            query.processInstanceTenantId(tenantId);
        }
        return query.singleResult();
    }

    @Override
    public BpmnProcessInstanceVO getProcessInstanceVO(BpmnProcessInstanceQueryDTO dto) {
        HistoricProcessInstance processInstance = null;
        if (StringUtils.isNotBlank(dto.getProcessInstanceId())) {
            processInstance = getProcessInstance(dto.getProcessInstanceId(), dto.getTenantId(), dto.getHasVariable());
        } else if (StringUtils.isNotBlank(dto.getBusinessKey())) {
            processInstance = getProcessInstanceByBusinessKey(dto.getBusinessKey(), dto.getTenantId(),
                    dto.getHasVariable());
        }

        // 获得流程实例
        if (Objects.isNull(processInstance)) {
            throw new WorkflowEngineException(PROCESS_INSTANCE_NOT_EXISTS);
        }
        // 获得流程定义
        BpmnProcessDefinitionVO processDefinition = processDefinitionService
                .getProcessDefinition(processInstance.getProcessDefinitionId());
        if (Objects.isNull(processDefinition)) {
            throw new WorkflowEngineException(PROCESS_DEFINITION_ID_NOT_EXISTS,
                    processInstance.getProcessDefinitionId());
        }

        Object tempAssigner = null;
        String version = null;
        if (dto.getHasVariable()) {
            tempAssigner = processInstance.getProcessVariables().get(INTERNAL_INITIATOR);
            if (Objects.isNull(tempAssigner)) {
                tempAssigner = processInstance.getProcessVariables().get(OLD_INTERNAL_INITIATOR);
            }
            version = (String) processInstance.getProcessVariables().get(WORKFLOW_ENGINE_VERSION);

        } else {
            List<HistoricVariableInstance> variableInstances = historyService.createHistoricVariableInstanceQuery()
                    .processInstanceId(processInstance.getId()).list();
            for (HistoricVariableInstance i : variableInstances) {
                if (Objects.equals(i.getVariableName(), INTERNAL_INITIATOR) || Objects.equals(i.getVariableName(),
                        OLD_INTERNAL_INITIATOR)) {
                    tempAssigner = i.getValue();
                }
                if (Objects.equals(i.getVariableName(), WORKFLOW_ENGINE_VERSION)) {
                    version = (String) i.getValue();
                }
            }
        }

        if (Objects.isNull(version)) {
            version = FLOW_SERVER_VERSION_121;
        }

        List<Task> runningTasks = taskService.createTaskQuery().processInstanceId(processInstance.getId())
                .active()
                .orderByTaskCreateTime()
                .desc()
                .list();

        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
        return instanceConverter.toVo(processInstance, processDefinition, getButtonConfig(bpmnModel.getMainProcess())
                , BpmnTaskDelegateAssigner.toObjectCompatible(tempAssigner), version, categoryService.get(BPM_MODEL_CATEGORY,
                        processInstance.getProcessDefinitionKey()), runningTasks);
    }

    /**
     * 获得流程实例 Map
     *
     * @param ids 流程实例的编号集合
     * @return 流程实例列表 Map
     */
    public Map<String, ProcessInstance> getProcessInstanceMap(Set<String> ids) {
        return BpmnCollectionUtils.convertMap(getProcessInstances(ids),
                ProcessInstance::getProcessInstanceId);
    }


    /**
     * 获得历史的流程实例 Map
     *
     * @param ids 流程实例的编号集合
     * @return 历史的流程实例列表 Map
     */
    public Map<String, HistoricProcessInstance> getHistoricProcessInstanceMap(Set<String> ids) {
        return BpmnCollectionUtils.convertMap(getHistoricProcessInstances(ids),
                HistoricProcessInstance::getId);
    }

    public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) {
        HistoricProcessInstanceQuery historicProcessInstanceQuery =
                historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids);
        return historicProcessInstanceQuery.list();
    }

    @Override
    public ObjectNode getProcessInstanceGraphical(String processInstanceId, @Nullable String tenantId) {
        HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId);
        if (StringUtils.isNotBlank(tenantId)) {
            query.processInstanceTenantId(tenantId);
        }
        HistoricProcessInstance processInstance = query.singleResult();
        if (Objects.isNull(processInstance)) {
            throw new WorkflowEngineException(PROCESS_INSTANCE_NOT_EXISTS);
        }
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());

        List<HistoricActivityInstance> activityInstances =
                historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list();

        Set<String> completedActivityInstances = new HashSet<>();
        Set<String> currentElements = new HashSet<>();
        if (!CollectionUtils.isEmpty(activityInstances)) {
            for (HistoricActivityInstance activityInstance : activityInstances) {
                if (activityInstance.getEndTime() != null) {
                    completedActivityInstances.add(activityInstance.getActivityId());
                } else {
                    currentElements.add(activityInstance.getActivityId());
                }
            }
        }
        // Gather completed flows
        List<String> completedFlows = graphicService.gatherCompletedFlows(completedActivityInstances, currentElements
                , bpmnModel);

        Set<String> completedElements = new HashSet<>(completedActivityInstances);
        completedElements.addAll(completedFlows);

        ObjectNode displayNode = graphicService.processProcessElements(bpmnModel, completedElements, currentElements,
                new ArrayList<>(), processInstanceId);
        if (!CollectionUtils.isEmpty(completedActivityInstances)) {
            ArrayNode completedActivities = displayNode.putArray("completedActivities");
            for (String completed : completedActivityInstances) {
                completedActivities.add(completed);
            }
        }

        if (!CollectionUtils.isEmpty(currentElements)) {
            ArrayNode currentActivities = displayNode.putArray("currentActivities");
            for (String current : currentElements) {
                currentActivities.add(current);
            }
        }

        if (!CollectionUtils.isEmpty(completedFlows)) {
            ArrayNode completedSequenceFlows = displayNode.putArray("completedSequenceFlows");
            for (String current : completedFlows) {
                completedSequenceFlows.add(current);
            }
        }
        return displayNode;
    }

    @Override
    public BpmPageResult<HistoricProcessInstanceVO> historicProcessInstancePage(HistoricProcessInstanceSearchDTO dto) {
        NativeHistoricProcessInstanceQuery query = historyService.createNativeHistoricProcessInstanceQuery();
        String tableName = managementService.getTableName(HistoricProcessInstance.class);
        StringBuilder baseQuerySql = new StringBuilder("SELECT a.* FROM ").append(tableName).append(" a");
        if (StringUtils.isNotBlank(dto.getCategory())) {
            baseQuerySql.append(" LEFT JOIN ACT_RE_PROCDEF b ON a.PROC_DEF_ID_ = b.ID_ ")
                    .append(sqlConnectors(baseQuerySql))
                    .append(" b.CATEGORY_ = #{category}");
            query.parameter("category", dto.getCategory());
        }
        if (StringUtils.isNotBlank(dto.getSearchKey())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql))
                    .append(" (a.BUSINESS_KEY_ LIKE #{searchKey}")
                    .append(" or")
                    .append(" a.NAME_ LIKE #{searchKey})");
            query.parameter("searchKey", "%" + dto.getSearchKey() + "%");
        }
        if (StringUtils.isNotBlank(dto.getResult())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql))
                    .append(" a.BUSINESS_STATUS_ = #{result}");
            query.parameter("result", dto.getResult());
        }
        if (Objects.nonNull(dto.getTenantId())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql))
                    .append(" a.TENANT_ID_ = #{tenantId}");
            query.parameter("tenantId", dto.getTenantId());
        }
        if (!CollectionUtils.isEmpty(dto.getUserIds())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql))
                    .append(" a.START_USER_ID_ in (");
            for (int i = 0; i < dto.getUserIds().size(); i++) {
                baseQuerySql.append("#{userId").append(i).append("}");
                if (i < dto.getUserIds().size() - 1) {
                    baseQuerySql.append(",");
                }
                query.parameter("userId" + i, dto.getUserIds().get(i));
            }
            baseQuerySql.append(")");
        }
        if (Objects.nonNull(dto.getStartBeginTime()) && Objects.nonNull(dto.getStartEndTime())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql))
                    .append(" a.START_TIME_ between #{startBeginTime} and #{startEndTime}");
            query.parameter("startBeginTime", dto.getStartBeginTime());
            query.parameter("startEndTime", dto.getStartEndTime());
        }
        if (Objects.nonNull(dto.getFinishedBeginTime()) && Objects.nonNull(dto.getFinishedEndTime())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql))
                    .append(" a.END_TIME_ between #{finishedBeginTime} and #{finishedEndTime}");
            query.parameter("finishedBeginTime", dto.getFinishedBeginTime());
            query.parameter("finishedEndTime", dto.getFinishedEndTime());
        }
        baseQuerySql.append(" ORDER BY a.START_TIME_ DESC");
        NativeHistoricProcessInstanceQuery dataSqlQuery = query.sql(baseQuerySql.toString());
        List<HistoricProcessInstance> instances = dataSqlQuery
                .listPage((dto.getPageNo() - 1) * dto.getPageSize(), dto.getPageSize());
        if (CollectionUtils.isEmpty(instances)) {
            return BpmPageResult.empty();
        }

        NativeHistoricProcessInstanceQuery countSqlQuery = query.sql(countSql(baseQuerySql));
        if (StringUtils.isNotBlank(dto.getCategory())) {
            return new BpmPageResult(historicProcessInstanceConverter.toVos(instances, dto.getCategory()),
                    countSqlQuery.count());
        } else {
            Set<String> procDefIds = new HashSet<>();
            instances.forEach(i -> procDefIds.add(i.getProcessDefinitionId()));
            Map<String, String> defCategoryMap =
                    repositoryService.createProcessDefinitionQuery().processDefinitionIds(procDefIds).list()
                            .stream().collect(Collectors.toMap(ProcessDefinition::getId,
                                    ProcessDefinition::getCategory, (s, t) -> s));
            List<HistoricProcessInstanceVO> vos = new ArrayList<>();
            instances.forEach(i -> vos.add(historicProcessInstanceConverter.toVo(i,
                    defCategoryMap.getOrDefault(i.getProcessDefinitionId(), ""))));
            return new BpmPageResult(vos, countSqlQuery.count());
        }
    }

    @Override
    public List<ProcessNodeDetailVO> getProcessInstanceNodeForecast(String processInstanceId,
                                                                    @Nullable String tenantId) {

        return getProcessInstanceNodeFilterForecast(processInstanceId, tenantId, Collections.emptyList());
    }

    @Override
    public List<ProcessNodeDetailVO> getProcessInstanceNodeFilterForecast(String processInstanceId, String tenantId, List<String> nodeDefinitionKeys) {
        ProcessInstanceQuery query = runtimeService.createProcessInstanceQuery()
                .processInstanceId(processInstanceId);
        if (StringUtils.isNotBlank(tenantId)) {
            query.processInstanceTenantId(tenantId);
        }
        ProcessInstance instance = query.singleResult();
        if (Objects.isNull(instance)) {
            throw new WorkflowEngineException(RUNNING_INSTANCE_ONLY_FORECAST);
        }
        List<FlowElement> flowElements = forecastService.performProcessForecasting(processInstanceId, instance);

        BpmnModel bpmnModel = repositoryService.getBpmnModel(instance.getProcessDefinitionId());
        List<ProcessNodeDetailVO> resultList = new ArrayList<>(flowElements.size());
        // 发起人节点,也是一个 UserTask 节点, 所以这里默认就包含了发起人节点
        flowElements.stream()
                .filter(i -> (i instanceof UserTask || i instanceof ReceiveTask || i instanceof ServiceTask))
                .forEach(i -> {
                    ProcessNodeDetailVO node = new ProcessNodeDetailVO();
                    node.setForecastAssigners(Collections.emptyList());
                    // 每个节点设置的按钮配置
                    getButtonConfig(bpmnModel.getMainProcess(), i.getId()).ifPresent(node::setButtonConf);
                    // 设置审批方式
                    getApprovalMethod(i).ifPresent(node::setApprovalMethod);
                    getNodeType(i).ifPresent(node::setNodeType);
                    if (Objects.equals(NODE_STARTER.getType(), i.getId())) {
                        node.setNodeType(NODE_STARTER);
                    }
                    node.setNodeMode(GENERAL);
                    node.setId(i.getId()).setName(i.getName());
                    if (i instanceof UserTask) {
                        parseUserTask(processInstanceId, (UserTask) i, node, nodeDefinitionKeys);
                    } else if (i instanceof ServiceTask) {
                        parseServiceTask(processInstanceId, (ServiceTask) i, node, nodeDefinitionKeys);
                    }
                    resultList.add(node);
                });
        return resultList;
    }

    private void parseServiceTask(String processInstanceId, ServiceTask i, ProcessNodeDetailVO node, List<String> skipTaskDefinitionKeys) {
        // ServiceTask 主要作用于抄送
        node.setId(i.getId()).setName(i.getName());
        if (skipTaskDefinitionKeys.contains(i.getId())) {
            return;
        }
        getCarbonCopyConfigs(i).ifPresent(carbons ->
                node.setForecastAssigners(springProcessEngineConfiguration.getCommandExecutor()
                        .execute(new CustomCarbonCopyUserSelectorCmd(processInstanceId, carbons,
                                i, engineExecutionStartListener,
                                historicTaskInstanceConverter, serviceVersion))));
    }

    private void parseUserTask(String processInstanceId, UserTask i, ProcessNodeDetailVO node, List<String> skipTaskDefinitionKeys) {
        node.setFormKey(i.getFormKey());
        // 设置审批模式,
        if (i.getBehavior() instanceof MultiInstanceActivityBehavior) {
            MultiInstanceActivityBehavior behavior =
                    (MultiInstanceActivityBehavior) i.getBehavior();
            node.setNodeMode(Objects.equals(AND_SIGN_EXPRESSION,
                    behavior.getCompletionCondition()) ?
                    AND : OR);
        } else {
            node.setNodeMode(BpmnFlowNodeMode.GENERAL);
        }

        if (skipTaskDefinitionKeys.contains(i.getId())) {
            return;
        }
        if (Objects.equals(node.getApprovalMethod(), human)) {
            // 推测当前节点的审批人,并且如果审批人为空,也会根据审批人为空的策略去找人
            List<BpmnTaskDelegateAssigner> forecastAssigners =
                    springProcessEngineConfiguration.getCommandExecutor()
                            .execute(new CustomForecastUserTaskAssigneeCmd(processInstanceId,
                                    i, engineExecutionStartListener));
            node.setForecastAssigners(forecastAssigners);
            if (CollectionUtils.isEmpty(forecastAssigners)) {
                getApproverEmptyHandleType(i).ifPresent(emptyHandleType -> {
                    switch (emptyHandleType) {
                        case autoPassed:
                            node.setApprovalMethod(autoPassed);
                            break;
                        case autoRejection:
                            node.setApprovalMethod(autoRejection);
                            break;
                        default:
                            node.setNodeMode(EXCEPTIONAL);
                            break;
                    }
                });
            }
        }
    }

    @Override
    public List<String> getTenantIds() {
        NativeHistoricProcessInstanceQuery query = historyService.createNativeHistoricProcessInstanceQuery();
        String tableName = managementService.getTableName(HistoricProcessInstance.class);
        String baseQuerySql = "SELECT DISTINCT TENANT_ID_ FROM " + tableName +
                " WHERE TENANT_ID_ IS NOT NULL AND TENANT_ID_ != '' AND TENANT_ID_ != 0 AND TENANT_ID_ != 'NULL'";
        NativeHistoricProcessInstanceQuery dataSqlQuery = query.sql(baseQuerySql);
        return ListUtils.emptyIfNull(dataSqlQuery.list()).stream().map(HistoricProcessInstance::getTenantId).distinct().collect(Collectors.toList());
    }
}
