package tech.easyflow.common.mq.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.ObjectProvider;
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.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.StringRedisTemplate;
import tech.easyflow.common.mq.core.MQConsumerContainer;
import tech.easyflow.common.mq.core.MQConsumerHandler;
import tech.easyflow.common.mq.core.MQDeadLetterHandler;
import tech.easyflow.common.mq.core.MQDeadLetterService;
import tech.easyflow.common.mq.core.MQMessageConverter;
import tech.easyflow.common.mq.core.MQProducer;
import tech.easyflow.common.mq.redis.JacksonMQMessageConverter;
import tech.easyflow.common.mq.redis.RedisMQConsumerContainer;
import tech.easyflow.common.mq.redis.RedisMQDeadLetterService;
import tech.easyflow.common.mq.redis.RedisMQProducer;
import tech.easyflow.common.mq.redis.RedisStreamKeySupport;
import tech.easyflow.common.mq.support.MQHealthSupport;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

import io.lettuce.core.api.StatefulConnection;

import java.util.List;

@Configuration
@EnableConfigurationProperties(MQProperties.class)
public class MQConfiguration {

    @Bean(destroyMethod = "close")
    @ConditionalOnProperty(prefix = "easyflow.mq", name = "enabled", havingValue = "true", matchIfMissing = true)
    public MQRedisResources mqRedisResources(RedisProperties redisProperties, MQProperties mqProperties) {
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        configuration.setHostName(redisProperties.getHost());
        configuration.setPort(redisProperties.getPort());
        configuration.setDatabase(mqProperties.getRedis().getDatabase());
        if (redisProperties.getUsername() != null) {
            configuration.setUsername(redisProperties.getUsername());
        }
        if (redisProperties.getPassword() != null) {
            configuration.setPassword(RedisPassword.of(redisProperties.getPassword()));
        }
        LettuceClientConfiguration clientConfiguration = createClientConfiguration(redisProperties, mqProperties);
        LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(configuration, clientConfiguration);
        connectionFactory.afterPropertiesSet();
        return new MQRedisResources(connectionFactory, new StringRedisTemplate(connectionFactory));
    }

    private LettuceClientConfiguration createClientConfiguration(RedisProperties redisProperties,
                                                                 MQProperties mqProperties) {
        MQProperties.Redis.Pool pool = mqProperties.getRedis().getPool();
        GenericObjectPoolConfig<StatefulConnection<?, ?>> poolConfig = new GenericObjectPoolConfig<>();
        poolConfig.setMaxTotal(pool.getMaxActive());
        poolConfig.setMaxIdle(pool.getMaxIdle());
        poolConfig.setMinIdle(pool.getMinIdle());
        LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder =
            LettucePoolingClientConfiguration.builder().poolConfig(poolConfig);
        if (redisProperties.getTimeout() != null) {
            builder.commandTimeout(redisProperties.getTimeout());
        }
        return builder.build();
    }

    @Bean(name = "mqRedisConnectionFactory", autowireCandidate = false, defaultCandidate = false)
    @ConditionalOnProperty(prefix = "easyflow.mq", name = "enabled", havingValue = "true", matchIfMissing = true)
    public LettuceConnectionFactory mqRedisConnectionFactory(MQRedisResources mqRedisResources) {
        return mqRedisResources.connectionFactory();
    }

    @Bean(name = "mqStringRedisTemplate", autowireCandidate = false, defaultCandidate = false)
    @ConditionalOnProperty(prefix = "easyflow.mq", name = "enabled", havingValue = "true", matchIfMissing = true)
    public StringRedisTemplate mqStringRedisTemplate(MQRedisResources mqRedisResources) {
        return mqRedisResources.stringRedisTemplate();
    }

    @Bean
    @ConditionalOnProperty(prefix = "easyflow.mq", name = "enabled", havingValue = "true", matchIfMissing = true)
    public RedisStreamKeySupport redisStreamKeySupport(MQProperties mqProperties) {
        return new RedisStreamKeySupport(mqProperties);
    }

    @Bean
    @ConditionalOnProperty(prefix = "easyflow.mq", name = "enabled", havingValue = "true", matchIfMissing = true)
    public MQMessageConverter mqMessageConverter(ObjectMapper objectMapper) {
        return new JacksonMQMessageConverter(objectMapper);
    }

    @Bean
    @ConditionalOnProperty(prefix = "easyflow.mq", name = "enabled", havingValue = "true", matchIfMissing = true)
    public MQDeadLetterService mqDeadLetterService(MQRedisResources mqRedisResources,
                                                   MQMessageConverter mqMessageConverter,
                                                   RedisStreamKeySupport redisStreamKeySupport,
                                                   ObjectProvider<MQDeadLetterHandler> handlersProvider) {
        List<MQDeadLetterHandler> handlers = handlersProvider.orderedStream().toList();
        return new RedisMQDeadLetterService(
                mqRedisResources.stringRedisTemplate(),
                mqMessageConverter,
                redisStreamKeySupport,
                handlers
        );
    }

    @Bean
    @ConditionalOnProperty(prefix = "easyflow.mq", name = "enabled", havingValue = "true", matchIfMissing = true)
    public MQProducer mqProducer(MQRedisResources mqRedisResources,
                                 MQProperties mqProperties,
                                 MQMessageConverter mqMessageConverter,
                                 RedisStreamKeySupport redisStreamKeySupport) {
        return new RedisMQProducer(
                mqRedisResources.stringRedisTemplate(),
                mqProperties,
                mqMessageConverter,
                redisStreamKeySupport
        );
    }

    @Bean
    @ConditionalOnProperty(prefix = "easyflow.mq", name = "enabled", havingValue = "true", matchIfMissing = true)
    public MQHealthSupport mqHealthSupport(MQRedisResources mqRedisResources) {
        return new MQHealthSupport(mqRedisResources.connectionFactory());
    }

    @Bean
    @ConditionalOnProperty(prefix = "easyflow.mq", name = "enabled", havingValue = "true", matchIfMissing = true)
    public MQConsumerContainer mqConsumerContainer(MQRedisResources mqRedisResources,
                                                   MQProperties mqProperties,
                                                   MQMessageConverter mqMessageConverter,
                                                   MQDeadLetterService mqDeadLetterService,
                                                   RedisStreamKeySupport redisStreamKeySupport,
                                                   ObjectProvider<MQConsumerHandler> handlersProvider) {
        List<MQConsumerHandler> handlers = handlersProvider.orderedStream().toList();
        return new RedisMQConsumerContainer(
                mqRedisResources.connectionFactory(),
                mqRedisResources.stringRedisTemplate(),
                mqProperties,
                mqMessageConverter,
                mqDeadLetterService,
                redisStreamKeySupport,
                handlers
        );
    }
}
