package com.mosquito.project.web;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.*;

@DisplayName("速率限制拦截器测试")
class RateLimitInterceptorTest {

    private final String API_KEY = "test-api-key-123456";
    private final int PER_MINUTE_LIMIT = 100;

    @Test
    @DisplayName("缺少API Key时应拒绝请求并返回401")
    void shouldRejectRequest_whenMissingApiKey() {
        // Given
        Environment environment = mock(Environment.class);
        given(environment.getActiveProfiles()).willReturn(new String[]{"dev"});
        given(environment.getProperty("app.rate-limit.per-minute", "100")).willReturn("100");
        
        RateLimitInterceptor interceptor = new RateLimitInterceptor(environment, null);
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();

        // When
        boolean result = interceptor.preHandle(request, response, new Object());

        // Then
        assertThat(result).isFalse();
        assertThat(response.getStatus()).isEqualTo(401);
    }

    @Test
    @DisplayName("缺少API Key且为空字符串时应拒绝请求")
    void shouldRejectRequest_whenEmptyApiKey() {
        // Given
        Environment environment = mock(Environment.class);
        given(environment.getActiveProfiles()).willReturn(new String[]{"dev"});
        given(environment.getProperty("app.rate-limit.per-minute", "100")).willReturn("100");
        
        RateLimitInterceptor interceptor = new RateLimitInterceptor(environment, null);
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.addHeader("X-API-Key", "   ");
        MockHttpServletResponse response = new MockHttpServletResponse();

        // When
        boolean result = interceptor.preHandle(request, response, new Object());

        // Then
        assertThat(result).isFalse();
        assertThat(response.getStatus()).isEqualTo(401);
    }

    @Test
    @DisplayName("Redis模式下首次请求应允许并设置计数为1")
    void shouldAllowRequestAndSetCountToOne_whenFirstRequestWithRedis() {
        // Given
        Environment environment = mock(Environment.class);
        StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class);
        ValueOperations<String, String> valueOperations = mock(ValueOperations.class);
        
        given(environment.getActiveProfiles()).willReturn(new String[]{"dev"});
        given(environment.getProperty("app.rate-limit.per-minute", "100")).willReturn("100");
        given(redisTemplate.opsForValue()).willReturn(valueOperations);
        given(valueOperations.increment(anyString())).willReturn(1L);
        given(redisTemplate.expire(anyString(), any())).willReturn(true);

        RateLimitInterceptor interceptor = new RateLimitInterceptor(environment, redisTemplate);
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.addHeader("X-API-Key", API_KEY);
        MockHttpServletResponse response = new MockHttpServletResponse();

        // When
        boolean result = interceptor.preHandle(request, response, new Object());

        // Then
        assertThat(result).isTrue();
        assertThat(response.getStatus()).isEqualTo(200);
        assertThat(response.getHeader("X-RateLimit-Limit")).isEqualTo("100");
        assertThat(response.getHeader("X-RateLimit-Remaining")).isEqualTo("99");
    }

    @Test
    @DisplayName("Redis模式下超过限制应拒绝请求并返回429")
    void shouldRejectRequestWith429_whenRateLimitExceededWithRedis() {
        // Given
        Environment environment = mock(Environment.class);
        StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class);
        ValueOperations<String, String> valueOperations = mock(ValueOperations.class);
        
        given(environment.getActiveProfiles()).willReturn(new String[]{"dev"});
        given(environment.getProperty("app.rate-limit.per-minute", "100")).willReturn("100");
        given(redisTemplate.opsForValue()).willReturn(valueOperations);
        given(valueOperations.increment(anyString())).willReturn(101L);

        RateLimitInterceptor interceptor = new RateLimitInterceptor(environment, redisTemplate);
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.addHeader("X-API-Key", API_KEY);
        MockHttpServletResponse response = new MockHttpServletResponse();

        // When
        boolean result = interceptor.preHandle(request, response, new Object());

        // Then
        assertThat(result).isFalse();
        assertThat(response.getStatus()).isEqualTo(429);
        assertThat(response.getHeader("Retry-After")).isEqualTo("60");
        assertThat(response.getHeader("X-RateLimit-Remaining")).isEqualTo("0");
    }

    @Test
    @DisplayName("Redis模式下Redis异常应返回503")
    void shouldReturn503_whenRedisException() {
        // Given
        Environment environment = mock(Environment.class);
        StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class);
        ValueOperations<String, String> valueOperations = mock(ValueOperations.class);
        
        given(environment.getActiveProfiles()).willReturn(new String[]{"dev"});
        given(environment.getProperty("app.rate-limit.per-minute", "100")).willReturn("100");
        given(redisTemplate.opsForValue()).willReturn(valueOperations);
        given(valueOperations.increment(anyString())).willThrow(new RuntimeException("Redis connection failed"));

        RateLimitInterceptor interceptor = new RateLimitInterceptor(environment, redisTemplate);
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.addHeader("X-API-Key", API_KEY);
        MockHttpServletResponse response = new MockHttpServletResponse();

        // When
        boolean result = interceptor.preHandle(request, response, new Object());

        // Then
        assertThat(result).isFalse();
        assertThat(response.getStatus()).isEqualTo(503);
        assertThat(response.getHeader("Retry-After")).isEqualTo("5");
    }

    @Test
    @DisplayName("本地内存模式下请求应正常计数")
    void shouldCountRequests_whenUsingLocalMemory() {
        // Given
        Environment environment = mock(Environment.class);
        given(environment.getActiveProfiles()).willReturn(new String[]{"dev"});
        given(environment.getProperty("app.rate-limit.per-minute", "100")).willReturn("100");

        RateLimitInterceptor interceptor = new RateLimitInterceptor(environment, null);
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.addHeader("X-API-Key", API_KEY);

        // When - 发送3次请求
        for (int i = 0; i < 3; i++) {
            MockHttpServletResponse response = new MockHttpServletResponse();
            boolean result = interceptor.preHandle(request, response, new Object());
            assertThat(result).isTrue();
            assertThat(response.getHeader("X-RateLimit-Remaining")).isEqualTo(String.valueOf(99 - i));
        }
    }

    @Test
    @DisplayName("生产模式下无Redis应抛出异常")
    void shouldThrowException_whenProductionModeWithoutRedis() {
        // Given
        Environment environment = mock(Environment.class);
        given(environment.getActiveProfiles()).willReturn(new String[]{"prod"});
        given(environment.getProperty("app.rate-limit.per-minute", "100")).willReturn("100");

        // Then
        assertThatThrownBy(() -> new RateLimitInterceptor(environment, null))
                .isInstanceOf(IllegalStateException.class)
                .hasMessageContaining("Production mode requires Redis");
    }

    @Test
    @DisplayName("生产模式下Redis可用应正常工作")
    void shouldWork_whenProductionModeWithRedis() {
        // Given
        Environment environment = mock(Environment.class);
        StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class);
        
        given(environment.getActiveProfiles()).willReturn(new String[]{"prod"});
        given(environment.getProperty("app.rate-limit.per-minute", "100")).willReturn("100");

        // When & Then - 不应抛出异常
        RateLimitInterceptor interceptor = new RateLimitInterceptor(environment, redisTemplate);
        assertThat(interceptor).isNotNull();
    }

    @Test
    @DisplayName("不同API Key应独立计数")
    void shouldCountIndependently_forDifferentApiKeys() {
        // Given
        Environment environment = mock(Environment.class);
        given(environment.getActiveProfiles()).willReturn(new String[]{"dev"});
        given(environment.getProperty("app.rate-limit.per-minute", "100")).willReturn("100");

        RateLimitInterceptor interceptor = new RateLimitInterceptor(environment, null);
        String apiKey1 = "key-1-abcdef";
        String apiKey2 = "key-2-ghijkl";

        // When
        MockHttpServletRequest request1 = new MockHttpServletRequest();
        request1.addHeader("X-API-Key", apiKey1);
        MockHttpServletResponse response1 = new MockHttpServletResponse();
        interceptor.preHandle(request1, response1, new Object());

        MockHttpServletRequest request2 = new MockHttpServletRequest();
        request2.addHeader("X-API-Key", apiKey2);
        MockHttpServletResponse response2 = new MockHttpServletResponse();
        interceptor.preHandle(request2, response2, new Object());

        // Then
        assertThat(response1.getHeader("X-RateLimit-Remaining")).isEqualTo("99");
        assertThat(response2.getHeader("X-RateLimit-Remaining")).isEqualTo("99");
    }

    @Test
    @DisplayName("短API Key应使用整个Key作为前缀")
    void shouldUseShortKey_whenApiKeyIsShort() {
        // Given
        Environment environment = mock(Environment.class);
        StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class);
        ValueOperations<String, String> valueOperations = mock(ValueOperations.class);
        
        given(environment.getActiveProfiles()).willReturn(new String[]{"dev"});
        given(environment.getProperty("app.rate-limit.per-minute", "100")).willReturn("100");
        given(redisTemplate.opsForValue()).willReturn(valueOperations);
        given(valueOperations.increment(anyString())).willReturn(1L);
        given(redisTemplate.expire(anyString(), any())).willReturn(true);

        RateLimitInterceptor interceptor = new RateLimitInterceptor(environment, redisTemplate);
        String shortKey = "short";

        // When
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.addHeader("X-API-Key", shortKey);
        MockHttpServletResponse response = new MockHttpServletResponse();
        boolean result = interceptor.preHandle(request, response, new Object());

        // Then
        assertThat(result).isTrue();
    }
}
