package com.njzscloud.common.redis.config;

import cn.hutool.core.util.StrUtil;
import com.njzscloud.common.redis.RedisCli;
import com.njzscloud.common.redis.annotation.RedisChannel;
import com.njzscloud.common.redis.annotation.RedisListener;
import com.njzscloud.common.redis.support.RedisMessageDispatch;
import io.lettuce.core.RedisURI;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;

import java.time.Duration;
import java.util.Map;

/**
 * Redis 配置类
 */
@Slf4j
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(value = "spring.redis.enable", havingValue = "true")
@EnableConfigurationProperties({RedisProperties.class, RedisOtherProperties.class})
public class RedisServiceAutoConfiguration {

    /**
     * Redis 客户端配置
     *
     * @param redisProperties      配置（Spring 自带的配置）
     * @param redisOtherProperties 配置（自定义配置）
     * @return {@link RedisCli}
     */
    @Bean(destroyMethod = "exit")
    public RedisCli redisCli(RedisProperties redisProperties, RedisOtherProperties redisOtherProperties) {
        String url = redisProperties.getUrl();
        RedisURI redisURI;
        if (url == null || url.isEmpty()) {
            redisURI = new RedisURI();
            redisURI.setSsl(redisProperties.isSsl());
            String username = redisProperties.getUsername();
            if (StrUtil.isNotBlank(username)) redisURI.setUsername(username);
            String password = redisProperties.getPassword();
            if (StrUtil.isNotBlank(password)) redisURI.setPassword(password.toCharArray());

            redisURI.setHost(redisProperties.getHost());
            redisURI.setPort(redisProperties.getPort());
            redisURI.setDatabase(redisProperties.getDatabase());
            Duration timeout = redisProperties.getTimeout();
            if (timeout != null) redisURI.setTimeout(timeout);
            String clientName = redisProperties.getClientName();
            if (StrUtil.isNotBlank(clientName)) redisURI.setClientName(clientName);
        } else {
            redisURI = RedisURI.create(url);
        }

        RedisProperties.Lettuce lettuce = redisProperties.getLettuce();
        RedisProperties.Pool lettucePool = lettuce.getPool();
        GenericObjectPoolConfig<?> poolConfig = new GenericObjectPoolConfig<>();
        poolConfig.setMaxTotal(lettucePool.getMaxActive());
        poolConfig.setMaxIdle(lettucePool.getMaxIdle());
        poolConfig.setMinIdle(lettucePool.getMinIdle());
        Duration timeBetweenEvictionRuns = lettucePool.getTimeBetweenEvictionRuns();
        if (timeBetweenEvictionRuns != null) poolConfig.setTimeBetweenEvictionRuns(timeBetweenEvictionRuns);

        Duration maxWait = lettucePool.getMaxWait();
        if (maxWait != null) poolConfig.setMaxWait(maxWait);


        return new RedisCli(redisURI, poolConfig, redisOtherProperties.isPubsub());
    }


    /**
     * 发布订阅模式的配置
     */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnProperty(value = "spring.redis.pubsub", havingValue = "true")
    public static class RedisPubSubConfiguration {

        /**
         * 配置消息派发器
         *
         * @param redisCli Redis 客户端 {@link RedisCli}
         * @return {@link RedisMessageDispatch}
         */
        @Bean
        public RedisMessageDispatch redisMessageDispatch(RedisCli redisCli) {
            return new RedisMessageDispatch(redisCli);
        }

        /**
         * 配置 Spring 事件监听器，当触发 Spring 容器刷新事件{@link ContextRefreshedEvent}时，注册自定义的 Redis 监听器
         *
         * @param redisMessageDispatch 消息派发器
         * @return {@link ApplicationListener}
         * @see RedisListener
         * @see RedisChannel
         */
        @Bean
        public ApplicationListener<ContextRefreshedEvent> applicationListener(RedisMessageDispatch redisMessageDispatch) {
            return event -> {
                Map<String, Object> beansWithAnnotation = event
                        .getApplicationContext()
                        .getBeansWithAnnotation(RedisListener.class);
                beansWithAnnotation.forEach((k, v) -> {
                    if (log.isDebugEnabled()) {
                        log.debug("发现 Redis 监听器：【{}】", v.getClass().getName());
                    }
                    redisMessageDispatch.subscribe(v);
                });
            };
        }
    }

}
