package cn.axzo.workflow.core.engine.cmd.helper;

import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.repository.entity.ExtAxHiTaskInst;
import cn.axzo.workflow.core.service.ExtAxHiTaskInstService;
import org.flowable.common.engine.impl.cfg.IdGenerator;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.persistence.entity.AttachmentEntity;
import org.flowable.engine.impl.persistence.entity.CommentEntity;
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.task.Attachment;
import org.flowable.engine.task.Comment;
import org.flowable.engine.task.Event;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.service.impl.persistence.entity.HistoricTaskInstanceEntity;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.flowable.task.service.impl.persistence.entity.TaskEntityImpl;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
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.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.TASK_COMPLETE_OPERATION_TYPE;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING;
import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.ASSIGNEE_HAS_BEEN_EXISTS;
import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF;
import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.TASK_COMPLETE_FAIL_NOT_EXISTS;
import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.TASK_HAS_BEEN_COMPLETE;
import static org.flowable.task.api.Task.DEFAULT_PRIORITY;

/**
 * 用于转交/加签命令中的辅助类
 *
 * @author wangli
 * @since 2023/12/22 10:59
 */
public class CustomTaskHelper {

    public static void addMultiTask(CommandContext commandContext, TaskEntity originTask,
                                    BpmnTaskDelegateAssigner newTaskAssignee) {
        if (Objects.isNull(originTask)) {
            return;
        }
        ProcessEngineConfigurationImpl processEngineConfiguration =
                CommandContextUtil.getProcessEngineConfiguration(commandContext);
        Map<String, Object> executionVariables = new HashMap<>();
        executionVariables.put("assigneeName", newTaskAssignee.buildAssigneeId());

        RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();
        Execution subExecution = runtimeService.addMultiInstanceExecution(originTask.getTaskDefinitionKey(),
                originTask.getProcessInstanceId(), executionVariables);

        setParentTaskId(originTask, subExecution);
    }

    public static void deleteMultiTask(CommandContext commandContext, TaskEntity originTask) {
        if (Objects.isNull(originTask)) {
            return;
        }
        ProcessEngineConfigurationImpl processEngineConfiguration =
                CommandContextUtil.getProcessEngineConfiguration(commandContext);
        RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();
        runtimeService.deleteMultiInstanceExecution(originTask.getExecutionId(), false);
    }

    private static void setParentTaskId(TaskEntity originTask, Execution subExecution) {
        CommandContextUtil.getEntityCache().findInCache(TaskEntity.class).stream().filter(i -> Objects.equals(i.getExecutionId(), subExecution.getId())).findAny().ifPresent(i -> i.setParentTaskId(originTask.getId()));

        CommandContextUtil.getEntityCache().findInCache(HistoricTaskInstanceEntity.class).stream().filter(i -> Objects.equals(i.getExecutionId(), subExecution.getId())).findAny().ifPresent(i -> i.setParentTaskId(originTask.getId()));
    }

    /**
     * 校验任务归属信息
     *
     * @param historicTaskInstance 历史表当前 id 任务
     * @param taskEntity           在途表当前 id 任务
     * @param originTaskAssigner
     */
    public static void validTask(HistoricTaskInstance historicTaskInstance, TaskEntity taskEntity,
                                 BpmnTaskDelegateAssigner originTaskAssigner) {
        if (Objects.nonNull(historicTaskInstance) &&
                (Objects.nonNull(historicTaskInstance.getEndTime()) || Objects.isNull(taskEntity))) {
            throw new WorkflowEngineException(TASK_HAS_BEEN_COMPLETE);
        }

        if (Objects.isNull(historicTaskInstance) && Objects.isNull(taskEntity)) {
            throw new WorkflowEngineException(TASK_COMPLETE_FAIL_NOT_EXISTS);
        }
        // FIXME by wangli: currentAssignee 去掉 OUID 前的代码
        //        if (Objects.nonNull(originTaskAssigner) &&
        //                !Objects.equals(taskEntity.getAssignee(), originTaskAssigner.buildAssigneeId())) {
        //            throw new WorkflowEngineException(TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF);
        //        }

        // FIXME by wangli: 在 2183 需求上线前, 因为团队 ID 和班组 ID 的问题,再次调整任务审批人的比对逻辑
        //        if (Objects.nonNull(originTaskAssigner) && StringUtils.hasLength(taskEntity.getAssignee())
        //                && (!taskEntity.getAssignee().contains(originTaskAssigner.buildAssigneeId())
        //                && !taskEntity.getAssignee().contains(originTaskAssigner.buildAssigneeId_1_2_1()))) {
        //            throw new WorkflowEngineException(TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF);
        //        }
        if (Objects.nonNull(originTaskAssigner) && StringUtils.hasLength(taskEntity.getAssignee())) {
            String[] split = taskEntity.getAssignee().split("\\|");
            if (!Objects.equals(split[1], originTaskAssigner.getPersonId())) {
                throw new WorkflowEngineException(TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF);
            }
        }
    }

    /**
     * 校验被转交/加签的人是否存在重复
     *
     * @param commandContext
     * @param taskEntity
     * @param targetAssigneeList
     * @return 返回数据库中现在审批人列表
     */
    public static List<BpmnTaskDelegateAssigner> validTaskAssignerDuplicated(CommandContext commandContext,
                                                                             TaskEntity taskEntity,
                                                                             List<BpmnTaskDelegateAssigner> targetAssigneeList) {
        ProcessEngineConfigurationImpl processEngineConfiguration =
                CommandContextUtil.getProcessEngineConfiguration(commandContext);
        RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();

        // 这个节点下所有审批人快照
        String activityListSnapshot =
                INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + taskEntity.getTaskDefinitionKey();
        List<BpmnTaskDelegateAssigner> taskAssignerListSnapshot =
                runtimeService.getVariable(taskEntity.getProcessInstanceId(), activityListSnapshot, List.class);
        AtomicInteger existsCount = new AtomicInteger();
        taskAssignerListSnapshot.forEach(i -> {
            targetAssigneeList.forEach(j -> {
                if (Objects.equals(i.buildAssigneeId(), j.buildAssigneeId())) {
                    existsCount.getAndIncrement();
                }
            });
        });

        if (existsCount.intValue() > 0) {
            throw new WorkflowEngineException(ASSIGNEE_HAS_BEEN_EXISTS);
        }
        return taskAssignerListSnapshot;
    }

    /**
     * 保存附件
     *
     * @param commandContext
     * @param processInstanceId
     * @param taskId
     * @param attachmentList
     */
    public static void batchAddAttachment(CommandContext commandContext, String processInstanceId, String taskId,
                                          List<AttachmentDTO> attachmentList, BpmnTaskDelegateAssigner assigner) {
        if (CollectionUtils.isEmpty(attachmentList)) {
            return;
        }
        ProcessEngineConfigurationImpl processEngineConfiguration =
                CommandContextUtil.getProcessEngineConfiguration(commandContext);
        TaskService taskService = processEngineConfiguration.getTaskService();
        Authentication.setAuthenticatedUserId(assigner.buildAssigneeId());
        attachmentList.forEach(dto -> {
            Attachment attachment = taskService.createAttachment(dto.getType().getType(), taskId, processInstanceId,
                    dto.getName(), dto.getDescription(), dto.getUrl());
            taskService.saveAttachment(attachment);
        });
        Authentication.setAuthenticatedUserId(null);
    }

    public static Comment addComment(CommandContext commandContext, String taskId, String processInstanceId,
                                     String type, String message) {
        TaskEntity task = new TaskEntityImpl();
        task.setId(taskId);
        task.setProcessInstanceId(processInstanceId);
        return addComment(commandContext, task, type, message);
    }

    public static Comment addComment(CommandContext commandContext, Task task, String type, String message) {
        ProcessEngineConfigurationImpl processEngineConfiguration =
                CommandContextUtil.getProcessEngineConfiguration(commandContext);

        String userId = Authentication.getAuthenticatedUserId();
        CommentEntity comment = processEngineConfiguration.getCommentEntityManager().create();
        comment.setUserId(userId);
        comment.setType((type == null) ? CommentEntity.TYPE_COMMENT : type);
        comment.setTime(processEngineConfiguration.getClock().getCurrentTime());
        comment.setTaskId(task.getId());
        comment.setProcessInstanceId(task.getProcessInstanceId());
        comment.setAction(Event.ACTION_ADD_COMMENT);

        String eventMessage = message.replaceAll("\\s+", " ");
        if (eventMessage.length() > 163) {
            eventMessage = eventMessage.substring(0, 160) + "...";
        }
        comment.setMessage(eventMessage);

        comment.setFullMessage(message);

        processEngineConfiguration.getCommentEntityManager().insert(comment);

        return comment;
    }

    public static Attachment addAttachment(CommandContext commandContext, Task task, AttachmentDTO attachmentDto) {
        ProcessEngineConfigurationImpl processEngineConfiguration =
                CommandContextUtil.getProcessEngineConfiguration(commandContext);
        AttachmentEntity attachment = processEngineConfiguration.getAttachmentEntityManager().create();
        attachment.setName(attachmentDto.getName());
        attachment.setProcessInstanceId(task.getProcessInstanceId());
        attachment.setTaskId(task.getId());
        attachment.setDescription(attachmentDto.getDescription());
        attachment.setType(attachmentDto.getType().getType());
        attachment.setUrl(attachmentDto.getUrl());
        attachment.setUserId(Authentication.getAuthenticatedUserId());
        attachment.setTime(processEngineConfiguration.getClock().getCurrentTime());

        processEngineConfiguration.getAttachmentEntityManager().insert(attachment, false);

        ExecutionEntity processInstance = null;
        if (task.getProcessInstanceId() != null) {
            processInstance =
                    processEngineConfiguration.getExecutionEntityManager().findById(task.getProcessInstanceId());
        }

        processEngineConfiguration.getHistoryManager().createAttachmentComment((TaskEntity) task, processInstance,
                attachmentDto.getName(), true);

        return attachment;
    }

    /**
     * 手动创建一个非正常的任务节点, 可自定义节点名称, 同时设置任务的审批人, 以及审批意见
     *
     * @param commandContext         用于在 Command 中执行的必要上下文
     * @param extAxHiTaskInstService 操作扩展的任务实例的 Service
     * @param processInstanceId      流程实例
     * @param nodeName               节点自定义显示名称
     * @param taskDefinitionKey      节点自定标识
     * @param advice                 审批意见
     * @param assigner               审批人
     * @param extTaskInstStatus      节点状态
     * @return
     */
    public static Task createVirtualTask(CommandContext commandContext, ExtAxHiTaskInstService extAxHiTaskInstService
            , String processInstanceId, String nodeName, String taskDefinitionKey, String advice,
                                         BpmnTaskDelegateAssigner assigner,
                                         String extTaskInstStatus) {
        ProcessEngineConfigurationImpl processEngineConfiguration =
                CommandContextUtil.getProcessEngineConfiguration(commandContext);
        HistoryService historyService = processEngineConfiguration.getHistoryService();

        HistoricProcessInstance processInstance =
                historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();

        TaskService taskService = processEngineConfiguration.getTaskService();
        IdGenerator idGenerator = processEngineConfiguration.getIdGenerator();
        TaskEntity task = (TaskEntity) taskService.newTask(idGenerator.getNextId());
        task.setCategory(processInstance.getProcessDefinitionKey().split(":")[0]);
        task.setDescription(processInstance.getDescription());
        task.setTenantId(processInstance.getTenantId());
        task.setName(nodeName);
        task.setProcessDefinitionId(processInstance.getProcessDefinitionId());
        task.setProcessInstanceId(processInstance.getId());
        task.setTaskDefinitionKey(taskDefinitionKey);
        task.setPriority(DEFAULT_PRIORITY);
        task.setCreateTime(new Date());
        // 创建临时节点
        taskService.saveTask(task);

        CommandContextUtil.getEntityCache().findInCache(TaskEntity.class).stream()
                .filter(i -> Objects.equals(i.getId(), task.getId())).findAny()
                .ifPresent(i -> i.setAssignee(assigner.buildAssigneeId()));

        CommandContextUtil.getEntityCache().findInCache(HistoricTaskInstanceEntity.class).stream()
                .filter(i -> Objects.equals(i.getId(), task.getId())).findAny()
                .ifPresent(i -> i.setAssignee(assigner.buildAssigneeId()));

        // 添加审批意见
        addAdvice(commandContext, task, advice, assigner.buildAssigneeId());

        CustomTaskHelper.createExtTaskInst(extAxHiTaskInstService, task.getProcessInstanceId(), task.getId(),
                task.getTaskDefinitionKey(), assigner, extTaskInstStatus);
        task.setTransientVariable(TASK_COMPLETE_OPERATION_TYPE + task.getId(), extTaskInstStatus);

        // 设置快照信息
        task.setVariable(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + task.getId(), assigner.toJson());

        // 完成临时节点
        taskService.complete(task.getId());

        return task;
    }

    private static void addAdvice(CommandContext commandContext, Task task, String comment, String userId) {
        if (StringUtils.hasLength(comment)) {
            Authentication.setAuthenticatedUserId(userId);
            addComment(commandContext, task, COMMENT_TYPE_ADVICE, comment);
            Authentication.setAuthenticatedUserId(null);
        }
    }

    public static void createExtTaskInst(ExtAxHiTaskInstService extAxHiTaskInstService, String processInstanceId,
                                         String taskId, String taskDefinitionKey, BpmnTaskDelegateAssigner assignee,
                                         String status) {
        ExtAxHiTaskInst old = extAxHiTaskInstService.getByTaskId(taskId, processInstanceId);
        if (Objects.nonNull(old)) {
            old.setStatus(StringUtils.hasLength(status) ? status : PROCESSING.getStatus());
            extAxHiTaskInstService.update(old);
        } else {
            ExtAxHiTaskInst entity = new ExtAxHiTaskInst();
            entity.setProcInstId(processInstanceId);
            entity.setTaskDefinitionKey(taskDefinitionKey);
            entity.setTaskId(taskId);
            entity.setStatus(StringUtils.hasLength(status) ? status : PROCESSING.getStatus());
            entity.setAssignee(assignee.buildAssigneeId());
            extAxHiTaskInstService.save(entity);
        }
    }

    public static List<BpmnTaskDelegateAssigner> removeDuplicateByPersonId(List<BpmnTaskDelegateAssigner> delegateAssigners) {
        if (CollectionUtils.isEmpty(delegateAssigners)) {
            return delegateAssigners;
        }
        return new ArrayList<>(delegateAssigners.stream()
                .filter(i -> StringUtils.hasText(i.getPersonId()))
                .filter(i -> !Objects.equals(i.getPersonId(), "null"))
                .collect(Collectors.toMap(BpmnTaskDelegateAssigner::getPersonId, Function.identity(), (s, t) -> s))
                .values());
    }

}
