package com.mosquito.project.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;

import java.time.Duration;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.BDDMockito.given;

@ExtendWith(MockitoExtension.class)
class CacheConfigTest {

    @Mock
    private RedisConnectionFactory connectionFactory;

    @Test
    void shouldCreateCacheManager_whenValidConfigProvided() {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setLeaderboardTtlMinutes(5);
        appConfig.getCache().setActivityTtlMinutes(1);
        appConfig.getCache().setStatsTtlMinutes(2);
        appConfig.getCache().setGraphTtlMinutes(10);

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        assertThat(cacheConfig).isNotNull();
    }

    @Test
    void shouldUseDefaultTtlValues_whenConfigNotModified() {
        AppConfig appConfig = new AppConfig();
        AppConfig.CacheConfig cache = appConfig.getCache();

        assertThat(cache.getLeaderboardTtlMinutes()).isEqualTo(5);
        assertThat(cache.getActivityTtlMinutes()).isEqualTo(1);
        assertThat(cache.getStatsTtlMinutes()).isEqualTo(2);
        assertThat(cache.getGraphTtlMinutes()).isEqualTo(10);
    }

    @Test
    void shouldReturnCorrectTtl_forLeaderboardsCache() {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setLeaderboardTtlMinutes(15);

        assertThat(appConfig.getCache().getLeaderboardTtlMinutes()).isEqualTo(15);
    }

    @Test
    void shouldReturnCorrectTtl_forActivitiesCache() {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setActivityTtlMinutes(3);

        assertThat(appConfig.getCache().getActivityTtlMinutes()).isEqualTo(3);
    }

    @Test
    void shouldReturnCorrectTtl_forActivityStatsCache() {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setStatsTtlMinutes(5);

        assertThat(appConfig.getCache().getStatsTtlMinutes()).isEqualTo(5);
    }

    @Test
    void shouldReturnCorrectTtl_forActivityGraphCache() {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setGraphTtlMinutes(30);

        assertThat(appConfig.getCache().getGraphTtlMinutes()).isEqualTo(30);
    }

    @Test
    void shouldThrowIllegalStateException_whenTtlIsZero() {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setLeaderboardTtlMinutes(0);

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        assertThatThrownBy(() -> cacheConfig.cacheManager(connectionFactory))
            .isInstanceOf(IllegalStateException.class)
            .hasMessageContaining("app.cache.leaderboard-ttl-minutes must be greater than 0");
    }

    @Test
    void shouldThrowIllegalStateException_whenTtlIsNegative() {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setActivityTtlMinutes(-1);

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        assertThatThrownBy(() -> cacheConfig.cacheManager(connectionFactory))
            .isInstanceOf(IllegalStateException.class)
            .hasMessageContaining("app.cache.activity-ttl-minutes must be greater than 0");
    }

    @ParameterizedTest
    @ValueSource(ints = {0, -1, -5, -100, -1000})
    void shouldThrowIllegalStateException_forAnyNonPositiveLeaderboardTtl(int invalidTtl) {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setLeaderboardTtlMinutes(invalidTtl);

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        assertThatThrownBy(() -> cacheConfig.cacheManager(connectionFactory))
            .isInstanceOf(IllegalStateException.class)
            .hasMessageContaining("must be greater than 0");
    }

    @ParameterizedTest
    @ValueSource(ints = {0, -1, -10, -9999})
    void shouldThrowIllegalStateException_forAnyNonPositiveActivityTtl(int invalidTtl) {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setActivityTtlMinutes(invalidTtl);

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        assertThatThrownBy(() -> cacheConfig.cacheManager(connectionFactory))
            .isInstanceOf(IllegalStateException.class)
            .hasMessageContaining("must be greater than 0");
    }

    @ParameterizedTest
    @ValueSource(ints = {0, -1, -50, -5000})
    void shouldThrowIllegalStateException_forAnyNonPositiveStatsTtl(int invalidTtl) {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setStatsTtlMinutes(invalidTtl);

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        assertThatThrownBy(() -> cacheConfig.cacheManager(connectionFactory))
            .isInstanceOf(IllegalStateException.class)
            .hasMessageContaining("must be greater than 0");
    }

    @ParameterizedTest
    @ValueSource(ints = {0, -1, -20, -99999})
    void shouldThrowIllegalStateException_forAnyNonPositiveGraphTtl(int invalidTtl) {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setGraphTtlMinutes(invalidTtl);

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        assertThatThrownBy(() -> cacheConfig.cacheManager(connectionFactory))
            .isInstanceOf(IllegalStateException.class)
            .hasMessageContaining("must be greater than 0");
    }

    @ParameterizedTest
    @ValueSource(ints = {1, 5, 10, 60, 1440, 525600})
    void shouldAcceptValidPositiveTtlValues_forLeaderboardCache(int validTtl) {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setLeaderboardTtlMinutes(validTtl);

        assertThat(appConfig.getCache().getLeaderboardTtlMinutes()).isEqualTo(validTtl);
    }

    @ParameterizedTest
    @ValueSource(ints = {1, 2, 5, 30, 120, 2880})
    void shouldAcceptValidPositiveTtlValues_forActivityCache(int validTtl) {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setActivityTtlMinutes(validTtl);

        assertThat(appConfig.getCache().getActivityTtlMinutes()).isEqualTo(validTtl);
    }

    @Test
    void shouldAcceptVeryLargeTtlValue() {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setLeaderboardTtlMinutes(Integer.MAX_VALUE);

        assertThat(appConfig.getCache().getLeaderboardTtlMinutes())
            .isEqualTo(Integer.MAX_VALUE);
    }

    @Test
    void shouldVerifyObjectMapperConfiguration_forRedisSerializer() {
        AppConfig appConfig = new AppConfig();
        CacheConfig cacheConfig = new CacheConfig(appConfig);

        assertThat(cacheConfig).isNotNull();
    }

    @Test
    void shouldExposeAllCacheNames_inConfiguration() {
        AppConfig appConfig = new AppConfig();

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        assertThat(cacheConfig).isNotNull();
    }

    @Test
    void shouldThrowExceptionWithCorrectConfigKey_forActivityTtl() {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setActivityTtlMinutes(0);

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        assertThatThrownBy(() -> cacheConfig.cacheManager(connectionFactory))
            .isInstanceOf(IllegalStateException.class)
            .hasMessage("app.cache.activity-ttl-minutes must be greater than 0");
    }

    @Test
    void shouldThrowExceptionWithCorrectConfigKey_forStatsTtl() {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setStatsTtlMinutes(-5);

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        assertThatThrownBy(() -> cacheConfig.cacheManager(connectionFactory))
            .isInstanceOf(IllegalStateException.class)
            .hasMessage("app.cache.stats-ttl-minutes must be greater than 0");
    }

    @Test
    void shouldThrowExceptionWithCorrectConfigKey_forGraphTtl() {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setGraphTtlMinutes(-1);

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        assertThatThrownBy(() -> cacheConfig.cacheManager(connectionFactory))
            .isInstanceOf(IllegalStateException.class)
            .hasMessage("app.cache.graph-ttl-minutes must be greater than 0");
    }

    @Test
    void shouldThrowExceptionWithCorrectConfigKey_forLeaderboardTtl() {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setLeaderboardTtlMinutes(0);

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        assertThatThrownBy(() -> cacheConfig.cacheManager(connectionFactory))
            .isInstanceOf(IllegalStateException.class)
            .hasMessage("app.cache.leaderboard-ttl-minutes must be greater than 0");
    }

    @Test
    void shouldVerifyMultipleZeroTtlConfigurationsThrowException() {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setLeaderboardTtlMinutes(0);
        appConfig.getCache().setActivityTtlMinutes(5);
        appConfig.getCache().setStatsTtlMinutes(5);
        appConfig.getCache().setGraphTtlMinutes(5);

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        assertThatThrownBy(() -> cacheConfig.cacheManager(connectionFactory))
            .isInstanceOf(IllegalStateException.class)
            .hasMessageContaining("leaderboard-ttl-minutes");
    }

    @Test
    void shouldVerifyAllCachesUseConsistentPrefixConfiguration() {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setLeaderboardTtlMinutes(5);
        appConfig.getCache().setActivityTtlMinutes(1);
        appConfig.getCache().setStatsTtlMinutes(2);
        appConfig.getCache().setGraphTtlMinutes(10);

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        assertThat(cacheConfig).isNotNull();
    }

    @Test
    void shouldVerifyMinimumValidTtlIsOne() {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setLeaderboardTtlMinutes(1);
        appConfig.getCache().setActivityTtlMinutes(1);
        appConfig.getCache().setStatsTtlMinutes(1);
        appConfig.getCache().setGraphTtlMinutes(1);

        assertThat(appConfig.getCache().getLeaderboardTtlMinutes()).isEqualTo(1);
        assertThat(appConfig.getCache().getActivityTtlMinutes()).isEqualTo(1);
        assertThat(appConfig.getCache().getStatsTtlMinutes()).isEqualTo(1);
        assertThat(appConfig.getCache().getGraphTtlMinutes()).isEqualTo(1);
    }

    @Test
    void shouldCreateCacheManagerBean_whenValidConfigurationProvided() {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setLeaderboardTtlMinutes(5);
        appConfig.getCache().setActivityTtlMinutes(1);
        appConfig.getCache().setStatsTtlMinutes(2);
        appConfig.getCache().setGraphTtlMinutes(10);

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        RedisCacheManager cacheManager = cacheConfig.cacheManager(connectionFactory);

        assertThat(cacheManager).isNotNull();
        assertThat(cacheManager).isInstanceOf(RedisCacheManager.class);
    }

    @Test
    void shouldConfigureAllFourCaches_withDifferentTtlValues() {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setLeaderboardTtlMinutes(60);
        appConfig.getCache().setActivityTtlMinutes(5);
        appConfig.getCache().setStatsTtlMinutes(15);
        appConfig.getCache().setGraphTtlMinutes(120);

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        RedisCacheManager cacheManager = cacheConfig.cacheManager(connectionFactory);

        assertThat(cacheManager).isNotNull();
    }

    @Test
    void shouldVerifyCacheConfigurationsMapIsCreated() {
        AppConfig appConfig = new AppConfig();

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        RedisCacheManager cacheManager = cacheConfig.cacheManager(connectionFactory);

        assertThat(cacheManager).isNotNull();
        assertThat(cacheManager).isInstanceOf(RedisCacheManager.class);
    }

    @Test
    void shouldVerifyConstructorInjection_worksCorrectly() {
        AppConfig appConfig = new AppConfig();
        CacheConfig cacheConfig = new CacheConfig(appConfig);

        assertThat(cacheConfig).isNotNull();
    }

    @Test
    void shouldUseDefaultTtlValues_whenCacheManagerCreated() {
        AppConfig appConfig = new AppConfig();

        CacheConfig cacheConfig = new CacheConfig(appConfig);
        RedisCacheManager cacheManager = cacheConfig.cacheManager(connectionFactory);

        assertThat(cacheManager).isNotNull();
    }

    @Test
    void shouldVerifyCacheManagerCreation_withAllDefaultTtls() {
        AppConfig appConfig = new AppConfig();

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        RedisCacheManager manager = cacheConfig.cacheManager(connectionFactory);

        assertThat(manager).isNotNull();
        assertThat(manager).isInstanceOf(RedisCacheManager.class);
    }

    @Test
    void shouldVerifyAllCacheNamesExist_whenManagerCreated() {
        AppConfig appConfig = new AppConfig();
        CacheConfig cacheConfig = new CacheConfig(appConfig);

        RedisCacheManager manager = cacheConfig.cacheManager(connectionFactory);

        assertThat(manager).isNotNull();
        assertThat(manager).isInstanceOf(RedisCacheManager.class);
    }

    @Test
    void shouldCreateCacheManager_withCustomLeaderboardTtl() {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setLeaderboardTtlMinutes(30);
        appConfig.getCache().setActivityTtlMinutes(1);
        appConfig.getCache().setStatsTtlMinutes(2);
        appConfig.getCache().setGraphTtlMinutes(10);

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        RedisCacheManager manager = cacheConfig.cacheManager(connectionFactory);

        assertThat(manager).isNotNull();
    }

    @Test
    void shouldCreateCacheManager_withAllCachesEnabled() {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setLeaderboardTtlMinutes(5);
        appConfig.getCache().setActivityTtlMinutes(1);
        appConfig.getCache().setStatsTtlMinutes(2);
        appConfig.getCache().setGraphTtlMinutes(10);

        CacheConfig cacheConfig = new CacheConfig(appConfig);
        RedisCacheManager manager = cacheConfig.cacheManager(connectionFactory);

        assertThat(manager).isNotNull();
        assertThat(manager).isInstanceOf(RedisCacheManager.class);
    }

    @Test
    void shouldVerifyCacheConfigImplementsCorrectPattern() {
        AppConfig appConfig = new AppConfig();
        CacheConfig cacheConfig = new CacheConfig(appConfig);

        RedisCacheManager manager = cacheConfig.cacheManager(connectionFactory);

        assertThat(manager).isNotNull();
    }

    @Test
    void shouldVerifyRedisCacheManagerBuilderIsUsed() {
        AppConfig appConfig = new AppConfig();
        CacheConfig cacheConfig = new CacheConfig(appConfig);

        RedisCacheManager manager = cacheConfig.cacheManager(connectionFactory);

        assertThat(manager).isInstanceOf(RedisCacheManager.class);
    }

    @Test
    void shouldVerifyDefaultCacheConfigHasTtl() {
        AppConfig appConfig = new AppConfig();
        CacheConfig cacheConfig = new CacheConfig(appConfig);

        RedisCacheManager manager = cacheConfig.cacheManager(connectionFactory);

        assertThat(manager).isNotNull();
    }

    @Test
    void shouldVerifyCacheManager_withVerySmallValidTtl() {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setLeaderboardTtlMinutes(1);
        appConfig.getCache().setActivityTtlMinutes(1);
        appConfig.getCache().setStatsTtlMinutes(1);
        appConfig.getCache().setGraphTtlMinutes(1);

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        RedisCacheManager manager = cacheConfig.cacheManager(connectionFactory);

        assertThat(manager).isNotNull();
    }

    @Test
    void shouldVerifyCacheManager_withMaximumAllowedTtl() {
        AppConfig appConfig = new AppConfig();
        // 最大允许值为 10080 分钟（7天）
        appConfig.getCache().setLeaderboardTtlMinutes(10080);
        appConfig.getCache().setActivityTtlMinutes(10080);
        appConfig.getCache().setStatsTtlMinutes(10080);
        appConfig.getCache().setGraphTtlMinutes(10080);

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        RedisCacheManager manager = cacheConfig.cacheManager(connectionFactory);

        assertThat(manager).isNotNull();
    }

    @Test
    void shouldThrowException_whenTtlExceedsMaximum() {
        AppConfig appConfig = new AppConfig();
        appConfig.getCache().setLeaderboardTtlMinutes(10081); // 超过最大值

        CacheConfig cacheConfig = new CacheConfig(appConfig);

        assertThatThrownBy(() -> cacheConfig.cacheManager(connectionFactory))
            .isInstanceOf(IllegalStateException.class)
            .hasMessageContaining("must not exceed 10080 minutes");
    }

    @Test
    void shouldVerifyCacheConfigurationsAreUnique() {
        AppConfig appConfig = new AppConfig();
        CacheConfig cacheConfig = new CacheConfig(appConfig);

        RedisCacheManager manager = cacheConfig.cacheManager(connectionFactory);

        assertThat(manager).isNotNull();
        assertThat(manager).isInstanceOf(RedisCacheManager.class);
    }

    @Test
    void shouldExposeAllCacheNames_afterManagerCreation() {
        AppConfig appConfig = new AppConfig();
        CacheConfig cacheConfig = new CacheConfig(appConfig);

        RedisCacheManager manager = cacheConfig.cacheManager(connectionFactory);

        assertThat(manager).isNotNull();
        assertThat(manager).isInstanceOf(RedisCacheManager.class);
    }
}
