package com.mosquito.project.config;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

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

@Configuration
@ConditionalOnBean(RedisConnectionFactory.class)
public class CacheConfig {

    private final AppConfig appConfig;

    public CacheConfig(AppConfig appConfig) {
        this.appConfig = appConfig;
    }

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        AppConfig.CacheConfig cacheConfig = appConfig.getCache();
        Duration leaderboardTtl = ttlMinutes(cacheConfig.getLeaderboardTtlMinutes(), "app.cache.leaderboard-ttl-minutes");
        Duration activityTtl = ttlMinutes(cacheConfig.getActivityTtlMinutes(), "app.cache.activity-ttl-minutes");
        Duration statsTtl = ttlMinutes(cacheConfig.getStatsTtlMinutes(), "app.cache.stats-ttl-minutes");
        Duration graphTtl = ttlMinutes(cacheConfig.getGraphTtlMinutes(), "app.cache.graph-ttl-minutes");

        // Use secure type validator with whitelist
        PolymorphicTypeValidator typeValidator = BasicPolymorphicTypeValidator.builder()
            .allowIfBaseType("com.mosquito.project.domain")
            .allowIfBaseType("com.mosquito.project.dto")
            .allowIfBaseType("java.util")
            .allowIfBaseType("java.time")
            .allowIfBaseType("java.lang")
            .build();

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.activateDefaultTyping(
            typeValidator,
            ObjectMapper.DefaultTyping.NON_FINAL,
            JsonTypeInfo.As.PROPERTY
        );

        RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(leaderboardTtl)
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
                new GenericJackson2JsonRedisSerializer(objectMapper)
            ))
            .disableCachingNullValues()
            .prefixCacheNameWith("mosquito:v1:");

        Map<String, RedisCacheConfiguration> cacheConfigs = new HashMap<>();
        cacheConfigs.put("leaderboards", defaultConfig.entryTtl(leaderboardTtl));
        cacheConfigs.put("activities", defaultConfig.entryTtl(activityTtl));
        cacheConfigs.put("activity_stats", defaultConfig.entryTtl(statsTtl));
        cacheConfigs.put("activity_graph", defaultConfig.entryTtl(graphTtl));

        return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(defaultConfig)
            .withInitialCacheConfigurations(cacheConfigs)
            .build();
    }

    private Duration ttlMinutes(int minutes, String configKey) {
        if (minutes <= 0) {
            throw new IllegalStateException(configKey + " must be greater than 0");
        }
        if (minutes > 10080) { // 7 days max
            throw new IllegalStateException(configKey + " must not exceed 10080 minutes (7 days)");
        }
        return Duration.ofMinutes(minutes);
    }
}
