package org.springblade.common.notify.rocketmq.producer;

import com.aliyun.openservices.ons.api.PropertyKeyConst;
import org.springblade.common.notify.constant.NotifyErrorConstants;
import com.wn.common.validator.Validator;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author caiyihua
 */
@Slf4j
@Component
@ConditionalOnProperty(name = "ali.mq.producer.support.flag", havingValue = "true", matchIfMissing = false)
public class RocketMqProducer {
    @Value("${ali.mq.namesrv.addr:http://onsaddr.mq-internet-access.mq-internet.aliyuncs.com:80}")
    @Setter
    private String nameServer;
    @Value("${ali.mq.type:aliyun}")
    @Setter
    private String mqType;
    @Value("${ali.mq.access.key:accessKey}")
    @Setter
    private String accessKey;
    @Value("${ali.mq.secret.key:secretKey}")
    @Setter
    private String secretKey;

    @Value("${ali.mq.producer.pool.max.total:100}")
    @Setter
    private Integer maxTotal;
    @Value("${ali.mq.producer.pool.max.idle:10}")
    @Setter
    private Integer maxIdle;
    @Value("${ali.mq.producer.pool.min.idle:0}")
    @Setter
    private Integer minIdle;
    @Value("${ali.mq.producer.pool.max.wait.millis:5000}")
    @Setter
    private Integer maxWaitMillis;
    @Value("${ali.mq.producer.pool.min.evictable.idle.time.millis:600000}")
    @Setter
    private Integer minEvictableIdleTimeMillis;
    @Value("${ali.mq.producer.pool.time.between.eviction.runs.millis:3000}")
    @Setter
    private Integer timeBetweenEvictionRunsMillis;
    @Value("${ali.mq.producer.pool.num.tests.per.eviction.run:10}")
    @Setter
    private Integer numTestsPerEvictionRun;

    private ProducerProxyPool producerProxyPool;

    @PostConstruct
    private void init() {
        Properties properties = new Properties();
        // AccessKey 阿里云身份验证，在阿里云服务器管理控制台创建
        properties.put(PropertyKeyConst.AccessKey, accessKey);
        // SecretKey 阿里云身份验证，在阿里云服务器管理控制台创建
        properties.put(PropertyKeyConst.SecretKey, secretKey);
        properties.put(PropertyKeyConst.MQType, mqType);
        // 设置 TCP 接入域名（此处以公共云生产环境为例）
        properties.put(PropertyKeyConst.NAMESRV_ADDR, nameServer);
        //设置发送超时时间，单位：毫秒。
        properties.setProperty(PropertyKeyConst.SendMsgTimeoutMillis, "3000");

        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        // 整个池最大值
        config.setMaxTotal(maxTotal);
        // 最大空闲
        config.setMaxIdle(maxIdle);
        // 最小空闲
        config.setMinIdle(minIdle);
        // 最大等待时间，-1表示一直等
        config.setMaxWaitMillis(maxWaitMillis);
        // 当对象池没有空闲对象时，新的获取对象的请求是否阻塞。true阻塞。默认值是true
        config.setBlockWhenExhausted(true);
        // 在从对象池获取对象时是否检测对象有效，true是；默认值是false
        config.setTestOnBorrow(false);
        // 在向对象池中归还对象时是否检测对象有效，true是，默认值是false
        config.setTestOnReturn(false);
        // 在检测空闲对象线程检测到对象不需要移除时，是否检测对象的有效性。true是，默认值是false
        config.setTestWhileIdle(false);
        // 可发呆的时间, 10mins
        config.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        // 发呆过长移除的时候是否test一下先
        config.setTestWhileIdle(true);
        // 回收资源线程的执行周期 3s
        config.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        config.setNumTestsPerEvictionRun(numTestsPerEvictionRun);

        producerProxyPool = new ProducerProxyPool(properties, config);
        log.info("RocketMQ Producer Pool started!");
    }

    /**
     * 同步发送消息
     *
     * @param topic            请注意topic应该和producerId配置一致
     * @param tag
     * @param messageBody
     * @param userProperties
     * @param startDeliverTime 延迟发送时间，是时间戳
     * @return
     */
    @Retryable(value = {Exception.class}, maxAttempts = 10, backoff = @Backoff(delay = 10000L, multiplier = 2))
    public SendMqMessageResult sendMessage(String topic, String tag, String messageBody, Properties userProperties, Long startDeliverTime) {
        ProducerProxy producerProxy = producerProxyPool.getProducerProxy();
        SendMqMessageResult sendResult = producerProxy.sendMessage(topic, tag, messageBody, userProperties, startDeliverTime);
        producerProxyPool.returnProducerProxy(producerProxy);
        return sendResult;
    }

    /**
     * 异步发送消息
     *
     * @param topic
     * @param tag
     * @param messageBody
     * @param userProperties
     * @return
     */
    public SendMqMessageResult sendAsyncMessage(String topic, String tag, String messageBody, Properties userProperties) {
        ProducerProxy producer = producerProxyPool.getProducerProxy();
        // 异步发送消息, 发送结果通过 callback 返回给客户端。
        SendMqMessageResult sendResult = producer.sendAsyncMessage(topic, tag, messageBody, userProperties);
        producerProxyPool.returnProducerProxy(producer);
        return sendResult;
    }

    /**
     * 同步发送多标签消息, 支持多个tag, 用英文逗号分隔
     *
     * @param topic
     * @param tags
     * @param messageBody
     * @param userProperties
     * @param startDeliverTime
     * @return
     */
    public List<SendMqMessageResult> sendMultiTagsMessage(String topic, String tags, String messageBody, Properties userProperties, Long startDeliverTime) {
        Validator.notEmpty(tags, NotifyErrorConstants.MQ_MESSAGE_PARMETER_ERROR);
        ProducerProxy producerProxy = producerProxyPool.getProducerProxy();
        String[] tagArray = tags.split(",");
        List<SendMqMessageResult> results = new ArrayList<>();
        for (String tag : tagArray) {
            SendMqMessageResult sendResult = producerProxy.sendMessage(topic, tag, messageBody, userProperties, startDeliverTime);
            results.add(sendResult);
        }
        producerProxyPool.returnProducerProxy(producerProxy);
        return results;
    }

    /**
     * 异步发送多标签消息, 支持多个tag, 用英文逗号分隔
     *
     * @param topic
     * @param tags
     * @param messageBody
     * @param userProperties
     * @return
     */
    public List<SendMqMessageResult> sendAsyncMultiTagsMessage(String topic, String tags, String messageBody, Properties userProperties) {
        Validator.notEmpty(tags, NotifyErrorConstants.MQ_MESSAGE_PARMETER_ERROR);
        ProducerProxy producerProxy = producerProxyPool.getProducerProxy();
        String[] tagArray = tags.split(",");
        List<SendMqMessageResult> results = new ArrayList<>();
        for (String tag : tagArray) {
            SendMqMessageResult sendResult = producerProxy.sendAsyncMessage(topic, tag, messageBody, userProperties);
            results.add(sendResult);
        }
        producerProxyPool.returnProducerProxy(producerProxy);
        return results;
    }

    @PreDestroy
    private void destroy() {
        // 在应用退出前，销毁 Producer 对象<br>
        // 注意：如果不销毁也没有问题
        producerProxyPool.close();
    }
}
