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

import cn.axzo.workflow.common.enums.BpmnCountersignTypeEnum;
import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf;
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.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.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.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.cmd.CustomApproveTaskAsyncCmd;
import cn.axzo.workflow.core.engine.cmd.CustomApproveTaskCmd;
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.CustomTransferUserTaskAsyncCmd;
import cn.axzo.workflow.core.engine.cmd.CustomTransferUserTaskCmd;
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.repository.entity.ExtAxHiTaskInst;
import cn.axzo.workflow.core.service.BpmnProcessTaskService;
import cn.axzo.workflow.core.service.ExtAxHiTaskInstService;
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 com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher;
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.impl.cfg.ProcessEngineConfigurationImpl;
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.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.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.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.Comparator;
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.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.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_CARBON_COPY;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED;
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.code.BpmnInstanceRespCode.PROCESS_INSTANCE_ID_NOT_EXISTS;
import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.FIND_TASK_BY_PERSON_ID_ERROR;
import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.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;

@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;

    @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 (dto.getAsync()) {
            commandExecutor.execute(new CustomApproveTaskAsyncCmd(dto));
        } else {
            commandExecutor.execute(new CustomApproveTaskCmd(dto));
        }
    }

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

    @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());
        for (BpmnHistoricTaskInstanceVO vo : vos) {
            ExtAxHiTaskInst taskInst = extTaskInstMap.getOrDefault(vo.getTaskId(), new ExtAxHiTaskInst());
            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 vos;
    }

    /**
     * 这个方法几乎没有人使用
     *
     * @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()));
        }
    }


    @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
    public void remindTask(BpmnTaskRemindDTO dto) {
        TaskQuery taskQuery = taskService.createTaskQuery().processInstanceId(dto.getProcessInstanceId());
        if (StringUtils.hasLength(dto.getTaskDefinitionKey())) {
            taskQuery.taskDefinitionKey(dto.getTaskDefinitionKey());
        }
        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_ACTIVITY_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),
                            Lists.newArrayList(assigner), noticeConfig.orElse(null),
                            processInstance.getProcessInstanceId(),
                            processInstance.getTenantId(), task.getId());
                    event.setProcessInstanceId(processInstance.getProcessInstanceId());
                    event.setTenantId(processInstance.getTenantId());
                    event.setTaskId(task.getId());
                    eventDispatcher.dispatchEvent(event, processEngineConfiguration.getEngineCfgKey());
                });
            });
        });
    }

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

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

    @Override
    public String findTaskIdByInstanceIdAndPersonId(String processInstanceId, String personId) {
        List<Task> list = taskService.createTaskQuery().processInstanceId(processInstanceId)
                .taskAssigneeLike("%" + personId)
                .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();
    }
}
