package cn.axzo.workflow.server.controller.listener.task;

import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.common.context.TaskOperationContext;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.engine.event.BizSpecifyAssigneeEventImpl;
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.listener.AbstractBpmnEventListener;
import cn.axzo.workflow.core.listener.BpmnTaskEventListener;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher;
import org.flowable.engine.RuntimeService;
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.task.service.delegate.DelegateTask;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT;
import static cn.axzo.workflow.common.constant.BpmnConstants.NO_ASSIGNEE;
import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_ASSIGNEE_SKIP_FLAT;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER;
import static cn.axzo.workflow.core.engine.event.BizSpecifyAssigneeEventType.ADD_ASSIGNEE;

/**
 * UserTask 节点被设置审批人时,推送待办消息
 * <p>
 * 注意: task 的发送消息的模式与 processInstance 是不一样的, 实例的触发是在 Core 包中的 EngineProcessInstanceEventListener 处理的
 *
 * @author wangli
 * @since 2023/11/30 20:50
 */
@Slf4j
@Component
@AllArgsConstructor
public class MessagePushTaskEvent_103_Listener extends AbstractBpmnEventListener<TaskOperationContext> implements BpmnTaskEventListener, Ordered {
    @Override
    public int getOrder() {
        return Integer.MIN_VALUE + 103;
    }

    private final RuntimeService runtimeService;

    @Override
    public void onAssigned(DelegateTask delegateTask) {
        log.info("MessagePushTaskEventListener#onAssigned...{}, assignee: {}, taskId: {}",
                    delegateTask.getTaskDefinitionKey(), delegateTask.getAssignee(), delegateTask.getId());
        if (Objects.equals(NODE_STARTER.getType(), delegateTask.getTaskDefinitionKey())) {
            return;
        }
        if (StringUtils.hasLength(delegateTask.getAssignee()) && delegateTask.getAssignee().contains(TASK_ASSIGNEE_SKIP_FLAT)) {
            // 转交功能原审批人完成待办, 由于在流程引擎侧, 任务是不会在转交时立即结束, 但待办消息需要立即完成,
            // 下面的 onDelete 事件根据测试情况,看是否需要过滤掉这种任务的"完成待办"事件的推送
            pendingComplete(delegateTask);
            return;
        }
        if (Objects.equals(NO_ASSIGNEE, delegateTask.getAssignee())) {
            // 业务指定审批人, 不用发待办
            // 但需要触发 PROCESS_ACTIVITY_WAIT_ASSIGNEE 事件
            ProcessInstance processInstance = getContext().getProcessInstance(() -> runtimeService.createProcessInstanceQuery()
                    .includeProcessVariables()
                    .processInstanceId(delegateTask.getProcessInstanceId())
                    .singleResult());
            ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration();
            FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher();
            eventDispatcher.dispatchEvent(new BizSpecifyAssigneeEventImpl(ADD_ASSIGNEE, delegateTask,
                            processInstance.getBusinessKey(), processInstance.getProcessVariables()),
                    processEngineConfiguration.getEngineCfgKey());
            return;
        }
        pendingPush(delegateTask);
        log.info("MessagePushTaskEventListener#onAssigned...end: {}", delegateTask.getTaskDefinitionKey());
    }

    @Override
    public void onDeleted(DelegateTask delegateTask) {
        log.info("MessagePushTaskEventListener#onDeleted...{}", delegateTask.getTaskDefinitionKey());
        if (Objects.equals(NODE_STARTER.getType(), delegateTask.getTaskDefinitionKey())) {
            return;
        }
        pendingComplete(delegateTask);
        log.info("MessagePushTaskEventListener#onDeleted...end: {}", delegateTask.getTaskDefinitionKey());
    }

    private void pendingComplete(DelegateTask delegateTask) {
        ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration();
        FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher();
        Process process = ProcessDefinitionUtil.getProcess(delegateTask.getProcessDefinitionId());

        Optional<BpmnNoticeConf> optNoticeConfig = BpmnMetaParserHelper.getNoticeConfig(process);
        optNoticeConfig.ifPresent(noticeConfig -> {
            // 优先读取节点自身的待办模板配置信息
            UserTask userTask = (UserTask) process.getFlowElement(delegateTask.getTaskDefinitionKey());
            BpmnMetaParserHelper.getNodePendingConfig(userTask).ifPresent(noticeConfig::setPending);

            MessagePushEventImpl event = MessagePushEventBuilder.createEvent(MessagePushEventType.PENDING_COMPLETE,
                    null, noticeConfig, delegateTask.getProcessInstanceId(), null, delegateTask.getId());
            log.info("发送完成待办的消息: {}", JSONUtil.toJsonStr(event));
            eventDispatcher.dispatchEvent(event, processEngineConfiguration.getEngineCfgKey());
        });
    }

    private void pendingPush(DelegateTask delegateTask) {
        ProcessInstance processInstance = getContext().getProcessInstance(() -> runtimeService.createProcessInstanceQuery()
                .processInstanceId(delegateTask.getProcessInstanceId())
                .includeProcessVariables()
                .singleResult());
        List<BpmnTaskDelegateAssigner> assigners =
                (List<BpmnTaskDelegateAssigner>) processInstance.getProcessVariables()
                        .getOrDefault(INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + delegateTask.getTaskDefinitionKey(), Collections.emptyList());
        if (CollectionUtils.isEmpty(assigners)) {
            return;
        }

        ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration();
        FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher();

        Process process = ProcessDefinitionUtil.getProcess(processInstance.getProcessDefinitionId());
        Optional<BpmnNoticeConf> optNoticeConfig = BpmnMetaParserHelper.getNoticeConfig(process);
        optNoticeConfig.ifPresent(noticeConf -> {
            // 优先读取节点自身的待办模板配置信息
            UserTask userTask = (UserTask) process.getFlowElement(delegateTask.getTaskDefinitionKey());
            BpmnMetaParserHelper.getNodePendingConfig(userTask).ifPresent(noticeConf::setPending);

            assigners.stream().filter(i -> Objects.equals(delegateTask.getAssignee(), i.buildAssigneeId())).findAny()
                    .ifPresent(i -> {
                        MessagePushEventImpl event =
                                MessagePushEventBuilder.createEvent(MessagePushEventType.PENDING_PUSH,
                                        Lists.newArrayList(i), noticeConf, processInstance.getProcessInstanceId(),
                                        processInstance.getTenantId(), delegateTask.getId());
                        log.info("发送推送待办的消息: {}", JSONUtil.toJsonStr(event));
                        eventDispatcher.dispatchEvent(event, processEngineConfiguration.getEngineCfgKey());
                    });
        });
    }
}
