package com.bonus.mq.rocket;

import com.bonus.mq.MQListener;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.remoting.RPCHook;
import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel;
import org.apache.rocketmq.spring.support.RocketMQUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.MethodParameter;
import org.springframework.core.env.Environment;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.converter.SmartMessageConverter;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.Assert;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

public class RocketListenerContainer implements InitializingBean, DisposableBean, SmartLifecycle, EnvironmentAware {
   private static final Logger log = LoggerFactory.getLogger(RocketListenerContainer.class);
   private Environment environment;
   private MessageConverter messageConverter;
   private MQListener<Object> mqListener;
   private DefaultMQPushConsumer consumer;
   private Type messageType;
   private MethodParameter methodParameter;
   private boolean running;
   private String name;
   private String nameServer;
   private String accessKey;
   private String secretKey;
   private boolean enableMsgTrace;
   private boolean tlsEnable;
   private String topic;
   private String tag;
   private String consumerGroup;
   private int consumeThreadMax = 10;
   private int consumeThreadMin = 10;
   private String customizedTraceTopic;
   private int maxReconsumeTimes = -1;

   public void destroy() {
      this.running = false;
      if (Objects.nonNull(this.consumer)) {
         this.consumer.shutdown();
      }

   }

   public void stop(Runnable callback) {
      this.stop();
      callback.run();
   }

   public void start() {
      if (!this.isRunning()) {
         try {
            this.consumer.start();
         } catch (MQClientException var2) {
            throw new IllegalStateException("Failed to start RocketMQ push consumer", var2);
         }

         this.running = true;
      }
   }

   public void stop() {
      if (this.isRunning()) {
         if (Objects.nonNull(this.consumer)) {
            this.consumer.shutdown();
         }

         this.running = false;
      }

   }

   public boolean isRunning() {
      return this.running;
   }

   public int getPhase() {
      return Integer.MAX_VALUE;
   }

   public void afterPropertiesSet() throws Exception {
      this.initMQConsumer();
      this.messageType = this.getMessageType();
      this.methodParameter = this.getMethodParameter();
   }

   private void initMQConsumer() throws MQClientException {
      if (this.mqListener == null) {
         throw new IllegalArgumentException("Property 'mqListener'  is required");
      } else {
         Assert.notNull(this.consumerGroup, "Property 'consumerGroup' is required");
         Assert.notNull(this.topic, "Property 'topic' is required");
         RPCHook rpcHook = RocketMQUtil.getRPCHookByAkSk(this.environment, this.accessKey, this.secretKey);
         if (Objects.nonNull(rpcHook)) {
            this.consumer = new DefaultMQPushConsumer(this.consumerGroup, rpcHook, new AllocateMessageQueueAveragely(), this.enableMsgTrace, this.customizedTraceTopic);
            this.consumer.setVipChannelEnabled(false);
         } else {
            log.debug("Access-key or secret-key not configure in " + String.valueOf(this) + ".");
            this.consumer = new DefaultMQPushConsumer(this.consumerGroup, this.enableMsgTrace, this.environment.resolveRequiredPlaceholders("${rocketmq.consumer.customized-trace-topic:}"));
         }

         this.consumer.setNamesrvAddr(this.nameServer);
         this.consumer.setConsumeThreadMax(this.consumeThreadMax);
         this.consumer.setConsumeThreadMin(this.consumeThreadMin);
         this.consumer.setConsumeTimeout(15L);
         this.consumer.setMaxReconsumeTimes(this.maxReconsumeTimes);
         this.consumer.setAwaitTerminationMillisWhenShutdown(3000L);
         this.consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
         this.consumer.setMessageModel(MessageModel.CLUSTERING);
         this.consumer.subscribe(this.topic, this.tag);
         this.consumer.setMessageListener(new DefaultMessageListenerConcurrently());
         this.consumer.setUseTLS(this.tlsEnable);
      }
   }

   private MethodParameter getMethodParameter() {
      Class<?> targetClass = null;
      if (this.mqListener != null) {
         targetClass = AopProxyUtils.ultimateTargetClass(this.mqListener);
      }

      Type messageType = this.getMessageType();
      Class clazz;
      if (messageType instanceof ParameterizedType && this.messageConverter instanceof SmartMessageConverter) {
         clazz = (Class)((ParameterizedType)messageType).getRawType();
      } else {
         if (!(messageType instanceof Class)) {
            throw new RuntimeException("parameterType:" + String.valueOf(messageType) + " of onMessage method is not supported");
         }

         clazz = (Class)messageType;
      }

      try {
         assert targetClass != null;

         Method method = targetClass.getMethod("onMessage", clazz);
         return new MethodParameter(method, 0);
      } catch (NoSuchMethodException var5) {
         throw new RuntimeException("parameterType:" + String.valueOf(messageType) + " of onMessage method is not supported");
      }
   }

   private Type getMessageType() {
      Class<?> targetClass = null;
      if (this.mqListener != null) {
         targetClass = AopProxyUtils.ultimateTargetClass(this.mqListener);
      }

      Type matchedGenericInterface;
      Type[] actualTypeArguments;
      for(matchedGenericInterface = null; Objects.nonNull(targetClass); targetClass = targetClass.getSuperclass()) {
         actualTypeArguments = targetClass.getGenericInterfaces();
         Type[] var4 = actualTypeArguments;
         int var5 = actualTypeArguments.length;

         for(int var6 = 0; var6 < var5; ++var6) {
            Type type = var4[var6];
            if (type instanceof ParameterizedType && Objects.equals(((ParameterizedType)type).getRawType(), MQListener.class)) {
               matchedGenericInterface = type;
               break;
            }
         }
      }

      if (Objects.isNull(matchedGenericInterface)) {
         return Object.class;
      } else {
         actualTypeArguments = ((ParameterizedType)matchedGenericInterface).getActualTypeArguments();
         if (Objects.nonNull(actualTypeArguments) && actualTypeArguments.length > 0) {
            return actualTypeArguments[0];
         } else {
            return Object.class;
         }
      }
   }

   public void setEnvironment(Environment environment) {
      this.environment = environment;
   }

   public Environment getEnvironment() {
      return this.environment;
   }

   public MessageConverter getMessageConverter() {
      return this.messageConverter;
   }

   public MQListener<Object> getMqListener() {
      return this.mqListener;
   }

   public DefaultMQPushConsumer getConsumer() {
      return this.consumer;
   }

   public String getName() {
      return this.name;
   }

   public String getNameServer() {
      return this.nameServer;
   }

   public String getAccessKey() {
      return this.accessKey;
   }

   public String getSecretKey() {
      return this.secretKey;
   }

   public boolean isEnableMsgTrace() {
      return this.enableMsgTrace;
   }

   public boolean isTlsEnable() {
      return this.tlsEnable;
   }

   public String getTopic() {
      return this.topic;
   }

   public String getTag() {
      return this.tag;
   }

   public String getConsumerGroup() {
      return this.consumerGroup;
   }

   public int getConsumeThreadMax() {
      return this.consumeThreadMax;
   }

   public int getConsumeThreadMin() {
      return this.consumeThreadMin;
   }

   public String getCustomizedTraceTopic() {
      return this.customizedTraceTopic;
   }

   public int getMaxReconsumeTimes() {
      return this.maxReconsumeTimes;
   }

   public void setMessageConverter(final MessageConverter messageConverter) {
      this.messageConverter = messageConverter;
   }

   public void setMqListener(final MQListener<Object> mqListener) {
      this.mqListener = mqListener;
   }

   public void setConsumer(final DefaultMQPushConsumer consumer) {
      this.consumer = consumer;
   }

   public void setMessageType(final Type messageType) {
      this.messageType = messageType;
   }

   public void setMethodParameter(final MethodParameter methodParameter) {
      this.methodParameter = methodParameter;
   }

   public void setRunning(final boolean running) {
      this.running = running;
   }

   public void setName(final String name) {
      this.name = name;
   }

   public void setNameServer(final String nameServer) {
      this.nameServer = nameServer;
   }

   public void setAccessKey(final String accessKey) {
      this.accessKey = accessKey;
   }

   public void setSecretKey(final String secretKey) {
      this.secretKey = secretKey;
   }

   public void setEnableMsgTrace(final boolean enableMsgTrace) {
      this.enableMsgTrace = enableMsgTrace;
   }

   public void setTlsEnable(final boolean tlsEnable) {
      this.tlsEnable = tlsEnable;
   }

   public void setTopic(final String topic) {
      this.topic = topic;
   }

   public void setTag(final String tag) {
      this.tag = tag;
   }

   public void setConsumerGroup(final String consumerGroup) {
      this.consumerGroup = consumerGroup;
   }

   public void setConsumeThreadMax(final int consumeThreadMax) {
      this.consumeThreadMax = consumeThreadMax;
   }

   public void setConsumeThreadMin(final int consumeThreadMin) {
      this.consumeThreadMin = consumeThreadMin;
   }

   public void setCustomizedTraceTopic(final String customizedTraceTopic) {
      this.customizedTraceTopic = customizedTraceTopic;
   }

   public void setMaxReconsumeTimes(final int maxReconsumeTimes) {
      this.maxReconsumeTimes = maxReconsumeTimes;
   }


   public class DefaultMessageListenerConcurrently implements MessageListenerConcurrently {
      public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
         Iterator var3 = msgs.iterator();

         while(var3.hasNext()) {
            MessageExt messageExt = (MessageExt)var3.next();

            try {
               String traceId = messageExt.getUserProperty("X-Trace-Id");
               MDC.put("X-Trace-Id", traceId == null ? UUID.randomUUID().toString() : traceId);
               if (RocketListenerContainer.this.mqListener != null) {
                  RocketListenerContainer.this.mqListener.onMessage(this.doConvertMessage(messageExt));
               }
            } catch (Exception var6) {
               RocketListenerContainer.log.error("Consume failed. messageId:{}, topic:{}, reconsumeTimes:{}", new Object[]{messageExt.getMsgId(), messageExt.getTopic(), messageExt.getReconsumeTimes(), var6});
               context.setDelayLevelWhenNextConsume(0);
               return ConsumeConcurrentlyStatus.RECONSUME_LATER;
            }
         }

         return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
      }

      private Object doConvertMessage(MessageExt messageExt) {
         if (!Objects.equals(RocketListenerContainer.this.messageType, MessageExt.class) && !Objects.equals(RocketListenerContainer.this.messageType, Message.class)) {
            String str = new String(messageExt.getBody(), StandardCharsets.UTF_8);
            if (Objects.equals(RocketListenerContainer.this.messageType, String.class)) {
               return str;
            } else {
               try {
                  return RocketListenerContainer.this.messageType instanceof Class ? RocketListenerContainer.this.messageConverter.fromMessage(MessageBuilder.withPayload(str).build(), (Class)RocketListenerContainer.this.messageType) : ((SmartMessageConverter)RocketListenerContainer.this.messageConverter).fromMessage(MessageBuilder.withPayload(str).build(), (Class)((ParameterizedType)RocketListenerContainer.this.messageType).getRawType(), RocketListenerContainer.this.methodParameter);
               } catch (Exception var4) {
                  RocketListenerContainer.log.info("convert failed. str:{}, msgType:{}", str, RocketListenerContainer.this.messageType);
                  throw new RuntimeException("cannot convert message to " + String.valueOf(RocketListenerContainer.this.messageType), var4);
               }
            }
         } else {
            return messageExt;
         }
      }
   }
}
