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

import cn.axzo.framework.domain.ServiceException;
import cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum;
import cn.axzo.workflow.common.enums.BpmnFlowNodeType;
import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnNodeBackSystemOperateDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnOptionalNodeDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCompleteDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnRobotTaskCreateDTO;
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.BpmnTaskAuditWithFormDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskBackAuditDTO;
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.request.bpmn.task.BpmnTaskResetApproversDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskTransferDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.ExtHiTaskSearchDTO;
import cn.axzo.workflow.common.model.response.BpmPageResult;
import cn.axzo.workflow.common.model.response.bpmn.BatchOperationItemResultVO;
import cn.axzo.workflow.common.model.response.bpmn.BatchOperationResultVO;
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.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.conf.SupportRefreshProperties;
import cn.axzo.workflow.core.engine.cmd.CustomApproveTaskAsyncCmd;
import cn.axzo.workflow.core.engine.cmd.CustomApproveTaskCmd;
import cn.axzo.workflow.core.engine.cmd.CustomApproveTaskWithFormAsyncCmd;
import cn.axzo.workflow.core.engine.cmd.CustomApproveTaskWithFormCmd;
import cn.axzo.workflow.core.engine.cmd.CustomBackTaskAsyncCmd;
import cn.axzo.workflow.core.engine.cmd.CustomBackTaskCmd;
import cn.axzo.workflow.core.engine.cmd.CustomCommentTaskCmd;
import cn.axzo.workflow.core.engine.cmd.CustomCompleteDummyTaskCmd;
import cn.axzo.workflow.core.engine.cmd.CustomCountersignUserTaskAsyncCmd;
import cn.axzo.workflow.core.engine.cmd.CustomCountersignUserTaskCmd;
import cn.axzo.workflow.core.engine.cmd.CustomCreateDummyTaskCmd;
import cn.axzo.workflow.core.engine.cmd.CustomRejectionTaskAsyncCmd;
import cn.axzo.workflow.core.engine.cmd.CustomRejectionTaskCmd;
import cn.axzo.workflow.core.engine.cmd.CustomRemindTaskAsyncCmd;
import cn.axzo.workflow.core.engine.cmd.CustomRemindTaskCmd;
import cn.axzo.workflow.core.engine.cmd.CustomResetTaskApproversAsyncCmd;
import cn.axzo.workflow.core.engine.cmd.CustomResetTaskApproversCmd;
import cn.axzo.workflow.core.engine.cmd.CustomTransferUserTaskAsyncCmd;
import cn.axzo.workflow.core.engine.cmd.CustomTransferUserTaskCmd;
import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst;
import cn.axzo.workflow.core.service.BpmnProcessDefinitionService;
import cn.axzo.workflow.core.service.BpmnProcessTaskService;
import cn.axzo.workflow.core.service.ExtAxHiTaskInstService;
import cn.axzo.workflow.core.service.ExtAxProcessLogService;
import cn.axzo.workflow.core.service.converter.BpmnHistoricAttachmentConverter;
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 lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.flowable.bpmn.model.BaseElement;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
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.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.task.Attachment;
import org.flowable.engine.task.Comment;
import org.flowable.form.api.FormInfo;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskInfo;
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.HistoricTaskInstanceEntity;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.flowable.variable.api.persistence.entity.VariableInstance;
import org.flowable.variable.service.impl.persistence.entity.HistoricVariableInstanceEntityImpl;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_ID_NOT_EXISTS;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_TASK_NOT_EXISTS;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.BACK_NODE_CANNOT_REACHABLE;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.FIND_TASK_BY_PERSON_ID_ERROR;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.REACHED_BACKED_MAXIMUM_NUM;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_COMPLETE_FAIL_NOT_EXISTS;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.TASK_HAS_BEEN_COMPLETE;
import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE;
import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_COMMENT_EXT;
import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_INFO;
import static cn.axzo.workflow.common.constant.BpmnConstants.MAX_BACKED_OPERATE_COUNT;
import static cn.axzo.workflow.common.constant.BpmnConstants.NO_ASSIGNEE;
import static cn.axzo.workflow.common.constant.BpmnConstants.OLD_INTERNAL_TASK_RELATION_ASSIGNEE_INFO_SNAPSHOT;
import static cn.axzo.workflow.common.constant.BpmnConstants.WORKFLOW_ENGINE_VERSION;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_BUSINESS;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_CARBON_COPY;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_EMPTY;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_SIGN;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_TASK;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.BACKED;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.DELETED;
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.common.util.BpmnNativeQueryUtil.countSql;
import static cn.axzo.workflow.common.util.BpmnNativeQueryUtil.sqlConnectors;
import static cn.axzo.workflow.core.common.utils.BpmnCollectionUtils.convertSet;

@Service
@Slf4j
public class BpmnProcessTaskServiceImpl implements BpmnProcessTaskService {

    @Resource
    private TaskService taskService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private HistoryService historyService;
    @Resource
    private BpmnProcessInstanceServiceImpl processInstanceService;
    @Resource
    private BpmnTaskTodoPageItemConverter todoPageItemConverter;
    @Resource
    private BpmnTaskDonePageItemConverter donePageItemConverter;
    @Resource
    private BpmnHistoricTaskInstanceConverter historicTaskInstanceConverter;
    @Resource
    private BpmnTaskConverter bpmnTaskConverter;
    @Resource
    private BpmnHistoricAttachmentConverter attachmentConverter;
    @Resource
    private ManagementService managementService;
    @Resource
    private SpringProcessEngineConfiguration processEngineConfiguration;
    @Resource
    private ExtAxHiTaskInstService extAxHiTaskInstService;
    @Resource
    private RepositoryService repositoryService;
    @Resource
    @Lazy
    private BpmnProcessTaskService bpmnProcessTaskService;
    @Resource
    private BpmnProcessDefinitionService bpmnProcessModelService;
    @Resource
    private ExtAxProcessLogService extAxProcessLogService;
    @Resource
    private ExtAxProcessLogService processLogService;
    @Resource
    private SupportRefreshProperties refreshProperties;

    @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 (Objects.nonNull(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 (Objects.nonNull(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) {
        CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
        if (Boolean.TRUE.equals(dto.getAsync())) {
            commandExecutor.execute(new CustomApproveTaskAsyncCmd(dto));
        } else {
            commandExecutor.execute(new CustomApproveTaskCmd(dto));
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void approveTaskWithForm(BpmnTaskAuditWithFormDTO dto) {
        // 表单级别暂时只允许同步
        CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
        if (Boolean.TRUE.equals(dto.getAsync())) {
            commandExecutor.execute(new CustomApproveTaskWithFormAsyncCmd(dto));
        } else {
            commandExecutor.execute(new CustomApproveTaskWithFormCmd(dto));
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void backTask(BpmnTaskBackAuditDTO dto) {
        Task task = processEngineConfiguration.getTaskService().createTaskQuery().taskId(dto.getTaskId()).singleResult();
        if (task == null) {
            throw new WorkflowEngineException(PROCESS_TASK_NOT_EXISTS);
        }
        List<BpmnOptionalNodeDTO> backOptionalNodes = getBackOptionalNodes(task.getProcessInstanceId(), task.getTaskDefinitionKey());
        if (CollectionUtils.isEmpty(backOptionalNodes)) {
            throw new WorkflowEngineException(BACK_NODE_CANNOT_REACHABLE, dto.getToActivityId());
        }
        List<String> activityList = backOptionalNodes.stream().map(BpmnOptionalNodeDTO::getProcessActivityId).collect(Collectors.toList());
        if (!activityList.contains(dto.getToActivityId())) {
            throw new WorkflowEngineException(BACK_NODE_CANNOT_REACHABLE, dto.getToActivityId());
        }
        Optional<String> instOpt = backOptionalNodes.stream()
                .map(BpmnOptionalNodeDTO::getProcessInstanceId)
                .filter(StringUtils::hasText)
                .findAny();
        if (instOpt.isPresent()) {
            ExtHiTaskSearchDTO searchDTO = new ExtHiTaskSearchDTO();
            searchDTO.setProcessInstanceId(instOpt.get());
            List<ExtAxHiTaskInst> extAxHiTaskInsts = extAxHiTaskInstService.queryList(searchDTO);
            if (CollectionUtils.isNotEmpty(extAxHiTaskInsts)) {
                long backOpeCount = extAxHiTaskInsts.stream()
                        .filter(ext -> BACKED.getStatus().equals(ext.getStatus()))
                        .count();
                if (backOpeCount > MAX_BACKED_OPERATE_COUNT) {
                    throw new WorkflowEngineException(REACHED_BACKED_MAXIMUM_NUM, String.valueOf(MAX_BACKED_OPERATE_COUNT));
                }
            }
        }
        CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
        if (Boolean.TRUE.equals(dto.getAsync())) {
            commandExecutor.execute(new CustomBackTaskAsyncCmd(dto));
        } else {
            commandExecutor.execute(new CustomBackTaskCmd(CustomBackTaskCmd.CustomBackParamsDto.builder()
                    .targetTaskIds(Collections.singletonList(dto.getTaskId()))
                    .advice(dto.getAdvice())
                    .operationDesc(dto.getOperationDesc())
                    .attachmentList(dto.getAttachmentList())
                    .operator(dto.getApprover())
                    .processInstanceId(task.getProcessInstanceId())
                    .currentActivityId(task.getTaskDefinitionKey())
                    .toActivityId(dto.getToActivityId())
                    .validateApprover(true)
                    .build()));
        }
    }

    @Override
    public void systemBackTask(BpmnNodeBackSystemOperateDTO dto) {
        //需要查询当前流程是否停留在当前节点
        //需要查询退回的节点是否可达
        //然后进行退回操作
        List<HistoricTaskInstance> taskList = processEngineConfiguration.getHistoryService()
                .createHistoricTaskInstanceQuery()
                .taskDefinitionKey(dto.getCurrentActivityId())
                .processInstanceId(dto.getProcessInstanceId())
                .list();
        if (CollectionUtils.isEmpty(taskList)) {
            throw new WorkflowEngineException(TASK_COMPLETE_FAIL_NOT_EXISTS);
        }
        List<HistoricTaskInstance> valuableTasks = taskList.stream()
                .filter(t -> Objects.isNull(t.getEndTime()))
                .filter(t -> {
                    if (CollectionUtils.isNotEmpty(dto.getTargetTaskIds())) {
                        return dto.getTargetTaskIds().contains(t.getId());
                    }
                    return true;
                })
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(valuableTasks)) {
            throw new WorkflowEngineException(TASK_HAS_BEEN_COMPLETE);
        }
        List<BpmnOptionalNodeDTO> backOptionalNodes = getBackOptionalNodes(valuableTasks.get(0).getProcessInstanceId(), valuableTasks.get(0).getTaskDefinitionKey());
        if (CollectionUtils.isEmpty(backOptionalNodes)) {
            throw new WorkflowEngineException(BACK_NODE_CANNOT_REACHABLE, dto.getToActivityId());
        }
        List<String> activityList = backOptionalNodes.stream().map(BpmnOptionalNodeDTO::getProcessActivityId).collect(Collectors.toList());
        if (!activityList.contains(dto.getToActivityId())) {
            throw new WorkflowEngineException(BACK_NODE_CANNOT_REACHABLE, dto.getToActivityId());
        }
        Optional<String> instOpt = backOptionalNodes.stream()
                .map(BpmnOptionalNodeDTO::getProcessInstanceId)
                .filter(StringUtils::hasText)
                .findAny();
        if (instOpt.isPresent()) {
            ExtHiTaskSearchDTO searchDTO = new ExtHiTaskSearchDTO();
            searchDTO.setProcessInstanceId(instOpt.get());
            List<ExtAxHiTaskInst> extAxHiTaskInsts = extAxHiTaskInstService.queryList(searchDTO);
            if (CollectionUtils.isNotEmpty(extAxHiTaskInsts)) {
                long backOpeCount = extAxHiTaskInsts.stream()
                        .filter(ext -> BACKED.getStatus().equals(ext.getStatus()))
                        .count();
                if (backOpeCount > MAX_BACKED_OPERATE_COUNT) {
                    throw new WorkflowEngineException(REACHED_BACKED_MAXIMUM_NUM, String.valueOf(MAX_BACKED_OPERATE_COUNT));
                }
            }
        }
        BpmnTaskDelegateAssigner operator = dto.getOperator();
        if (operator == null) {
            operator = new BpmnTaskDelegateAssigner("系统", "system", taskList.get(0).getTenantId());
        }
        CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
        commandExecutor.execute(new CustomBackTaskCmd(CustomBackTaskCmd.CustomBackParamsDto.builder()
                .targetTaskIds(valuableTasks.stream().map(TaskInfo::getId).collect(Collectors.toList()))
                .advice(dto.getAdvice())
                .operationDesc(dto.getOperationDesc())
                .attachmentList(dto.getAttachmentList())
                .operator(operator)
                .processInstanceId(valuableTasks.get(0).getProcessInstanceId())
                .currentActivityId(dto.getCurrentActivityId())
                .toActivityId(dto.getToActivityId())
                .build()));
    }

    @Override
    public List<BpmnOptionalNodeDTO> getBackOptionalNodesByTaskId(String taskId) {
        Task task = processEngineConfiguration.getTaskService().createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            throw new WorkflowEngineException(PROCESS_TASK_NOT_EXISTS);
        }
        return this.getBackOptionalNodes(task.getProcessInstanceId(), task.getTaskDefinitionKey());
    }

    @Override
    public List<BpmnOptionalNodeDTO> getBackOptionalNodes(String processInstanceId, String currentActivityId) {
        //1.获取当前的流程实例
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (processInstance == null) {
            //流程为空，已经结束，返回空
            throw new WorkflowEngineException(PROCESS_INSTANCE_NOT_EXISTS);
        }
        List<BpmnHistoricTaskInstanceVO> tasks = this.getHistoricTaskListByProcessInstanceId(processInstanceId, null);
        tasks = tasks.stream()
                .filter(t -> t.getNodeType() == NODE_STARTER
                        || t.getNodeType() == NODE_TASK
                        || t.getNodeType() == NODE_BUSINESS
                        || t.getNodeType() == NODE_SIGN)
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(tasks)) {
            return Collections.emptyList();
        }
        tasks.sort(Comparator.comparing(BpmnHistoricTaskInstanceVO::getCreateTime));
        LinkedList<Pair<String, List<BpmnHistoricTaskInstanceVO>>> executedList = new LinkedList<>();
        for (BpmnHistoricTaskInstanceVO vo : tasks) {
            Pair<String, List<BpmnHistoricTaskInstanceVO>> last = org.springframework.util.CollectionUtils.isEmpty(executedList) ? null : executedList.getLast();
            if (last != null && last.getLeft().equals(vo.getTaskDefinitionKey())) {
                last.getRight().add(vo);
                continue;
            }
            ArrayList<BpmnHistoricTaskInstanceVO> objects = new ArrayList<>();
            objects.add(vo);
            executedList.addLast(Pair.of(vo.getTaskDefinitionKey(), objects));
        }
        List<Pair<String, List<BpmnHistoricTaskInstanceVO>>> valuableList = new LinkedList<>();
        for (Pair<String, List<BpmnHistoricTaskInstanceVO>> pair : executedList) {
            List<BpmnHistoricTaskInstanceVO> taskInstanceVOList = pair.getRight();
            Optional<BpmnHistoricTaskInstanceVO> backTaskOpt = taskInstanceVOList
                    .stream()
                    .filter(t -> t.getResult() != null)
                    .filter(t -> t.getResult() == BpmnProcessInstanceResultEnum.BACKED)
                    .findFirst();
            if (backTaskOpt.isPresent()) {
                String deleteReason = backTaskOpt.get().getDeleteReason();
                String changeParentActivityTo = deleteReason
                        .replace("Change parent activity to ", "")
                        .replace("Change activity to ", "");
                if (org.springframework.util.CollectionUtils.isEmpty(valuableList)) {
                    throw new ServiceException("状态异常，首个节点进行了退回操作");
                }
                int j = valuableList.size() - 1;
                for (; j >= 0; j--) {
                    Pair<String, List<BpmnHistoricTaskInstanceVO>> vPair = valuableList.get(j);
                    if (vPair.getLeft().equals(changeParentActivityTo)) {
                        break;
                    }
                }
                valuableList = valuableList.subList(0, j);
            } else {
                valuableList.add(pair);
            }
        }
        List<FlowElement> flowElements = bpmnProcessModelService.findFlowElements(processInstance.getProcessDefinitionId());
        Map<String, FlowElement> flowElementMap = flowElements.stream().collect(Collectors.toMap(BaseElement::getId, f -> f));
        AtomicInteger index = new AtomicInteger(0);
        List<BpmnOptionalNodeDTO> resultList = valuableList
                .stream()
                .filter(pair -> !currentActivityId.equals(pair.getLeft())) //排除当前节点
                .map(pair -> flowElementMap.get(pair.getLeft()))
                .filter(flowElement -> {
                    BpmnFlowNodeType currentNodeType = BpmnMetaParserHelper.getNodeType(flowElement).orElse(NODE_EMPTY);
                    return currentNodeType == NODE_TASK || currentNodeType == NODE_BUSINESS || currentNodeType == NODE_SIGN;
                })
                .filter(flowElement -> !NODE_STARTER.getType().equals(flowElement.getId()))
                .map(flowElement -> BpmnOptionalNodeDTO
                        .builder()
                        .processInstanceId(processInstanceId)
                        .processDefinitionId(processInstance.getProcessDefinitionId())
                        .processActivityId(flowElement.getId())
                        .processActivityName(flowElement.getName())
                        .processNodeDesc(flowElement.getName())
                        .nodeType(BpmnMetaParserHelper.getNodeType(flowElement).orElse(NODE_EMPTY))
                        .ordinal(index.incrementAndGet())
                        .build())
                .collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(resultList)) {
            BpmnOptionalNodeDTO bpmnOptionalNodeDTO = resultList.get(resultList.size() - 1);
            bpmnOptionalNodeDTO.setProcessNodeDesc(bpmnOptionalNodeDTO.getProcessActivityName() + "(上一步)");
        }
        resultList.sort((o1, o2) -> o2.getOrdinal() - o1.getOrdinal());
        return resultList;
    }

    @Override
    public BatchOperationResultVO batchApproveTask(List<BpmnTaskAuditDTO> dtos) {
        return batchOperation(dtos, bpmnProcessTaskService::approveTask);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rejectTask(BpmnTaskAuditDTO dto) {
        CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
        if (Boolean.TRUE.equals(dto.getAsync())) {
            commandExecutor.execute(new CustomRejectionTaskAsyncCmd(dto));
        } else {
            commandExecutor.execute(new CustomRejectionTaskCmd(dto, extAxHiTaskInstService));
        }
    }

    @Override
    public BatchOperationResultVO batchRejectTask(List<BpmnTaskAuditDTO> dtos) {
        return batchOperation(dtos, bpmnProcessTaskService::rejectTask);
    }

    @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);
//                        .includeProcessVariables();
        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();
        taskInstances.forEach(task -> ((HistoricTaskInstanceEntity) task).setCreateTime(((HistoricTaskInstanceEntity) task).getLastUpdateTime()));
        taskInstances.sort(Comparator.comparing(p -> ((HistoricTaskInstanceEntity) p).getLastUpdateTime()));

        Map<String, HistoricVariableInstance> variableInstanceMap =
                // 不能使用框架提供的历史变量 API 查询,有 BUG
                historyService.createNativeHistoricVariableInstanceQuery()
                        .sql("select * from ACT_HI_VARINST t where t.proc_inst_id_= #{processInstanceId}")
                        .parameter("processInstanceId", processInstanceId)
                        .list().stream()
                        .collect(Collectors.toMap(HistoricVariableInstance::getVariableName, Function.identity(),
                                (s, t) -> s));
        HistoricVariableInstance instanceVersion = variableInstanceMap.getOrDefault(WORKFLOW_ENGINE_VERSION, null);
        // 过滤了多实例或签自动完成的任务
        List<BpmnHistoricTaskInstanceVO> vos = historicTaskInstanceConverter.toVosSkipSystemOperation(taskInstances,
                Objects.isNull(instanceVersion) ? null :
                        ((HistoricVariableInstanceEntityImpl) instanceVersion).getTextValue());
        Map<String, List<Comment>> commentByTaskIdMap =
                taskService.getProcessInstanceComments(processInstanceId).stream()
                        .collect(Collectors.groupingBy(Comment::getTaskId));

        Map<String, List<Attachment>> attachmentByTaskIdMap =
                taskService.getProcessInstanceAttachments(processInstanceId).stream()
                        .collect(Collectors.groupingBy(Attachment::getTaskId));

        ExtHiTaskSearchDTO searchDTO = new ExtHiTaskSearchDTO();
        searchDTO.setProcessInstanceId(processInstanceId);
        Map<String, ExtAxHiTaskInst> extTaskInstMap = extAxHiTaskInstService.queryList(searchDTO).stream()
                .collect(Collectors.toMap(ExtAxHiTaskInst::getTaskId, Function.identity(), (s, t) -> s));

        BpmnModel bpmnModel = repositoryService.getBpmnModel(instance.getProcessDefinitionId());
        List<BpmnHistoricTaskInstanceVO> resultList = new ArrayList<>();
        for (BpmnHistoricTaskInstanceVO vo : vos) {
            ExtAxHiTaskInst taskInst = extTaskInstMap.getOrDefault(vo.getTaskId(), new ExtAxHiTaskInst());
            if (Objects.equals(taskInst.getStatus(), DELETED.getStatus())) {
                continue;
            }
            resultList.add(vo);
            vo.setResult(valueOfStatus(taskInst.getStatus()));
            List<Comment> taskComments = commentByTaskIdMap.getOrDefault(vo.getTaskId(), Collections.emptyList());

            // 处理 advice
            taskComments.stream().filter(i -> Objects.equals(i.getTaskId(), vo.getTaskId()))
                    .filter(i -> Objects.equals(i.getType(), COMMENT_TYPE_ADVICE)).findFirst()
                    .ifPresent(i -> vo.setAdvice(i.getFullMessage()));

            // 处理 operationDesc
            taskComments.stream().filter(i -> Objects.equals(i.getTaskId(), vo.getTaskId()))
                    .filter(i -> Objects.equals(COMMENT_TYPE_OPERATION_DESC, i.getType()))
                    .max(Comparator.comparing(Comment::getTime))
                    .ifPresent(i -> vo.setOperationDesc(i.getFullMessage()));

            // 处理 CommentExt
            taskComments.stream().filter(i -> Objects.equals(i.getTaskId(), vo.getTaskId()))
                    .filter(i -> Objects.equals(i.getType(), COMMENT_TYPE_COMMENT_EXT)).findFirst()
                    .ifPresent(i -> vo.setCommentExt(i.getFullMessage()));

            List<Attachment> attachments = attachmentByTaskIdMap.getOrDefault(vo.getTaskId(), Collections.emptyList());
            vo.setAttachments(attachmentConverter.toVos(attachments));

            HistoricVariableInstance assginerSnapshot =
                    variableInstanceMap.getOrDefault(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + vo.getTaskId(),
                            null);
            if (Objects.isNull(assginerSnapshot)) {
                assginerSnapshot =
                        variableInstanceMap.getOrDefault(OLD_INTERNAL_TASK_RELATION_ASSIGNEE_INFO_SNAPSHOT + vo.getTaskId(), null);
            }

            BpmnTaskDelegateAssigner assigner = null;
            if (Objects.nonNull(assginerSnapshot)) {
                assigner = BpmnTaskDelegateAssigner.toObjectCompatible(assginerSnapshot.getValue());
            }
            if (Objects.nonNull(assigner) && !Objects.equals(assigner.buildAssigneeId(), NO_ASSIGNEE)) {
                vo.setAssigneeSnapshot(assigner);
            }
            BpmnMetaParserHelper.getNodeType(bpmnModel.getFlowElement(vo.getTaskDefinitionKey())).ifPresent(nodeType -> {
                vo.setNodeType(nodeType);
                if (Objects.equals(NODE_CARBON_COPY, nodeType)) {
                    HistoricVariableInstance carbonUsers =
                            variableInstanceMap.getOrDefault(INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + vo.getTaskDefinitionKey(), null);
                    vo.setForecastAssignees(Objects.isNull(carbonUsers) ? Collections.emptyList() :
                            (List<BpmnTaskDelegateAssigner>) carbonUsers.getValue());
                } else {
                    vo.setForecastAssignees(Collections.emptyList());
                }
            });
            BpmnMetaParserHelper.getApprovalMethod(bpmnModel.getFlowElement(vo.getTaskDefinitionKey()))
                    .ifPresent(vo::setApprovalMethod);
        }
        return resultList;
    }

    /**
     * 这个方法几乎没有人使用
     *
     * @param processInstanceId
     * @param tenantId
     * @return
     */
    @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 + i.getTaskId()).collect(Collectors.toList());
        Map<String, VariableInstance> instanceMap = runtimeService.getVariableInstances(processInstanceId,
                snapshotTaskIds);
        vos.forEach(i -> i.setAssigner(BpmnTaskDelegateAssigner.toObjectCompatible(instanceMap.get(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + i.getTaskId()).getValue())));
        return vos;
    }

    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
    @Transactional(rollbackFor = Exception.class)
    public void transferTask(BpmnTaskTransferDTO dto) {
        CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
        if (dto.getAsync() != null && dto.getAsync()) {
            commandExecutor.execute(new CustomTransferUserTaskAsyncCmd(dto));
        } else {
            commandExecutor.execute(new CustomTransferUserTaskCmd(dto.getTaskId(),
                    dto.getOriginAssigner(), dto.getAdvice(), dto.getAttachmentList(), dto.getTargetAssigner(), dto.getAdditionalOpeDesc()));
        }
    }

    @Override
    public BatchOperationResultVO batchTransferTask(List<BpmnTaskTransferDTO> dtos) {
        return batchOperation(dtos, bpmnProcessTaskService::transferTask);
    }

    private <T> BatchOperationResultVO batchOperation(List<T> dtos, Consumer<T> consumer) {
        BatchOperationResultVO result = new BatchOperationResultVO();
        result.setRequestCount(dtos.size());
        if (CollectionUtils.isEmpty(dtos)) {
            return result;
        }
        List<BatchOperationItemResultVO> details = new ArrayList<>();
        result.setDetails(details);
        dtos.forEach(dto -> {
            BatchOperationItemResultVO itemVO = new BatchOperationItemResultVO();
            details.add(itemVO);
            itemVO.setType("流程");
            try {
                if (dto instanceof BpmnTaskTransferDTO) {
                    itemVO.setId(((BpmnTaskTransferDTO) dto).getTaskId());
                }
                if (dto instanceof BpmnTaskAuditDTO) {
                    itemVO.setId(((BpmnTaskAuditDTO) dto).getTaskId());
                }
                consumer.accept(dto);
            } catch (Exception e) {
                itemVO.setHasError(true);
                itemVO.setErrorMessage(e.getMessage());
            }
        });
        result.setFailCount((int) result.getDetails().stream().filter(BatchOperationItemResultVO::getHasError).count());
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void commentTask(BpmnTaskCommentDTO dto) {
        CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
        commandExecutor.execute(new CustomCommentTaskCmd(dto.getProcessInstanceId(),
                dto.getOperator(), dto.getComment(), dto.getCommentExt(), dto.getAttachmentList(),
                extAxHiTaskInstService));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void attachmentTask(BpmnTaskAttachmentDTO dto, String assignee) {
        Authentication.setAuthenticatedUserId(assignee);
        Attachment attachment = taskService.createAttachment(dto.getType(), dto.getTaskId(),
                dto.getProcessInstanceId(), dto.getName(), dto.getDescription(),
                dto.getUrl());
        taskService.saveAttachment(attachment);
        Authentication.setAuthenticatedUserId(null);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void countersignTask(BpmnTaskCountersignDTO dto) {
        CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
        if (dto.getAsync() != null && dto.getAsync()) {
            //异步处理
            commandExecutor.execute(new CustomCountersignUserTaskAsyncCmd(dto));
        } else {
            commandExecutor.execute(new CustomCountersignUserTaskCmd(BpmnCountersignTypeEnum.valueOfType(dto.getCountersignType()), dto.getTaskId(),
                    dto.getOriginAssigner(), dto.getAdvice(), dto.getAttachmentList(), dto.getTargetAssignerList(),
                    extAxHiTaskInstService));
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void resetTaskApprovers(BpmnTaskResetApproversDTO dto) {
        CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
        if (dto.getAsync() != null && dto.getAsync()) {
            commandExecutor.execute(new CustomResetTaskApproversAsyncCmd(dto));
        } else {
            commandExecutor.execute(new CustomResetTaskApproversCmd(dto.getTaskId(), dto.getAdvice(),
                    dto.getAttachmentList(), dto.getOriginAssigner(), dto.getTargetAssignerList(), extAxHiTaskInstService));
        }
    }

    @Override
    public void remindTask(BpmnTaskRemindDTO dto) {
        CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
        if (Boolean.TRUE.equals(dto.getAsync())) {
            commandExecutor.execute(new CustomRemindTaskAsyncCmd(dto));
        } else {
            commandExecutor.execute(new CustomRemindTaskCmd(dto.getTerminalType(), dto.getProcessInstanceId(), dto.getTaskDefinitionKey(), dto.getRemindTypes(), refreshProperties));
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String createRobotTask(BpmnRobotTaskCreateDTO dto) {
        CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
        return commandExecutor.execute(new CustomCreateDummyTaskCmd(dto, extAxHiTaskInstService));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void completeRobotTask(BpmnRobotTaskCompleteDTO dto) {
        CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
        commandExecutor.execute(new CustomCompleteDummyTaskCmd(dto, extAxHiTaskInstService, extAxProcessLogService));
    }

    @Override
    public String findTaskIdByInstanceIdAndPersonId(String processInstanceId, String personId) {
        TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(processInstanceId);
        if (StringUtils.hasText(personId)) {
            taskQuery.taskAssigneeLike("%|" + personId);
        }
        List<Task> list = taskQuery
                .active()
                .list();
        if (CollectionUtils.isEmpty(list) || list.size() > 1) {
            throw new WorkflowEngineException(FIND_TASK_BY_PERSON_ID_ERROR, processInstanceId, personId);
        }
        return list.get(0).getId();
    }

    @Override
    public Map<String, String> findTaskIdByInstanceIdsAndPersonId(List<String> processInstanceIds, String personId) {
        List<Task> tasks = taskService.createTaskQuery().processInstanceIdIn(processInstanceIds)
                .taskAssigneeLike("%|" + personId)
                .active()
                .list();
        if (CollectionUtils.isEmpty(tasks)) {
            return new HashMap<>();
        }
        return tasks.stream().collect(Collectors.toMap(TaskInfo::getProcessInstanceId, TaskInfo::getId));
    }
}
