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

import cn.axzo.framework.core.util.StringUtil;
import cn.axzo.workflow.common.enums.BpmnCountersignType;
import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAssigneeDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAttachmentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCommentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCountersignDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskPageSearchDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskRemindDTO;
import cn.axzo.workflow.common.model.response.BpmPageResult;
import cn.axzo.workflow.common.model.response.bpmn.task.BpmnHistoricTaskInstanceGroupVO;
import cn.axzo.workflow.common.model.response.bpmn.task.BpmnHistoricTaskInstanceVO;
import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskDonePageItemVO;
import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskInstanceVO;
import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskTodoPageItemVO;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.engine.event.MessagePushEventBuilder;
import cn.axzo.workflow.core.engine.event.MessagePushEventImpl;
import cn.axzo.workflow.core.engine.event.MessagePushEventType;
import cn.axzo.workflow.core.engine.id.TimeBasedIdGenerator;
import cn.axzo.workflow.core.service.BpmnProcessTaskService;
import cn.axzo.workflow.core.service.converter.BpmnHistoricCommentConverter;
import cn.axzo.workflow.core.service.converter.BpmnHistoricTaskInstanceConverter;
import cn.axzo.workflow.core.service.converter.BpmnTaskConverter;
import cn.axzo.workflow.core.service.converter.BpmnTaskDonePageItemConverter;
import cn.axzo.workflow.core.service.converter.BpmnTaskTodoPageItemConverter;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.Activity;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher;
import org.flowable.common.engine.impl.identity.Authentication;
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.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.task.Comment;
import org.flowable.form.api.FormInfo;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
import org.flowable.task.service.history.NativeHistoricTaskInstanceQuery;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.flowable.task.service.impl.persistence.entity.TaskEntityImpl;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.flowable.variable.api.persistence.entity.VariableInstance;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import static cn.axzo.workflow.common.constant.BpmnConstants.APPROVAL_ENDS_AUTOMATICALLY;
import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE;
import static cn.axzo.workflow.common.constant.BpmnConstants.COUNTERSIGN_ORIGIN_ASSIGNER;
import static cn.axzo.workflow.common.constant.BpmnConstants.COUNTERSIGN_REMAIN_ASSIGNER_LIST;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_DELETE_PROCESS_FLAG;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_END_TENANT_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_END_USER_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_END_USER_NAME;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_TYPE_REJECT;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_SPECIFY_NEXT_APPROVER;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_INFO_SNAPSHOT;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT;
import static cn.axzo.workflow.common.constant.BpmnConstants.MULTI_INSTANCE_LOOP_COUNTER;
import static cn.axzo.workflow.common.constant.BpmnConstants.NUMBER_OF_INSTANCES;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.CANCELLED;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.REJECTED;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.valueOfStatus;
import static cn.axzo.workflow.core.common.enums.BpmnErrorCode.PROCESS_INSTANCE_ID_NOT_EXISTS;
import static cn.axzo.workflow.core.common.enums.BpmnErrorCode.PROCESS_INSTANCE_NOT_EXISTS;
import static cn.axzo.workflow.core.common.enums.BpmnErrorCode.TASK_APOSTILLE_NOT_SUPPORT;
import static cn.axzo.workflow.core.common.enums.BpmnErrorCode.TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF;
import static cn.axzo.workflow.core.common.enums.BpmnErrorCode.TASK_COMPLETE_FAIL_NOT_EXISTS;
import static cn.axzo.workflow.core.common.enums.BpmnErrorCode.TASK_REMIND_ERROR_NOT_EXISTS;
import static cn.axzo.workflow.core.common.utils.BpmnCollectionUtils.convertSet;
import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.countSql;
import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.sqlConnectors;
import static org.flowable.engine.impl.persistence.entity.CommentEntity.TYPE_COMMENT;

@Service
@Slf4j
public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {

    @Resource
    private TaskService taskService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private HistoryService historyService;
    @Resource
    private RepositoryService repositoryService;
    @Resource
    private BpmnProcessInstanceServiceImpl processInstanceService;
    @Resource
    private BpmnTaskTodoPageItemConverter todoPageItemConverter;
    @Resource
    private BpmnTaskDonePageItemConverter donePageItemConverter;
    @Resource
    private BpmnHistoricTaskInstanceConverter historicTaskInstanceConverter;
    @Resource
    private BpmnTaskConverter bpmnTaskConverter;
    @Resource
    private BpmnHistoricCommentConverter commentConverter;
    @Resource
    private ManagementService managementService;

    @Override
    public BpmPageResult<BpmnTaskTodoPageItemVO> getTodoTaskPage(BpmnTaskPageSearchDTO dto) {
        List<HistoricTaskInstance> tasks;
        Long resultTotalCount;
        if (CollectionUtils.isEmpty(dto.getResults())) {
            HistoricTaskInstanceQuery query =
                    historyService.createHistoricTaskInstanceQuery().unfinished().taskAssignee(dto.getUserId()) // 分配给自己
                            .orderByTaskCreateTime().desc();
            populateQuery(dto, query);
            tasks = query.listPage((dto.getPageNo() - 1) * dto.getPageSize(), dto.getPageSize());
            resultTotalCount = query.count();
        } else {
            StringBuilder baseQuerySql = new StringBuilder();
            NativeHistoricTaskInstanceQuery nativeQuery = compositeTodoQuery(dto, baseQuerySql);
            tasks = nativeQuery.listPage((dto.getPageNo() - 1) * dto.getPageSize(), dto.getPageSize());
            NativeHistoricTaskInstanceQuery countSqlQuery = nativeQuery.sql(countSql(baseQuerySql));
            resultTotalCount = countSqlQuery.count();
        }
        if (CollUtil.isEmpty(tasks)) {
            return BpmPageResult.empty();
        }

        Set<String> processInstanceIds = convertSet(tasks, HistoricTaskInstance::getProcessInstanceId);

        // 获得 ProcessInstance Map
        Map<String, ProcessInstance> processInstanceMap =
                processInstanceService.getProcessInstanceMap(processInstanceIds);

        List<BpmnTaskTodoPageItemVO> vos = todoPageItemConverter.toVos(tasks, processInstanceMap);
        return new BpmPageResult<>(vos, resultTotalCount);
    }

    @Override
    public BpmPageResult<BpmnTaskDonePageItemVO> getDoneTaskPage(BpmnTaskPageSearchDTO dto) {
        List<HistoricTaskInstance> tasks;
        Long resultTotalCount;
        // 如果没有状态筛选,则走引擎内置 API
        if (CollectionUtils.isEmpty(dto.getResults())) {
            // 查询已办任务
            HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery().finished() // 已完成
                    .taskAssignee(dto.getUserId()) // 分配给自己
                    .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序
            populateQuery(dto, query);
            // 执行查询
            tasks = query.listPage((dto.getPageNo() - 1) * dto.getPageSize(), dto.getPageSize());
            resultTotalCount = query.count();
        } else {
            StringBuilder baseQuerySql = new StringBuilder();
            NativeHistoricTaskInstanceQuery nativeQuery = compositeDoneQuery(dto, baseQuerySql);
            tasks = nativeQuery.listPage((dto.getPageNo() - 1) * dto.getPageSize(), dto.getPageSize());
            NativeHistoricTaskInstanceQuery countSqlQuery = nativeQuery.sql(countSql(baseQuerySql));
            resultTotalCount = countSqlQuery.count();
        }
        if (CollectionUtils.isEmpty(tasks)) {
            return BpmPageResult.empty();
        }
        Set<String> processInstanceIds = convertSet(tasks, HistoricTaskInstance::getProcessInstanceId);
        Map<String, HistoricProcessInstance> historicProcessInstanceMap =
                processInstanceService.getHistoricProcessInstanceMap(processInstanceIds);
        List<BpmnTaskDonePageItemVO> vos = donePageItemConverter.toVos(tasks, historicProcessInstanceMap);
        return new BpmPageResult<>(vos, resultTotalCount);
    }


    private NativeHistoricTaskInstanceQuery compositeTodoQuery(BpmnTaskPageSearchDTO dto, StringBuilder baseQuerySql) {
        NativeHistoricTaskInstanceQuery nativeQuery = compositeQuery(dto, baseQuerySql);
        baseQuerySql.append(sqlConnectors(baseQuerySql)).append(" a.END_TIME_ is null").append(sqlConnectors(baseQuerySql)).append(" a.ASSIGNEE_ = #{assignee}").append(" ORDER BY a.END_TIME_ DESC");
        nativeQuery.sql(baseQuerySql.toString()).parameter("assignee", dto.getUserId());
        return nativeQuery;
    }

    private NativeHistoricTaskInstanceQuery compositeDoneQuery(BpmnTaskPageSearchDTO dto, StringBuilder baseQuerySql) {
        NativeHistoricTaskInstanceQuery nativeQuery = compositeQuery(dto, baseQuerySql);
        baseQuerySql.append(sqlConnectors(baseQuerySql)).append(" a.END_TIME_ is not null").append(sqlConnectors(baseQuerySql)).append(" a.ASSIGNEE_ = #{assignee}").append(" ORDER BY a.START_TIME_ DESC");
        nativeQuery.sql(baseQuerySql.toString()).parameter("assignee", dto.getUserId());
        return nativeQuery;
    }

    private NativeHistoricTaskInstanceQuery compositeQuery(BpmnTaskPageSearchDTO dto, StringBuilder baseQuerySql) {
        NativeHistoricTaskInstanceQuery nativeQuery = historyService.createNativeHistoricTaskInstanceQuery();
        String tableName = managementService.getTableName(HistoricTaskInstance.class);
        baseQuerySql.append("SELECT a.* FROM ").append(tableName).append(" a JOIN " + "ACT_HI_PROCINST b").append(" " +
                "ON b.PROC_INST_ID_ = a.PROC_INST_ID_");

        if (StringUtils.hasLength(dto.getTenantId())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql)).append(" b.TENANT_ID_ = #{tenantId}").append(sqlConnectors(baseQuerySql)).append(" a.TENANT_ID_ = #{tenantId}");
            nativeQuery.parameter("tenantId", dto.getTenantId());
        }
        if (StringUtils.hasLength(dto.getName())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql)).append(" a.NAME_ = #{name}");
            nativeQuery.parameter("name", dto.getName());
        }
        if (StringUtils.hasLength(dto.getBusinessKey())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql)).append(" b.BUSINESS_KEY_ = #{businessKey}");
            nativeQuery.parameter("businessKey", dto.getBusinessKey());
        }
        if (Objects.nonNull(dto.getBeginCreateTime()) && Objects.nonNull(dto.getEndCreateTime())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql)).append(" a.START_TIME_ < #{endCreateTime}").append(sqlConnectors(baseQuerySql)).append(" a.START_TIME_ > #{startCreateTime}");
        }
        if (StringUtils.hasLength(dto.getCategories())) {
            List<String> categories =
                    Arrays.stream(dto.getCategories().split(",")).map(String::trim).collect(Collectors.toList());
            baseQuerySql.append(sqlConnectors(baseQuerySql)).append(" a.CATEGORY_ in (");
            for (int i = 0; i < categories.size(); i++) {
                baseQuerySql.append("#{category").append(i).append("}");
                if (i < categories.size() - 1) {
                    baseQuerySql.append(",");
                }
                nativeQuery.parameter("category" + i, categories.get(i));
            }
            baseQuerySql.append(")");
        }
        if (!CollectionUtils.isEmpty(dto.getResults())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql)).append(" b.BUSINESS_STATUS_ in (");
            for (int i = 0; i < dto.getResults().size(); i++) {
                baseQuerySql.append("#{businessStatus").append(i).append("}");
                if (i < dto.getResults().size() - 1) {
                    baseQuerySql.append(",");
                }
                nativeQuery.parameter("businessStatus" + i, dto.getResults().get(i).getStatus());
            }
            baseQuerySql.append(")");
        }
        return nativeQuery;
    }

    private void populateQuery(BpmnTaskPageSearchDTO dto, HistoricTaskInstanceQuery query) {
        if (StringUtils.hasLength(dto.getTenantId())) {
            query.taskTenantId(dto.getTenantId());
        }
        if (StringUtils.hasLength(dto.getName())) {
            query.taskNameLike("%" + dto.getName() + "%");
        }
        if (StringUtils.hasLength(dto.getBusinessKey())) {
            query.processInstanceBusinessKey(dto.getBusinessKey());
        }
        if (Objects.nonNull(dto.getBeginCreateTime()) && Objects.nonNull(dto.getEndCreateTime())) {
            query.taskCreatedBefore(dto.getEndCreateTime());
            query.taskCreatedAfter(dto.getBeginCreateTime());
        }
        if (StringUtils.hasLength(dto.getCategories())) {
            List<String> categories =
                    Arrays.stream(dto.getCategories().split(",")).map(String::trim).collect(Collectors.toList());
            query.processCategoryIn(categories);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void approveTask(BpmnTaskAuditDTO dto) {
        // 校验任务存在
        TaskEntity task = (TaskEntity) checkTask(dto.getApprover().getTenantId(), dto.getApprover().buildAssigneeId()
                , dto.getTaskId());
        // 校验流程实例存在
        HistoricProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId(),
                dto.getApprover().getTenantId(), true);
        if (instance == null) {
            throw new WorkflowEngineException(PROCESS_INSTANCE_NOT_EXISTS);
        }

        if (StringUtils.hasLength(dto.getAdvice())) {
            Authentication.setAuthenticatedUserId(dto.getApprover().buildAssigneeId());
            taskService.addComment(dto.getTaskId(), instance.getId(), COMMENT_TYPE_ADVICE, dto.getAdvice());
        }
        if (Objects.nonNull(dto.getNextApprover())) {
            runtimeService.setVariable(task.getExecutionId(), INTERNAL_SPECIFY_NEXT_APPROVER, dto.getNextApprover());
        }
        taskService.setAssignee(task.getId(), dto.getApprover().buildAssigneeId());
        // 主动设置下级审批人
        // 完成任务，审批通过
        // FIXME 如果 task 被重复删除会抛出异常
        if (StringUtil.isNotEmpty(task.getExecutionId())) {
            taskService.complete(task.getId(), runtimeService.getVariables(task.getExecutionId()));
        } else {
            //加签子任务 没有executionId
            taskService.complete(task.getId());
        }
        String parentTaskId = task.getParentTaskId();
        //add by zuoqinbo 处理加签任务，分为向前加签和向后加签
        if (cn.azxo.framework.common.utils.StringUtils.isNotBlank(parentTaskId)) {
            String tableName = managementService.getTableName(TaskEntity.class);
            String sql = "select count(1) from " + tableName + " where PARENT_TASK_ID_=#{parentTaskId}";
            long subTaskCount =
                    taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).count();
            TaskEntity taskEntity = (TaskEntity) taskService.createTaskQuery().taskId(parentTaskId).singleResult();
            //当前任务是加签任务,parentTask下没有其他子任务 subTaskCount == 0
            //目前子任务是依次顺序审批, taskService.complete(task.getId()) 已经把当前任务完成,则subTaskCount == 0
            log.info("加签父任务:{},子任务:{},subTaskCount:{},正在进行审批！", parentTaskId, task.getId(), subTaskCount);
            if (subTaskCount == 0) {
                //让剩余加签人进行处理
                List<BpmnTaskDelegateAssigner> remainAssignerList =
                        (List<BpmnTaskDelegateAssigner>) taskService.getVariable(taskEntity.getId(),
                                COUNTERSIGN_REMAIN_ASSIGNER_LIST, List.class);
                //如果加签流程还未流转完，还有剩余加签人
                if (!CollectionUtils.isEmpty(remainAssignerList)) {
                    BpmnTaskDelegateAssigner remainAssigner = remainAssignerList.remove(0);
                    log.info("加签父任务:{},下一级流程处理人:{},", parentTaskId,
                            JSONUtil.toJsonStr(Lists.newArrayList(remainAssigner)));
                    this.createSubTask(taskEntity, parentTaskId, remainAssigner.buildAssigneeId());
                    taskService.setVariable(taskEntity.getId(), COUNTERSIGN_REMAIN_ASSIGNER_LIST, remainAssignerList);
                } else {
                    //没有剩余加签人进行加签,则是最后一个完成加签的操作
                    if (BpmnCountersignType.FORWARD_COUNTERSIGN.getType().equals(taskEntity.getScopeType())) {
                        String originAssigneeId = (String) taskService.getVariable(taskEntity.getId(),
                                COUNTERSIGN_ORIGIN_ASSIGNER);
                        //加签父任务向前加签回归原始发起加签的用户
                        taskService.setAssignee(parentTaskId, originAssigneeId);
                        log.info("加签父任务:{},向前加签,回归流程原始处理人:{},", parentTaskId, originAssigneeId);
                    } else if (BpmnCountersignType.BACK_COUNTERSIGN.getType().equals(taskEntity.getScopeType())) {
                        //向后加签 会直接完成该任务
                        taskService.complete(parentTaskId);
                        log.info("加签父任务:{},向后加签,自动完成", parentTaskId);
                    }
                }
            }
        }
    }

    /**
     * 创建加签子任务
     * A加签给A A再加签给A
     * A加签给B B再加签给A 再加签给B
     * A加签给B B加签给C 再加签A
     * A加签给B B加签给C 再加签D
     *
     * @param countersignDTO 加签参数
     * @param taskEntity     父任务
     */
    private void createSignSubTasks(BpmnTaskCountersignDTO countersignDTO, TaskEntity taskEntity) {
        //如果还有预留或者继承的加签人
        List<BpmnTaskDelegateAssigner> targetAssignerList =
                (List<BpmnTaskDelegateAssigner>) taskService.getVariable(taskEntity.getId(),
                        COUNTERSIGN_REMAIN_ASSIGNER_LIST);
        List<BpmnTaskDelegateAssigner> newTargetAssignerList = Lists.newArrayList();
        //为了保证顺序，优先使用当前剩余加签人列表
        newTargetAssignerList.addAll(countersignDTO.getTargetAssignerList());
        //再添加之前剩余加签人列表
        if (org.apache.commons.collections.CollectionUtils.isNotEmpty(targetAssignerList)) {
            newTargetAssignerList.addAll(targetAssignerList);
        }
        log.info("正在进行加签任务:{},待加签人合并列表:{}", taskEntity.getId(), JSONUtil.toJsonStr(newTargetAssignerList));
        if (org.apache.commons.collections.CollectionUtils.isNotEmpty(newTargetAssignerList)) {
            String parentTaskId = taskEntity.getParentTaskId();
            if (cn.azxo.framework.common.utils.StringUtils.isBlank(parentTaskId)) {
                parentTaskId = taskEntity.getId();
            }
            String finalParentTaskId = parentTaskId;
            BpmnTaskDelegateAssigner removeAssigner = newTargetAssignerList.remove(0);
            //创建一个待加签任务，并且分配给下一个加签人
            this.createSubTask(taskEntity, finalParentTaskId, removeAssigner.buildAssigneeId());
            //动态设置剩余加签人列表,用于任务传递
            taskService.setVariable(taskEntity.getId(), COUNTERSIGN_REMAIN_ASSIGNER_LIST, newTargetAssignerList);
        }
    }

    /**
     * 创建子任务
     *
     * @param parentTask 子任务继承原始任务信息
     * @param assignee   子任务的执行人
     * @return
     */
    protected TaskEntity createSubTask(TaskEntity parentTask, String parentTaskId, String assignee) {
        TaskEntity task = null;
        if (parentTask != null) {
            //1.生成新的工作流子任务
            task = (TaskEntity) taskService.newTask(new TimeBasedIdGenerator().getNextId());
            task.setCategory(parentTask.getCategory());
            task.setDescription(parentTask.getDescription());
            task.setTenantId(parentTask.getTenantId());
            task.setAssignee(assignee);
            task.setName(parentTask.getName());
            task.setParentTaskId(parentTaskId);
            task.setProcessDefinitionId(parentTask.getProcessDefinitionId());
            task.setProcessInstanceId(parentTask.getProcessInstanceId());
            task.setTaskDefinitionKey(parentTask.getTaskDefinitionKey());
            task.setTaskDefinitionId(parentTask.getTaskDefinitionId());
            //task.setExecutionId(parentTask.getExecutionId());
            task.setPriority(parentTask.getPriority());
            task.setCreateTime(new Date());
            log.info("流程加签父任务:{},正在创建加签子任务:{},", parentTaskId, JSONUtil.toJsonStr(Lists.newArrayList(task)));
            taskService.saveTask(task);
        }
        return task;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rejectTask(BpmnTaskAuditDTO dto) {
        // 校验任务存在
        Task task = checkTask(dto.getApprover().getTenantId(), dto.getApprover().buildAssigneeId(), dto.getTaskId());

        // 校验流程实例存在
        HistoricProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId(),
                dto.getApprover().getTenantId(), false);

        if (instance == null) {
            throw new WorkflowEngineException(PROCESS_INSTANCE_NOT_EXISTS);
        }
        if (StringUtils.hasLength(dto.getAdvice())) {
            Authentication.setAuthenticatedUserId(dto.getApprover().buildAssigneeId());
            taskService.addComment(dto.getTaskId(), instance.getId(), COMMENT_TYPE_ADVICE, dto.getAdvice());
        }
        // 多实例
        Activity activity;
        if (isMultiInstance(activity = getActivity((TaskEntity) task))) {
            Integer currentNumberOfInstances = (Integer) runtimeService.getVariable(task.getExecutionId(),
                    NUMBER_OF_INSTANCES);
            // 不能与 deleteMultiInstanceExecution 换执行顺序
            if (isSequentialMultiInstance(activity)) {
                int loopCounter = (int) runtimeService.getVariable(task.getExecutionId(), MULTI_INSTANCE_LOOP_COUNTER);
                if (currentNumberOfInstances == 1) {
                    // 强转修改多实例的模型,防止因为后续删除多实例执行体内创建一个空任务
                    activity.getLoopCharacteristics().setSequential(false);
                }
                runtimeService.setVariable(task.getExecutionId(), MULTI_INSTANCE_LOOP_COUNTER, ++loopCounter);
            }

            runtimeService.deleteMultiInstanceExecution(task.getExecutionId(), false);
            taskService.deleteTask(task.getId(), dto.getAdvice());
            if (Objects.nonNull(currentNumberOfInstances) && currentNumberOfInstances == 1) {
                finishProcessInstance(dto, instance);
            }
        } else {
            finishProcessInstance(dto, instance);
        }

    }

    /**
     * 审批拒绝时,自动解决整个流程实例
     *
     * @param dto
     * @param instance
     */
    private void finishProcessInstance(BpmnTaskAuditDTO dto, HistoricProcessInstance instance) {
        Map<String, Object> variables = new HashMap<>();
        variables.put(INTERNAL_END_TENANT_ID, dto.getApprover().getTenantId());
        variables.put(INTERNAL_END_USER_NAME, dto.getApprover().getAssignerName());
        variables.put(INTERNAL_END_USER_ID, dto.getApprover().buildAssigneeId());
        variables.put(INTERNAL_DELETE_PROCESS_FLAG, INTERNAL_PROCESS_TYPE_REJECT);
        runtimeService.setVariables(instance.getId(), variables);

        // 删除流程实例，以实现驳回任务时，取消整个审批流程
        processInstanceService.deleteProcessInstance(instance.getId(), APPROVAL_ENDS_AUTOMATICALLY);
    }

    @Override
    public List<BpmnHistoricTaskInstanceVO> getHistoricTaskListByProcessInstanceId(String processInstanceId,
                                                                                   String tenantId) {
        HistoricTaskInstanceQuery query =
                historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId);
        if (StringUtils.hasLength(tenantId)) {
            query.taskTenantId(tenantId);
        }
        HistoricProcessInstanceQuery instanceQuery =
                historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId);
        if (StringUtils.hasLength(tenantId)) {
            instanceQuery.processInstanceTenantId(tenantId);
        }
        HistoricProcessInstance instance = instanceQuery.singleResult();
        if (Objects.isNull(instance)) {
            throw new WorkflowEngineException(PROCESS_INSTANCE_ID_NOT_EXISTS, processInstanceId);
        }

        List<HistoricTaskInstance> taskInstances = query.orderByHistoricTaskInstanceStartTime().desc() // 创建时间倒序
                .list();
        // 过滤了多实例或签自动完成的任务
        List<BpmnHistoricTaskInstanceVO> vos = historicTaskInstanceConverter.toVosSkipMiEnd(taskInstances);
        Map<String, List<Comment>> commentByTaskIdMap =
                taskService.getProcessInstanceComments(processInstanceId).stream().collect(Collectors.groupingBy(Comment::getTaskId));

        Map<String, HistoricVariableInstance> variableInstanceMap =
                historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).list().stream().collect(Collectors.toMap(HistoricVariableInstance::getVariableName, Function.identity(), (s, t) -> s));

        Set<String> taskDefinitionKeys = new HashSet<>();
        int count = 0;
        BpmnProcessInstanceResultEnum finalBusinessStatus = valueOfStatus(instance.getBusinessStatus());
        for (BpmnHistoricTaskInstanceVO vo : vos) {
            if (count == 0 || taskDefinitionKeys.contains(vo.getTaskDefinitionKey())) {
                vo.setResult(finalBusinessStatus);
                if (Objects.nonNull(vo.getEndTime()) && !Objects.equals(finalBusinessStatus, REJECTED) && !Objects.equals(finalBusinessStatus, CANCELLED)) {
                    vo.setResult(APPROVED);
                }
            } else {
                vo.setResult(APPROVED);
            }
            taskDefinitionKeys.add(vo.getTaskDefinitionKey());
            List<Comment> taskComments = commentByTaskIdMap.getOrDefault(vo.getTaskId(), Collections.emptyList());

            Optional<Comment> advice = taskComments.stream().filter(i -> Objects.equals(i.getUserId(),
                    vo.getAssignee())).filter(i -> Objects.equals(i.getType(), COMMENT_TYPE_ADVICE)).findFirst();
            advice.ifPresent(i -> vo.setAdvice(i.getFullMessage()));

            List<Comment> comments = taskComments.stream().filter(i -> Objects.equals(i.getUserId(),
                    vo.getAssignee())).filter(i -> Objects.equals(i.getType(), TYPE_COMMENT)).collect(Collectors.toList());
            vo.setComments(commentConverter.toVos(comments));

            HistoricVariableInstance assgineeSnapshot =
                    variableInstanceMap.getOrDefault(INTERNAL_TASK_RELATION_ASSIGNEE_INFO_SNAPSHOT + vo.getTaskId(),
                            null);

            BpmnTaskDelegateAssigner assigner = Objects.nonNull(assgineeSnapshot) ?
                    (BpmnTaskDelegateAssigner) assgineeSnapshot.getValue() : null;
            vo.setAssignee(Objects.isNull(assigner) ? "" : assigner.getAssignee());
            vo.setAssigneeSnapshot(assigner);
            count++;
        }
        return vos;
    }

    @Override
    public List<BpmnHistoricTaskInstanceGroupVO> getHistoricTaskListGroupByProcessInstanceId(String processInstanceId
            , String tenantId) {
        List<BpmnHistoricTaskInstanceVO> vos = getHistoricTaskListByProcessInstanceId(processInstanceId, tenantId);

        Map<String, List<BpmnHistoricTaskInstanceVO>> voMapByTaskDefKey =
                vos.stream().collect(Collectors.groupingBy(BpmnHistoricTaskInstanceVO::getTaskDefinitionKey));

        List<BpmnHistoricTaskInstanceGroupVO> groupVos = new ArrayList<>();
        for (Map.Entry<String, List<BpmnHistoricTaskInstanceVO>> entry : voMapByTaskDefKey.entrySet()) {
            BpmnHistoricTaskInstanceGroupVO groupVO = new BpmnHistoricTaskInstanceGroupVO();
            groupVO.setProcessInstanceId(processInstanceId);
            groupVO.setTaskDefinitionKey(entry.getKey());
            if (CollectionUtils.isEmpty(entry.getValue())) {
                // 没有审批人
                groupVO.setResult(PROCESSING);
            } else if (entry.getValue().size() == 1) {
                // 单人审批
                groupVO.setResult(entry.getValue().get(0).getResult());
            } else {
                // 多人审批
                if (entry.getValue().stream().allMatch(i -> Objects.equals(APPROVED, i.getResult()))) {
                    groupVO.setResult(APPROVED);
                } else if (entry.getValue().stream().anyMatch(i -> Objects.equals(PROCESSING, i.getResult()))) {
                    groupVO.setResult(PROCESSING);
                } else if (entry.getValue().stream().anyMatch(i -> Objects.equals(REJECTED, i.getResult()))) {
                    // FIXME 等版本迭代进行逻辑修复
                    groupVO.setResult(REJECTED);
                }
            }
            groupVO.setTasks(entry.getValue());
            groupVos.add(groupVO);
        }
        return groupVos;
    }

    @Override
    public List<BpmnTaskInstanceVO> getActiveTasksByProcessInstanceId(String processInstanceId, String tenantId) {
        TaskQuery query = taskService.createTaskQuery().processInstanceId(processInstanceId);
        if (StringUtils.hasLength(tenantId)) {
            query.taskTenantId(tenantId);
        }
        List<BpmnTaskInstanceVO> vos = bpmnTaskConverter.toVos(query.list());
        List<String> snapshotTaskIds =
                vos.stream().map(i -> INTERNAL_TASK_RELATION_ASSIGNEE_INFO_SNAPSHOT + i.getTaskId()).collect(Collectors.toList());
        Map<String, VariableInstance> instanceMap = runtimeService.getVariableInstances(processInstanceId,
                snapshotTaskIds);
        vos.forEach(i -> i.setAssigner((BpmnTaskDelegateAssigner) instanceMap.get(INTERNAL_TASK_RELATION_ASSIGNEE_INFO_SNAPSHOT + i.getTaskId()).getValue()));
        return vos;
    }

    /**
     * •
     * 校验任务是否存在， 并且是否是分配给自己的任务
     *
     * @param tenantId 租户ID
     * @param assignee 用户ID
     * @param taskId   任务ID
     */
    private Task checkTask(String tenantId, String assignee, String taskId) {
        Task task = getTask(taskId, null, null);
        if (Objects.nonNull(task) && !Objects.equals(assignee, task.getAssignee())) {
            throw new WorkflowEngineException(TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF);
        }
        if (Objects.isNull(task)) {
            throw new WorkflowEngineException(TASK_COMPLETE_FAIL_NOT_EXISTS);
        }
        return task;
    }

    private Task getTask(String id, String assignee, String tenantId) {
        TaskQuery taskQuery = taskService.createTaskQuery().taskId(id);
        if (StringUtils.hasLength(assignee)) {
            taskQuery.taskAssignee(assignee);
        }
        if (StringUtils.hasLength(tenantId)) {
            taskQuery.taskTenantId(tenantId);
        }
        return taskQuery.singleResult();
    }

    @Override
    public FormInfo getTaskFormModel(String taskId, String tenantId) {
        Task task = getTask(taskId, null, tenantId);
        if (StringUtils.hasLength(task.getFormKey())) {
            return taskService.getTaskFormModel(taskId);
        }
        return null;
    }

    @Override
    public void assigneeTask(BpmnTaskAssigneeDTO dto) {
        Task task = taskService.createTaskQuery().taskId(dto.getTaskId()).active().singleResult();
        if (Objects.isNull(task)) {
            throw new WorkflowEngineException(TASK_COMPLETE_FAIL_NOT_EXISTS);
        }
        if ((StringUtils.hasLength(task.getAssignee()) && Objects.isNull(dto.getOriginAssigner())) || !Objects.equals(task.getAssignee(), dto.getOriginAssigner().buildAssigneeId())) {
            throw new WorkflowEngineException(TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF);
        }

        if (Objects.isNull(dto.getTargetAssigner())) {
            taskService.setAssignee(task.getId(), "");
            runtimeService.removeVariable(task.getProcessInstanceId(),
                    INTERNAL_TASK_RELATION_ASSIGNEE_INFO_SNAPSHOT + task.getId());
        } else {
            runtimeService.setVariable(task.getProcessInstanceId(),
                    INTERNAL_TASK_RELATION_ASSIGNEE_INFO_SNAPSHOT + task.getId(), dto.getTargetAssigner());
            taskService.setAssignee(task.getId(), dto.getTargetAssigner().buildAssigneeId());
        }
    }

    @Override
    public void commentTask(BpmnTaskCommentDTO dto) {
        TaskQuery query = taskService.createTaskQuery().taskId(dto.getTaskId());
        if (StringUtils.hasLength(dto.getOperator().getTenantId())) {
            query.taskTenantId(dto.getOperator().getTenantId());
        }
        Task task = query.singleResult();
        if (Objects.isNull(task)) {
            throw new WorkflowEngineException(TASK_COMPLETE_FAIL_NOT_EXISTS);
        }
        taskService.addComment(dto.getTaskId(), dto.getProcessInstanceId(), dto.getComment());
    }

    @Override
    public void attachmentTask(BpmnTaskAttachmentDTO dto) {
        taskService.createAttachment(dto.getType(), dto.getTaskId(), null, dto.getName(), dto.getDescription(),
                dto.getUrl());
        // TODO 图片附件的保存
        //        taskService.saveAttachment();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void countersignTask(BpmnTaskCountersignDTO countersignDTO) {
        String tenantId = countersignDTO.getOriginAssigner().getTenantId();
        TaskEntityImpl taskEntity =
                (TaskEntityImpl) taskService.createTaskQuery().taskId(countersignDTO.getTaskId()).active().singleResult();
        if (Objects.isNull(taskEntity)) {
            throw new WorkflowEngineException(TASK_COMPLETE_FAIL_NOT_EXISTS);
        }
        if (org.apache.commons.lang.StringUtils.isNotEmpty(tenantId)) {
            taskEntity.setTenantId(tenantId);
        }
        if (BpmnCountersignType.isValidAppType(countersignDTO.getCountersignType()) != null) {
            //加签不能改变BPMN2.0原流程定义，因为下一个流程实例，不需要加签
            String parentTaskId = taskEntity.getParentTaskId();
            BpmnTaskDelegateAssigner originAssigner = countersignDTO.getOriginAssigner();
            //更新当前父任务信息，如果该父任务被加签后，当前任务用户将看不到该代办信息
            //或者其他方式隐藏
            if (StringUtil.isEmpty(parentTaskId)) {
                taskEntity.setOwner(taskEntity.getId());
                taskEntity.setAssignee(taskEntity.getId());
            }
            taskEntity.setScopeType(countersignDTO.getCountersignType());
            taskService.saveTask(taskEntity);
            //更新父任务待加签人列表
            taskService.setVariable(taskEntity.getId(), COUNTERSIGN_ORIGIN_ASSIGNER, originAssigner.buildAssigneeId());
            //该父任务加签时，会创建子任务用于分配给其他人加签
            //目前多个子任务时，会按照加签人的顺序依次进行加签操作，
            //例如 加签按顺序依次分配给A、B、C三人,需A任务进行完成后，进行B任务、再进行C任务
            this.createSignSubTasks(countersignDTO, taskEntity);
        } else {
            throw new WorkflowEngineException(TASK_APOSTILLE_NOT_SUPPORT);
        }
    }

    @Override
    public void remindTask(BpmnTaskRemindDTO dto) {
        TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(dto.getProcessInstanceId());
        if (StringUtils.hasLength(dto.getTaskDefinitionKey())) {
            taskQuery.taskDefinitionKey(dto.getTaskDefinitionKey());
        }
        if (StringUtils.hasLength(dto.getTenantId())) {
            taskQuery.processInstanceId(dto.getTenantId());
        }
        List<Task> list = taskQuery.active().list();
        if (CollectionUtils.isEmpty(list)) {
            throw new WorkflowEngineException(TASK_REMIND_ERROR_NOT_EXISTS);
        }

        if (CollectionUtils.isEmpty(dto.getRemindTypes())) {
            return;
        }
        ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration();
        FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher();
        ProcessInstance processInstance =
                runtimeService.createProcessInstanceQuery().processInstanceId(dto.getProcessInstanceId()).singleResult();
        Optional<BpmnNoticeConf> noticeConfig =
                BpmnMetaParserHelper.getNoticeConfig(ProcessDefinitionUtil.getProcess(processInstance.getProcessDefinitionId()));
        List<BpmnTaskDelegateAssigner> assigners =
                (List<BpmnTaskDelegateAssigner>) runtimeService.getVariable(dto.getProcessInstanceId(),
                        INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + dto.getTaskDefinitionKey());

        // 过滤出未审批的任何,用选择的方式去发送消息
        dto.getRemindTypes().forEach(type -> {
            list.forEach(task -> {
                assigners.stream().filter(i -> Objects.equals(task.getAssignee(), i.buildAssigneeId())).findAny().ifPresent(assigner -> {
                    MessagePushEventImpl event = MessagePushEventBuilder.createEvent(MessagePushEventType.valueOf(type),
                            assigner, noticeConfig.orElse(null));
                    event.setProcessInstanceId(processInstance.getProcessInstanceId());
                    event.setTaskId(task.getId());
                    eventDispatcher.dispatchEvent(event, processEngineConfiguration.getEngineCfgKey());
                });
            });
        });
    }

    private Activity getActivity(TaskEntity task) {
        BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
        FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey());
        if (flowElement instanceof UserTask) {
            return (Activity) flowElement;
        }
        return null;
    }

    private boolean isMultiInstance(Activity activity) {
        if (Objects.nonNull(activity)) {
            return activity.hasMultiInstanceLoopCharacteristics();
        }
        return false;
    }

    private boolean isSequentialMultiInstance(Activity activity) {
        if (Objects.nonNull(activity) && Objects.nonNull(activity.getLoopCharacteristics())) {
            return activity.getLoopCharacteristics().isSequential();
        }
        return false;
    }

    private ExecutionEntity getMultiInstanceRootExecution(ExecutionEntity executionEntity) {
        ExecutionEntity multiInstanceRootExecution = null;
        ExecutionEntity currentExecution = executionEntity;
        while (currentExecution != null && multiInstanceRootExecution == null && currentExecution.getParent() != null) {
            if (currentExecution.isMultiInstanceRoot()) {
                multiInstanceRootExecution = currentExecution;
            } else {
                currentExecution = currentExecution.getParent();
            }
        }
        return multiInstanceRootExecution;
    }

}
