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

import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventProducer;
import cn.axzo.workflow.common.enums.ProcessInstanceEventEnum;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.common.model.response.mq.ProcessInstanceDTO;
import cn.axzo.workflow.core.common.context.ProcessOperationContext;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.listener.AbstractBpmnEventListener;
import cn.axzo.workflow.core.listener.BpmnProcessEventListener;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.Process;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEntityEvent;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.event.FlowableCancelledEvent;
import org.flowable.engine.delegate.event.FlowableProcessStartedEvent;
import org.flowable.engine.delegate.event.impl.FlowableProcessCancelledEventImpl;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl;
import org.flowable.engine.repository.Deployment;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Objects;

import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_121;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_PROCESS_DELETE_REASON;
import static cn.axzo.workflow.common.constant.BpmnConstants.WORKFLOW_ENGINE_VERSION;
import static cn.axzo.workflow.common.enums.ProcessInstanceEventEnum.PROCESS_INSTANCE_ABORTED;
import static cn.axzo.workflow.common.enums.ProcessInstanceEventEnum.PROCESS_INSTANCE_CANCELLED;
import static cn.axzo.workflow.common.enums.ProcessInstanceEventEnum.PROCESS_INSTANCE_COMPLETED;
import static cn.axzo.workflow.common.enums.ProcessInstanceEventEnum.PROCESS_INSTANCE_CREATED;
import static cn.axzo.workflow.common.enums.ProcessInstanceEventEnum.PROCESS_INSTANCE_REJECTED;
import static cn.axzo.workflow.common.enums.ProcessInstanceEventEnum.PROCESS_INSTANCE_STARTED;


/**
 * 流程实例的 MQ 发送监听
 *
 * @author wangli
 * @since 2023/7/10 18:22
 */
@Slf4j
@Component
public class RocketMqBpmnProcessEventListener extends AbstractBpmnEventListener<ProcessOperationContext> implements BpmnProcessEventListener, Ordered {
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private EventProducer<?> eventProducer;
    @Resource
    private RepositoryService repositoryService;
    @Value("${sendMq:true}")
    private Boolean sendMQ;

    @Override
    public void onCreated(FlowableEngineEntityEvent event) {
        log.info("MessagePushProcessEventListener#onCreated...{}", event.getProcessInstanceId());
        Deployment deployment = getContext().getDeployment(() -> repositoryService.createDeploymentQuery()
                .deploymentId(((ExecutionEntityImpl) event.getEntity()).getDeploymentId()).singleResult());
        Process mainProcess = getContext().getProcess(() -> repositoryService.getBpmnModel(event.getProcessDefinitionId()).getMainProcess());
        BpmnTaskDelegateAssigner initiator = getContext().getInitiator(() -> BpmnTaskDelegateAssigner.toObjectCompatible(((ExecutionEntityImpl) event.getEntity()).getVariable(INTERNAL_INITIATOR)));
        ProcessInstanceDTO dto = new ProcessInstanceDTO()
                .setType(PROCESS_INSTANCE_CREATED)
                .setCategory(deployment.getCategory())
                .setProcessInstanceId(event.getProcessInstanceId())
                .setCurrentElementKey(((ExecutionEntityImpl) event.getEntity()).getStartActivityId())
                .setProcessDefinitionId(event.getProcessDefinitionId())
                .setProcessDefinitionKey(((ExecutionEntityImpl) event.getEntity()).getProcessDefinitionKey())
                .setProcessDefinitionVersion(((ExecutionEntityImpl) event.getEntity()).getProcessDefinitionVersion())
                .setInitiator(initiator)
                .setVariables(((ExecutionEntityImpl) event.getEntity()).getVariables())
                .setStartTime(((ExecutionEntityImpl) event.getEntity()).getStartTime())
                .setTenantId(((ExecutionEntityImpl) event.getEntity()).getTenantId())
                .setBusinessKey(((ExecutionEntityImpl) event.getEntity()).getBusinessKey())
                .setBusinessProcessInstanceName(((ExecutionEntityImpl) event.getEntity()).getName());
        BpmnMetaParserHelper.getNoticeConfig(mainProcess).ifPresent(dto::setNoticeConf);

        setProcessInstanceVersion(event.getProcessInstanceId(), dto);
        sendMessageQueue(dto, PROCESS_INSTANCE_CREATED);
        log.info("MessagePushProcessEventListener#onCreated...end: {}", event.getProcessInstanceId());
    }


    @Override
    public void onStarted(FlowableProcessStartedEvent event) {
        log.info("MessagePushProcessEventListener#onStarted...{}", ((ExecutionEntityImpl) event.getEntity()).getProcessInstanceId());
        Deployment deployment = getContext().getDeployment(() -> repositoryService.createDeploymentQuery()
                .deploymentId(((ExecutionEntityImpl) event.getEntity()).getDeploymentId())
                .singleResult());

        Process mainProcess = getContext().getProcess(() -> repositoryService.getBpmnModel(((ExecutionEntityImpl) event.getEntity())
                .getProcessDefinitionId()).getMainProcess());
        BpmnTaskDelegateAssigner initiator = getContext().getInitiator(() -> BpmnTaskDelegateAssigner.toObjectCompatible(((ExecutionEntityImpl) event.getEntity())
                .getVariable(INTERNAL_INITIATOR)));

        ProcessInstanceDTO dto = new ProcessInstanceDTO()
                .setType(PROCESS_INSTANCE_STARTED)
                .setCategory(deployment.getCategory())
                .setProcessInstanceId(((ExecutionEntityImpl) event.getEntity()).getProcessInstanceId())
                .setCurrentElementKey(((ExecutionEntityImpl) event.getEntity()).getCurrentActivityId())
                .setProcessDefinitionId(((ExecutionEntityImpl) event.getEntity()).getProcessDefinitionId())
                .setProcessDefinitionKey(((ExecutionEntityImpl) event.getEntity()).getProcessDefinitionKey())
                .setProcessDefinitionVersion(((ExecutionEntityImpl) event.getEntity()).getProcessDefinitionVersion())
                .setInitiator(initiator)
                .setVariables(((ExecutionEntityImpl) event.getEntity()).getVariables())
                .setStartTime(((ExecutionEntityImpl) event.getEntity()).getStartTime())
                .setTenantId(((ExecutionEntityImpl) event.getEntity()).getTenantId())
                .setBusinessKey(((ExecutionEntityImpl) event.getEntity()).getProcessInstance().getBusinessKey())
                .setBusinessProcessInstanceName(((ExecutionEntityImpl) event.getEntity()).getProcessInstance().getName());
        BpmnMetaParserHelper.getNoticeConfig(mainProcess).ifPresent(dto::setNoticeConf);

        setProcessInstanceVersion(((ExecutionEntityImpl) event.getEntity()).getProcessInstanceId(), dto);
        sendMessageQueue(dto, PROCESS_INSTANCE_STARTED);
        log.info("MessagePushProcessEventListener#onStarted...end: {}", ((ExecutionEntityImpl) event.getEntity()).getProcessInstanceId());
    }

    @Override
    public void onCancelled(FlowableCancelledEvent event) {
        log.info("MessagePushProcessEventListener#onCancelled...{}", event.getProcessInstanceId());
        Deployment deployment = getContext().getDeployment(() -> repositoryService.createDeploymentQuery()
                .deploymentId(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getDeploymentId())
                .singleResult());
        Process mainProcess = getContext().getProcess(() -> repositoryService.getBpmnModel(event.getProcessDefinitionId()).getMainProcess());
        BpmnTaskDelegateAssigner initiator = getContext().getInitiator(() -> BpmnTaskDelegateAssigner.toObjectCompatible(((FlowableProcessCancelledEventImpl) event)
                .getExecution().getVariable(INTERNAL_INITIATOR)));

        ProcessInstanceDTO dto = new ProcessInstanceDTO()
                .setType(PROCESS_INSTANCE_CANCELLED)
                .setCategory(deployment.getCategory())
                .setProcessInstanceId(event.getProcessInstanceId())
                .setCurrentElementKey("撤回流程实例无节点信息")
                .setProcessDefinitionId(event.getProcessDefinitionId())
                .setProcessDefinitionKey(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getProcessDefinitionKey())
                .setProcessDefinitionVersion(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getProcessDefinitionVersion())
                .setInitiator(initiator)
                .setVariables(((FlowableProcessCancelledEventImpl) event).getExecution().getVariables())
                .setStartTime(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getStartTime())
                .setTenantId(((FlowableProcessCancelledEventImpl) event).getExecution().getTenantId())
                .setBusinessKey(((FlowableProcessCancelledEventImpl) event).getExecution().getProcessInstanceBusinessKey())
                .setBusinessProcessInstanceName(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getName());
        BpmnMetaParserHelper.getNoticeConfig(mainProcess).ifPresent(dto::setNoticeConf);
        setProcessDeleteReason(event, dto);
        setProcessInstanceVersion(event.getProcessInstanceId(), dto);
        sendMessageQueue(dto, PROCESS_INSTANCE_CANCELLED);
        log.info("MessagePushProcessEventListener#onCancelled...end: {}", event.getProcessInstanceId());
    }

    /**
     * 流程实例被驳回后回调
     *
     * @param event
     */
    @Override
    public void onRejected(FlowableCancelledEvent event) {
        log.info("MessagePushProcessEventListener#onRejected...{}", event.getProcessInstanceId());
        Deployment deployment = getContext().getDeployment(() -> repositoryService.createDeploymentQuery()
                .deploymentId(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getDeploymentId())
                .singleResult());
        Process mainProcess = getContext().getProcess(() -> repositoryService.getBpmnModel(event.getProcessDefinitionId()).getMainProcess());
        BpmnTaskDelegateAssigner initiator = getContext().getInitiator(() -> BpmnTaskDelegateAssigner.toObjectCompatible(((FlowableProcessCancelledEventImpl) event)
                .getExecution().getVariable(INTERNAL_INITIATOR)));

        ProcessInstanceDTO dto = new ProcessInstanceDTO()
                .setType(PROCESS_INSTANCE_REJECTED)
                .setCategory(deployment.getCategory())
                .setProcessInstanceId(event.getProcessInstanceId())
                .setCurrentElementKey("驳回流程实例无节点信息")
                .setProcessDefinitionId(event.getProcessDefinitionId())
                .setProcessDefinitionKey(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getProcessDefinitionKey())
                .setProcessDefinitionVersion(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getProcessDefinitionVersion())
                .setInitiator(initiator)
                .setVariables(((FlowableProcessCancelledEventImpl) event).getExecution().getVariables())
                .setStartTime(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getStartTime())
                .setTenantId(((FlowableProcessCancelledEventImpl) event).getExecution().getTenantId())
                .setBusinessKey(((FlowableProcessCancelledEventImpl) event).getExecution().getProcessInstanceBusinessKey())
                .setBusinessProcessInstanceName(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getName());
        BpmnMetaParserHelper.getNoticeConfig(mainProcess).ifPresent(dto::setNoticeConf);
        setProcessDeleteReason(event, dto);
        setProcessInstanceVersion(event.getProcessInstanceId(), dto);
        sendMessageQueue(dto, PROCESS_INSTANCE_REJECTED);
        log.info("MessagePushProcessEventListener#onRejected...end: {}", event.getProcessInstanceId());
    }

    @Override
    public void onAborted(FlowableCancelledEvent event) {
        log.info("MessagePushProcessEventListener#onAborted...{}", event.getProcessInstanceId());
        Deployment deployment = getContext().getDeployment(() -> repositoryService.createDeploymentQuery()
                .deploymentId(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getDeploymentId())
                .singleResult());
        Process mainProcess = getContext().getProcess(() -> repositoryService.getBpmnModel(event.getProcessDefinitionId()).getMainProcess());
        BpmnTaskDelegateAssigner initiator = getContext().getInitiator(() -> BpmnTaskDelegateAssigner.toObjectCompatible(((FlowableProcessCancelledEventImpl) event)
                .getExecution().getVariable(INTERNAL_INITIATOR)));

        ProcessInstanceDTO dto = new ProcessInstanceDTO()
                .setType(PROCESS_INSTANCE_ABORTED)
                .setCategory(deployment.getCategory())
                .setProcessInstanceId(event.getProcessInstanceId())
                .setCurrentElementKey("中止流程实例无节点信息")
                .setProcessDefinitionId(event.getProcessDefinitionId())
                .setProcessDefinitionKey(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getProcessDefinitionKey())
                .setProcessDefinitionVersion(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getProcessDefinitionVersion())
                .setInitiator(initiator)
                .setVariables(((FlowableProcessCancelledEventImpl) event).getExecution().getVariables())
                .setStartTime(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getStartTime())
                .setTenantId(((FlowableProcessCancelledEventImpl) event).getExecution().getTenantId())
                .setBusinessKey(((FlowableProcessCancelledEventImpl) event).getExecution().getProcessInstanceBusinessKey())
                .setBusinessProcessInstanceName(((ExecutionEntityImpl) ((FlowableProcessCancelledEventImpl) event).getExecution()).getName());
        BpmnMetaParserHelper.getNoticeConfig(mainProcess).ifPresent(dto::setNoticeConf);
        setProcessDeleteReason(event, dto);
        setProcessInstanceVersion(event.getProcessInstanceId(), dto);
        sendMessageQueue(dto, PROCESS_INSTANCE_ABORTED);
        log.info("MessagePushProcessEventListener#onAborted...end: {}", event.getProcessInstanceId());
    }

    @Override
    public void onCompleted(FlowableEngineEntityEvent event) {
        log.info("MessagePushProcessEventListener#onCompleted...{}", event.getProcessInstanceId());
        Deployment deployment = getContext().getDeployment(() -> repositoryService.createDeploymentQuery()
                .deploymentId(((ExecutionEntityImpl) event.getEntity()).getDeploymentId())
                .singleResult());
        Process mainProcess = getContext().getProcess(() -> repositoryService.getBpmnModel(event.getProcessDefinitionId()).getMainProcess());
        BpmnTaskDelegateAssigner initiator = getContext().getInitiator(() -> BpmnTaskDelegateAssigner.toObjectCompatible(((ExecutionEntityImpl) event.getEntity())
                .getVariable(INTERNAL_INITIATOR)));

        ProcessInstanceDTO dto = new ProcessInstanceDTO()
                .setType(PROCESS_INSTANCE_COMPLETED)
                .setCategory(deployment.getCategory())
                .setProcessInstanceId(event.getProcessInstanceId())
                .setCurrentElementKey("撤回流程实例无节点信息")
                .setProcessDefinitionId(event.getProcessDefinitionId())
                .setProcessDefinitionKey(((ExecutionEntityImpl) event.getEntity()).getProcessDefinitionKey())
                .setProcessDefinitionVersion(((ExecutionEntityImpl) event.getEntity()).getProcessDefinitionVersion())
                .setInitiator(initiator)
                .setVariables(((ExecutionEntityImpl) event.getEntity()).getVariables())
                .setStartTime(((ExecutionEntityImpl) event.getEntity()).getStartTime())
                .setTenantId(((ExecutionEntityImpl) event.getEntity()).getTenantId())
                .setBusinessKey(((ExecutionEntityImpl) event.getEntity()).getProcessInstanceBusinessKey())
                .setBusinessProcessInstanceName(((ExecutionEntityImpl) event.getEntity()).getName());
        BpmnMetaParserHelper.getNoticeConfig(mainProcess).ifPresent(dto::setNoticeConf);
        String version = (String) runtimeService.getVariable(event.getProcessInstanceId(), WORKFLOW_ENGINE_VERSION);
        if (Objects.isNull(version)) {
            version = FLOW_SERVER_VERSION_121;
        }
        dto.setWorkflowEngineVersion(version);
        sendMessageQueue(dto, PROCESS_INSTANCE_COMPLETED);
        log.info("MessagePushProcessEventListener#onCompleted...end: {}", event.getProcessInstanceId());
    }

    private void setProcessDeleteReason(FlowableCancelledEvent event, ProcessInstanceDTO dto) {
        String processDeleteReason = (String) runtimeService.getVariable(event.getProcessInstanceId(), INTERNAL_PROCESS_DELETE_REASON);
        dto.setReason(processDeleteReason);
    }


    private void setProcessInstanceVersion(String processInstanceId, ProcessInstanceDTO dto) {
        String version = getContext().getProcessInstanceVersion(() -> runtimeService.getVariable(processInstanceId, WORKFLOW_ENGINE_VERSION, String.class));

        if (Objects.isNull(version)) {
            version = FLOW_SERVER_VERSION_121;
        }
        dto.setWorkflowEngineVersion(version);
    }

    private void sendMessageQueue(ProcessInstanceDTO dto, ProcessInstanceEventEnum eventEnum) {
        if (!sendMQ) {
            return;
        }
        eventProducer.send(Event.builder()
                .shardingKey(dto.getProcessInstanceId())
                .eventCode(eventEnum.getEventCode())
                .targetId(dto.getProcessInstanceId())
                .targetType(eventEnum.getTag())
                .data(dto)
                .build());
    }

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE;
    }
}
