package com.red.circle.mq.rocket.business.service;

import com.red.circle.component.mq.props.RocketMQProperties;
import com.red.circle.component.mq.service.*;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.text.StringUtils;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.apis.ClientConfiguration;
import org.apache.rocketmq.client.apis.ClientException;
import org.apache.rocketmq.client.apis.ClientServiceProvider;
import org.apache.rocketmq.client.apis.StaticSessionCredentialsProvider;
import org.apache.rocketmq.client.apis.message.Message;
import org.apache.rocketmq.client.apis.message.MessageBuilder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;

/**
 * 安全的 RocketMQ Producer.
 *
 * @author Leo
 */
@Slf4j
@Component
public class ManagedRocketMQProducer implements Producer {

  private RocketMQProperties rocketMQProperties;
  private final Map<String, RocketMQProducerMapping> producerMapping = new ConcurrentHashMap<>();
  private final ThreadPoolExecutor mqCallbackExecutor;
  @Value("${rocketmq.local-disable:false}")
  private boolean localDisable;

  public ManagedRocketMQProducer(
      @Nullable RocketMQProperties rocketMQProperties,
      @Qualifier("mqCallbackExecutor") ThreadPoolExecutor mqCallbackExecutor) {
    this.rocketMQProperties = rocketMQProperties;
    this.mqCallbackExecutor = mqCallbackExecutor;
  }

  private Message convertMessage(ProducerMessage producerMessage) {
    if (Objects.isNull(producerMessage)) {
      throw new IllegalArgumentException("producerMessage is null.");
    }
    
    RocketMQProducerMapping mapping = getRocketMQProducerMapping(producerMessage.getTopic());
    MessageBuilder builder = mapping.getProvider()
        .newMessageBuilder()
        .setTopic(producerMessage.getTopic())
        .setKeys(producerMessage.getKeys())
        .setBody(producerMessage.getBody());
    
    if (Objects.nonNull(producerMessage.getDeliveryTimestamp())) {
      builder.setDeliveryTimestamp(producerMessage.getDeliveryTimestamp());
    }
    
    if (StringUtils.isNotBlank(producerMessage.getTag())) {
      builder.setTag(producerMessage.getTag());
    }
    
    if (StringUtils.isNotBlank(producerMessage.getMessageGroup())) {
      builder.setMessageGroup(producerMessage.getMessageGroup());
    }
    
    return builder.build();
  }

  private RocketMQProducerMapping getRocketMQProducerMapping(String topic) {
    RocketMQProducerMapping mapping = producerMapping.get(topic);
    if (Objects.isNull(mapping)) {
      throw new IllegalArgumentException("Not Topic RocketMQProducerMapping.");
    }
    return mapping;
  }

  @PostConstruct
  public void start() {
    if (localDisable) {
      log.warn("rocketmq.local-disable=true, skip RocketMQ producer initialization");
      return;
    }
    if (Objects.isNull(rocketMQProperties)) {
      throw new IllegalStateException("RocketMQProperties bean is required when RocketMQ is enabled");
    }

    List<String> failedTopics = new ArrayList<>();
    
    rocketMQProperties.getTopics().forEach((topic, value) -> {
      try {
        ClientServiceProvider provider = ClientServiceProvider.loadService();
        org.apache.rocketmq.client.apis.producer.Producer producer = provider
            .newProducerBuilder()
            .setClientConfiguration(newClientConfiguration())
            .setTopics(new String[]{topic})
            .build();
        producerMapping.put(topic, new RocketMQProducerMapping(provider, producer));
        log.info("MQ Producer 初始化成功: topic={}", topic);
      } catch (Throwable e) {
        log.error("MQ Producer 初始化失败: topic={}", topic, e);
        failedTopics.add(topic);
      }
    });

    if (!failedTopics.isEmpty()) {
      throw new RuntimeException("MQ Producer 初始化失败的 topic: " + failedTopics);
    }
  }

  public ClientConfiguration newClientConfiguration() {
    String var1 = rocketMQProperties.getAccessKey();
    String var2 = rocketMQProperties.getSecretKey();
    StaticSessionCredentialsProvider var3 = new StaticSessionCredentialsProvider(var1, var2);
    String var4 = rocketMQProperties.getEndpoints();

    return ClientConfiguration.newBuilder()
            .setEndpoints(var4)
            .setCredentialProvider(var3)
            .setRequestTimeout(Duration.ofSeconds(30L))
            .build();
  }

  public String send(ProducerMessage message) {
    if (localDisable) {
      log.warn("RocketMQ disabled in local mode, skip sync send. topic:{}, tag:{}",
          message.getTopic(), message.getTag());
      return "local-disabled";
    }

    org.apache.rocketmq.client.apis.producer.Producer producer = 
        getRocketMQProducerMapping(message.getTopic()).getProducer();
    
    if (Objects.isNull(producer)) {
      throw new IllegalArgumentException("Topic=" + message.getTopic() + " config error.");
    }
    
    try {
      return producer.send(convertMessage(message)).getMessageId().toString();
    } catch (ClientException e) {
      log.error("MQ 同步发送失败, topic:{}, tag:{}", 
          message.getTopic(), message.getTag(), e);
      throw new RuntimeException("MQ send failed", e);
    }
  }


  public void sendAsync(ProducerMessage message, SendCallback callback) {
    if (localDisable) {
      log.warn("RocketMQ disabled in local mode, skip async send. topic:{}, tag:{}",
          message.getTopic(), message.getTag());
      callback.onSuccess("local-disabled", message);
      return;
    }

    org.apache.rocketmq.client.apis.producer.Producer producer = 
        getRocketMQProducerMapping(message.getTopic()).getProducer();
    
    if (Objects.isNull(producer)) {
      throw new IllegalArgumentException("Topic=" + message.getTopic() + " config error.");
    }
    
    CompletableFuture<org.apache.rocketmq.client.apis.producer.SendReceipt> future = 
        producer.sendAsync(convertMessage(message));
    
    future.whenCompleteAsync((result, throwable) -> {
      if (throwable != null) {
        callback.onFail(throwable);
      } else if (Objects.isNull(result.getMessageId())) {
        callback.onFail(new RuntimeException("message Id is null."));
      } else {
        callback.onSuccess(result.getMessageId().toString(), message);
      }
    }, mqCallbackExecutor);
  }

  @PreDestroy
  public void shutdown() {
    if (CollectionUtils.isNotEmpty(producerMapping)) {
      producerMapping.values().forEach(mapping -> {
        mapping.close();
        log.info("producer [{}] close.", mapping.getProducer());
      });
      producerMapping.clear();
    }
  }

}
