package org.jeecg.modules.redis;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.jeecg.common.modules.redis.config.RedisConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheAspectSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.CacheKeyPrefix;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

import static org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;

/**
 * Redis cache配置
 *
 * @author wfansh
 */
@Configuration
@EnableCaching
public class CacheConfig {

    public static final String PRIMARY_CACHE_MANAGER = "primaryCacheManager";

    public static final String TTL_CACHE_MANAGER = "ttlCacheManager";

    private static final int DEFAULT_TTL_HOURS = 5;

    /**
     * Jeecg默认Redis cache配置
     */
    @Autowired
    private RedisConfig redisConfig;

    /**
     * Workaround - 重新注册Jeecg默认的{@link CacheManager}，标识为Primary
     *
     * <p>当有多个{@link CacheManager} beans时，需标识出@Prinary
     *
     * <p>参考{@link RedisConfig#cacheManager(LettuceConnectionFactory)}方法
     *
     * <p>参考{@link CacheAspectSupport#setCacheManager(CacheManager)}方法
     */
    @Bean(name = PRIMARY_CACHE_MANAGER)
    @Primary
    public CacheManager primaryCacheManager(LettuceConnectionFactory connectionFactory) {
        return redisConfig.cacheManager(connectionFactory);
    }

    /**
     * 注册{@link CacheManager}，支持在{@link Cacheable#cacheNames()}中设置Redis TTL
     */
    @Bean(name = TTL_CACHE_MANAGER)
    public CacheManager ttlCacheManager(RedisConnectionFactory connectionFactory) {
        // 解决缓存对象转换异常问题
        JsonMapper jsonMapper =
                JsonMapper.builder()
                        .visibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY)
                        .activateDefaultTyping(
                                LaissezFaireSubTypeValidator.instance,
                                ObjectMapper.DefaultTyping.NON_FINAL)
                        .configure(MapperFeature.USE_ANNOTATIONS, false) // 忽略@JsonIgnore注解，缓存所有字段
                        .build();

        // 配置缓存默认TTL，以及键值序列化方式 - 解决乱码的问题
        RedisCacheConfiguration cacheConfiguration =
                RedisCacheConfiguration.defaultCacheConfig()
                        .computePrefixWith(CacheKeyPrefix.simple())
                        .entryTtl(Duration.ofHours(DEFAULT_TTL_HOURS))
                        .serializeKeysWith(
                                SerializationPair.fromSerializer(new StringRedisSerializer()))
                        .serializeValuesWith(
                                SerializationPair.fromSerializer(
                                        new Jackson2JsonRedisSerializer(jsonMapper, Object.class)))
                        .disableCachingNullValues();

        // 非锁写入方式
        RedisCacheWriter cacheWriter =
                RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);

        return new TTLCacheManager(cacheWriter, cacheConfiguration);
    }
}
