package cn.axzo.workflow.core.engine.listener;

import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum;
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.ProcessOperationContext;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
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.BpmnProcessEventListener;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEvent;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.event.AbstractFlowableEngineEventListener;
import org.flowable.engine.delegate.event.FlowableCancelledEvent;
import org.flowable.engine.delegate.event.FlowableProcessStartedEvent;
import org.flowable.engine.delegate.event.impl.FlowableProcessStartedEventImpl;
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.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_DELETE_PROCESS_FLAG;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_TYPE_ABORT;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_TYPE_CANCEL;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_TYPE_REJECT;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.ABORTED;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.CANCELLED;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.DELETED;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.REJECTED;

/**
 * 引擎全局的流程实例事件监听
 */
@Slf4j
@Component
public class EngineProcessInstanceEventListener extends AbstractFlowableEngineEventListener {
    @Resource
    ObjectProvider<List<BpmnProcessEventListener>> processEventListeners;

    @Resource
    @Lazy
    private RuntimeService runtimeService;
    public static final Set<FlowableEngineEventType> PROCESS_INSTANCE_EVENTS =
            ImmutableSet.<FlowableEngineEventType>builder()
                    .add(FlowableEngineEventType.PROCESS_CREATED)
                    .add(FlowableEngineEventType.PROCESS_STARTED)
                    .add(FlowableEngineEventType.PROCESS_CANCELLED)
                    .add(FlowableEngineEventType.PROCESS_COMPLETED)
                    .build();

    public EngineProcessInstanceEventListener() {
        super(PROCESS_INSTANCE_EVENTS);
    }

    @Override
    protected void processCreated(FlowableEngineEntityEvent event) {
        getOrderedListeners().forEach(i -> i.onCreated(event));
    }


    @Override
    protected void processStarted(FlowableProcessStartedEvent event) {
        runtimeService.updateBusinessStatus(((FlowableProcessStartedEventImpl) event).getProcessInstanceId(),
                BpmnProcessInstanceResultEnum.PROCESSING.getStatus());
        getOrderedListeners().forEach(i -> i.onStarted(event));
    }

    @Override
    protected void processCompleted(FlowableEngineEntityEvent event) {
        runtimeService.updateBusinessStatus(event.getProcessInstanceId(),
                BpmnProcessInstanceResultEnum.APPROVED.getStatus());
        getOrderedListeners().forEach(i -> i.onCompleted(event));
        // 推送站内信
        pushNotice(event);
    }

    @Override
    protected void processCancelled(FlowableCancelledEvent event) {
        String deleteProcessType = String.valueOf(runtimeService.getVariable(event.getExecutionId(),
                INTERNAL_DELETE_PROCESS_FLAG));
        BpmnProcessInstanceResultEnum resultEnum;
        switch (deleteProcessType) {
            case INTERNAL_PROCESS_TYPE_CANCEL:
                resultEnum = CANCELLED;
                break;
            case INTERNAL_PROCESS_TYPE_REJECT:
                resultEnum = REJECTED;
                break;
            case INTERNAL_PROCESS_TYPE_ABORT:
                resultEnum = ABORTED;
                break;
            default:
                if (Objects.equals("deleted deployment", event.getCause())) {
                    // 级联参数 deployment 会执行到这里
                    resultEnum = DELETED;
                    break;
                }
                log.error("这里是不允许执行到的地方,为防止引擎一些未发现的地方,在这里输出错误日志! this current thread name: {}",
                        Thread.currentThread().getName());
                throw new IllegalArgumentException("这里是不允许执行到的地方, 请关注程序逻辑");
        }
        runtimeService.updateBusinessStatus(event.getProcessInstanceId(), resultEnum.getStatus());
        getOrderedListeners().forEach(i -> {
            if (Objects.equals(resultEnum, CANCELLED)) {
                i.onCancelled(event);
            } else if (Objects.equals(resultEnum, REJECTED)) {
                i.onRejected(event);
            } else {
                i.onAborted(event);
            }
        });
        // 推送站内信
        pushNotice(event);
    }

    private void pushNotice(FlowableEngineEvent event) {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                .processInstanceId(event.getProcessInstanceId())
                .includeProcessVariables().singleResult();
        BpmnTaskDelegateAssigner assigner =
                BpmnTaskDelegateAssigner.toObjectCompatible(processInstance.getProcessVariables().get(INTERNAL_INITIATOR));
        if (Objects.isNull(assigner)) {
            return;
        }
        ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration();
        FlowableEventDispatcher eventDispatcher = processEngineConfiguration.getEventDispatcher();
        Optional<BpmnNoticeConf> noticeConfig =
                BpmnMetaParserHelper.getNoticeConfig(ProcessDefinitionUtil.getProcess(processInstance.getProcessDefinitionId()));
        MessagePushEventImpl messagePushEvent = MessagePushEventBuilder.createEvent(MessagePushEventType.NOTICE,
                Lists.newArrayList(assigner), noticeConfig.orElse(null),
                processInstance.getProcessInstanceId(), processInstance.getTenantId(), null);
        log.info("发送站内信: {}", JSONUtil.toJsonStr(messagePushEvent));
        eventDispatcher.dispatchEvent(messagePushEvent, processEngineConfiguration.getEngineCfgKey());
    }

    private List<BpmnProcessEventListener> getOrderedListeners() {
        ProcessOperationContext context = new ProcessOperationContext();
        List<BpmnProcessEventListener> orderListeners = new ArrayList<>();
        processEventListeners.ifAvailable(orderListeners::addAll);
        orderListeners.forEach(i -> i.setContext(context));
        return orderListeners;
    }

}
