package cn.axzo.workflow.es.service.aggregation;

import cn.axzo.basics.common.BeanMapper;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.common.model.request.es.TaskSearchReqDTO;
import cn.axzo.workflow.common.model.response.es.ProcessTaskDocumentVO;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst;
import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog;
import cn.axzo.workflow.core.service.BpmnProcessInstanceForEsService;
import cn.axzo.workflow.core.service.BpmnProcessTaskForEsService;
import cn.axzo.workflow.core.service.converter.BpmnHistoricAttachmentConverter;
import cn.axzo.workflow.es.model.ProcessTaskDocument;
import cn.axzo.workflow.es.service.EsProcessTaskService;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.persistence.entity.CommentEntityImpl;
import org.flowable.engine.task.Comment;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE;
import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC;
import static cn.axzo.workflow.common.constant.BpmnConstants.ES_FIXED_ROUTING;
import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_130;
import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_142;
import static cn.axzo.workflow.common.constant.BpmnConstants.HIDDEN_ASSIGNEE_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_INFO;
import static cn.axzo.workflow.common.constant.BpmnConstants.OLD_TASK_ASSIGNEE_SKIP_FLAT;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_CARBON_COPY;
import static cn.axzo.workflow.core.common.enums.BpmnProcessTaskResultEnum.MI_END;
import static cn.axzo.workflow.core.common.enums.BpmnProcessTaskResultEnum.REJECTION_AUTO_COMPLETED;

/**
 * 任务纬度的聚合操作
 *
 * @author wangli
 * @since 2024-09-30 11:46
 */
@Component
@AllArgsConstructor
@Slf4j
public class AggregateProcessTaskService {
    private final static DefaultArtifactVersion SUPPORT_VERSION = new DefaultArtifactVersion(FLOW_SERVER_VERSION_142);
    private final EsProcessTaskService esProcessTaskService;
    private final BpmnProcessInstanceForEsService bpmnProcessInstanceForEsService;
    private final BpmnProcessTaskForEsService bpmnProcessTaskForEsService;
    private final BpmnHistoricAttachmentConverter attachmentConverter;

    /**
     * 通过新的日志表进行任务纬度的数据同步至 ES
     */
    public List<ProcessTaskDocument> syncProcessTaskForNew(HistoricProcessInstance hpi, String instanceVersion) {
        DefaultArtifactVersion currentVersion = new DefaultArtifactVersion(instanceVersion);
        if (currentVersion.compareTo(SUPPORT_VERSION) < 0) {
            return Collections.emptyList();
        }

        List<ExtAxProcessLog> logs = bpmnProcessTaskForEsService.queryProcessLogByProcessInstanceId(hpi.getId());
//        Map<String, List<Attachment>> attachmentMap = bpmnProcessTaskForEsService.queryAttachmentByProcessInstanceId(hpi.getId())
//                .stream().collect(Collectors.groupingBy(Attachment::getTaskId, Collectors.mapping(Function.identity(), Collectors.toList())));

        List<ProcessTaskDocument> toEsProcessTaskDocuments = new ArrayList<>();
        logs.stream()
                .filter(i -> !Objects.equals(NODE_CARBON_COPY.getType(), i.getNodeType()))
                .forEach(log -> {
                    ProcessTaskDocument processTaskDocument = new ProcessTaskDocument();
                    processTaskDocument.setTaskId(log.getTaskId());
                    processTaskDocument.setProcessInstanceId(hpi.getId());
                    processTaskDocument.setTaskDefinitionKey(log.getActivityId());
                    processTaskDocument.setTaskName(log.getActivityName());
                    processTaskDocument.setTaskStatus(log.getStatus());
                    processTaskDocument.setAdvice(log.getAdvice());
                    processTaskDocument.setOperationDesc(log.getOperationDesc());
                    processTaskDocument.setTaskStartTime(log.getStartTime());
                    processTaskDocument.setTaskEndTime(log.getEndTime());
                    if (Objects.nonNull(log.getEndTime())) {
                        processTaskDocument.setDuration(DateUtil.betweenMs(log.getEndTime(), log.getStartTime()));
                    }
                    processTaskDocument.setTaskTenantId(log.getTenantId());
                    BpmnTaskDelegateAssigner assigner = CollectionUtils.isEmpty(log.getAssigneeFull()) ? null : log.getAssigneeFull().get(0);
                    if (Objects.nonNull(assigner)) {
                        processTaskDocument.setAssigneeName(assigner.getAssignerName());
                        processTaskDocument.setAssigneeOuId(assigner.getOuId());
                        processTaskDocument.setAssigneePersonId(assigner.getPersonId());
                        processTaskDocument.setAssigneeTenantId(assigner.getTenantId());
                    }
//            esProcessTask.setAttachments(attachmentConverter.toVos(attachmentMap.getOrDefault(log.getTaskId(), Collections.emptyList())));
                    processTaskDocument.setApprovalMethod(log.getApprovalMethod());
                    processTaskDocument.setNodeType(log.getNodeType());
                    processTaskDocument.setNodeMode(log.getNodeMode());
                    toEsProcessTaskDocuments.add(processTaskDocument);
                });

        esProcessTaskService.insertBatch(ES_FIXED_ROUTING, hpi.getId(), toEsProcessTaskDocuments);
        return toEsProcessTaskDocuments;
    }

    /**
     * 优先使用三个参数的同步方法.
     * <p>
     * 通过原本引擎表进行任务纬度的数据同步至 ES
     */
    public List<ProcessTaskDocument> syncProcessTaskForOld(HistoricProcessInstance hpi, String instanceVersion) {
        BpmnModel bpmnModel = bpmnProcessInstanceForEsService.queryBpmnModel(hpi.getProcessDefinitionId());
        return syncProcessTaskForOld(hpi, instanceVersion, bpmnModel, null);
    }

    /**
     * 通过原本引擎表进行任务纬度的数据同步至 ES
     *
     * @param hpi
     * @param instanceVersion
     * @param bpmnModel
     * @param function
     */
    public List<ProcessTaskDocument> syncProcessTaskForOld(HistoricProcessInstance hpi, String instanceVersion,
                                                           BpmnModel bpmnModel,
                                                           Function<List<BpmnTaskDelegateAssigner>, Map<Long, BpmnTaskDelegateAssigner>> function) {

        List<HistoricTaskInstance> tasks = bpmnProcessTaskForEsService.queryHistoricProcessTaskByProcessInstanceId(hpi.getId());

        Map<String, ExtAxHiTaskInst> extTaskMap = bpmnProcessTaskForEsService.queryExtAxHiTaskInstByProcessInstanceId(hpi.getId())
                .stream().collect(Collectors.toMap(ExtAxHiTaskInst::getTaskId, Function.identity(), (s, t) -> s));

        Map<String, List<Comment>> commentMap = bpmnProcessTaskForEsService.queryCommentByProcessInstanceId(hpi.getId())
                .stream().collect(Collectors.groupingBy(Comment::getTaskId, Collectors.mapping(Function.identity(), Collectors.toList())));

        // 父子文档中子文档不支持 Nested 类型字段
//        Map<String, List<Attachment>> attachmentMap = bpmnProcessTaskForEsService.queryAttachmentByProcessInstanceId(hpi.getId())
//                .stream().collect(Collectors.groupingBy(Attachment::getTaskId, Collectors.mapping(Function.identity(), Collectors.toList())));

        // 过滤出有效任务, 比如同节点的多实例下或签, 一个人操作了同节点的一个任务,会把同节点的其他任务处理了,此时被处理的任务认为非人工处理
        List<HistoricTaskInstance> filteredEffectiveTasks = filterEffectiveTasks(tasks, instanceVersion);

        List<String> taskIds = filteredEffectiveTasks.stream().map(i -> INTERNAL_TASK_RELATION_ASSIGNEE_INFO + i.getId()).distinct().collect(Collectors.toList());
        Map<String, Object> taskAssigneeMap = bpmnProcessInstanceForEsService.queryInstanceVariables(hpi.getId(), taskIds);

        List<ProcessTaskDocument> toEsProcessTaskDocuments = new ArrayList<>();
        for (HistoricTaskInstance task : filteredEffectiveTasks) {
            ExtAxHiTaskInst extTask = extTaskMap.getOrDefault(task.getId(), new ExtAxHiTaskInst());
            List<Comment> comments = commentMap.getOrDefault(task.getId(), Collections.emptyList());
//            List<Attachment> attachments = attachmentMap.getOrDefault(task.getId(), Collections.emptyList());

            ProcessTaskDocument processTaskDocument = new ProcessTaskDocument();
            processTaskDocument.setTaskId(task.getId());
            processTaskDocument.setProcessInstanceId(hpi.getId());
            processTaskDocument.setTaskDefinitionKey(task.getTaskDefinitionKey());
            processTaskDocument.setTaskName(task.getName());
            processTaskDocument.setTaskStatus(extTask.getStatus());
            // 处理 advice
            processTaskDocument.setAdvice(comments.stream().filter(i -> Objects.equals(i.getType(), COMMENT_TYPE_ADVICE))
                    .findFirst().orElse(new CommentEntityImpl()).getFullMessage());
            // 处理 operationDesc
            processTaskDocument.setOperationDesc(comments.stream().filter(i -> Objects.equals(COMMENT_TYPE_OPERATION_DESC, i.getType()))
                    .max(Comparator.comparing(Comment::getTime))
                    .orElse(new CommentEntityImpl()).getFullMessage());
            processTaskDocument.setTaskStartTime(task.getCreateTime());
            processTaskDocument.setTaskEndTime(task.getEndTime());
            processTaskDocument.setDuration(task.getDurationInMillis());
            processTaskDocument.setTaskTenantId(task.getTenantId());
            BpmnTaskDelegateAssigner assigner = BpmnTaskDelegateAssigner.toObjectCompatible(taskAssigneeMap.getOrDefault(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + task.getId(), null));
            if (Objects.nonNull(assigner)) {
                BpmnTaskDelegateAssigner fullAssigner = null;
                if (Objects.nonNull(function) && !StringUtils.hasText(assigner.getAssignerName()) && NumberUtil.isNumber(assigner.getPersonId())) {
                    fullAssigner = function.apply(Lists.newArrayList(assigner)).getOrDefault(Long.parseLong(assigner.getPersonId()), null);
                }
                processTaskDocument.setAssigneeName(Objects.nonNull(fullAssigner) ? fullAssigner.getAssignerName() : StringUtils.hasText(assigner.getAssignerName()) ? assigner.getAssignerName() : "未知");
                processTaskDocument.setAssigneeOuId(assigner.getOuId());
                processTaskDocument.setAssigneePersonId(assigner.getPersonId());
                processTaskDocument.setAssigneeTenantId(assigner.getTenantId());
            }
            if (Objects.nonNull(bpmnModel)) {
                FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey());
                BpmnMetaParserHelper.getApprovalMethod(flowElement)
                        .ifPresent(e -> processTaskDocument.setApprovalMethod(e.getType()));
                BpmnMetaParserHelper.getNodeType(flowElement)
                        .ifPresent(e -> processTaskDocument.setNodeType(e.getType()));
            }
            if (!Objects.equals(processTaskDocument.getNodeType(), NODE_CARBON_COPY.getType())) {
                toEsProcessTaskDocuments.add(processTaskDocument);
            }
        }
        esProcessTaskService.insertBatch(ES_FIXED_ROUTING, hpi.getId(), toEsProcessTaskDocuments);
        return toEsProcessTaskDocuments;
    }

    private List<HistoricTaskInstance> filterEffectiveTasks(List<HistoricTaskInstance> tasks, String instanceVersion) {
        List<HistoricTaskInstance> effectiveTasks = new ArrayList<>();
        if (CollectionUtils.isEmpty(tasks)) {
            return effectiveTasks;
        }
        Stream<HistoricTaskInstance> taskInstanceStream = tasks.stream()
                .filter(i -> !Objects.equals(i.getAssignee(), HIDDEN_ASSIGNEE_ID))
                .filter(i -> !Objects.equals(i.getDeleteReason(), HIDDEN_ASSIGNEE_ID))
                .filter(i -> !Objects.equals(i.getDeleteReason(), MI_END.getStatus()));
        if (Objects.isNull(instanceVersion)) {
            compatibleVersion(taskInstanceStream).forEach(effectiveTasks::add);
        } else {
            if (StringUtils.hasText(instanceVersion)) {
                DefaultArtifactVersion version = new DefaultArtifactVersion(instanceVersion);
                DefaultArtifactVersion supportVersion = new DefaultArtifactVersion(FLOW_SERVER_VERSION_130);
                if (version.compareTo(supportVersion) < 0) {
                    compatibleVersion(taskInstanceStream).forEach(effectiveTasks::add);
                } else {
                    taskInstanceStream.forEach(effectiveTasks::add);
                }
            } else {
                taskInstanceStream.forEach(effectiveTasks::add);
            }
        }
        return effectiveTasks;
    }


    /**
     * 兼容 1.2.1 版本的审批日志
     *
     * @param stream
     * @return
     */
    private Stream<HistoricTaskInstance> compatibleVersion(Stream<HistoricTaskInstance> stream) {
        return stream.filter(i -> (!Objects.equals(REJECTION_AUTO_COMPLETED.getDesc(), i.getDeleteReason()))
                || (Objects.equals(i.getAssignee(), OLD_TASK_ASSIGNEE_SKIP_FLAT) && Objects.equals(REJECTION_AUTO_COMPLETED.getDesc(), i.getDeleteReason()))
        ).filter(i -> !(!Objects.equals(i.getAssignee(), OLD_TASK_ASSIGNEE_SKIP_FLAT) && Objects.equals(MI_END.getStatus(), i.getDeleteReason())));
    }

    public List<ProcessTaskDocumentVO> search(TaskSearchReqDTO dto) {
        return BeanMapper.copyList(esProcessTaskService.search(dto), ProcessTaskDocumentVO.class);
    }

    public Integer deleteByParentId(String processInstanceId) {
        return esProcessTaskService.delete(ES_FIXED_ROUTING, processInstanceId);
    }
}
