package cn.novalon.manage.gateway.filter;

import cn.novalon.manage.gateway.config.RateLimitConfig;
import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RateLimiterConfig;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import java.net.InetSocketAddress;
import java.time.Duration;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class RateLimitFilterTest {

    @Mock
    private RateLimiter globalRateLimiter;

    @Mock
    private RateLimiter ipRateLimiter;

    @Mock
    private RateLimiter userRateLimiter;

    @Mock
    private RateLimitConfig rateLimitConfig;

    @Mock
    private GatewayFilterChain chain;

    private RateLimitFilter rateLimitFilter;

    @BeforeEach
    void setUp() {
        lenient().when(rateLimitConfig.isRateLimitEnabled()).thenReturn(true);
        
        RateLimiterConfig config = RateLimiterConfig.custom()
                .limitForPeriod(100)
                .limitRefreshPeriod(Duration.ofSeconds(1))
                .timeoutDuration(Duration.ZERO)
                .build();

        lenient().when(globalRateLimiter.getRateLimiterConfig()).thenReturn(config);
        lenient().when(ipRateLimiter.getRateLimiterConfig()).thenReturn(config);
        lenient().when(userRateLimiter.getRateLimiterConfig()).thenReturn(config);

        rateLimitFilter = new RateLimitFilter(
                globalRateLimiter, 
                ipRateLimiter, 
                userRateLimiter, 
                rateLimitConfig);
    }

    @Test
    void testFilter_WhenRateLimitDisabled_ShouldPassThrough() {
        when(rateLimitConfig.isRateLimitEnabled()).thenReturn(false);
        when(chain.filter(any())).thenReturn(Mono.empty());

        MockServerHttpRequest request = MockServerHttpRequest
                .get("/api/test")
                .build();

        MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();

        StepVerifier.create(rateLimitFilter.filter(exchange, chain))
                .verifyComplete();

        verify(chain).filter(exchange);
        verify(globalRateLimiter, never()).acquirePermission();
    }

    @Test
    void testFilter_WhenGlobalRateLimitExceeded_ShouldReturn429() {
        when(globalRateLimiter.acquirePermission()).thenReturn(false);

        MockServerHttpRequest request = MockServerHttpRequest
                .get("/api/test")
                .remoteAddress(new InetSocketAddress("192.168.1.1", 12345))
                .build();

        MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();

        StepVerifier.create(rateLimitFilter.filter(exchange, chain))
                .verifyComplete();

        assertEquals(HttpStatus.TOO_MANY_REQUESTS, exchange.getResponse().getStatusCode());
        verify(chain, never()).filter(any());
    }

    @Test
    void testFilter_WhenAllRateLimitsPass_ShouldContinueChain() {
        when(globalRateLimiter.acquirePermission()).thenReturn(true);
        when(chain.filter(any())).thenReturn(Mono.empty());

        MockServerHttpRequest request = MockServerHttpRequest
                .get("/api/test")
                .header("X-User-Id", "user123")
                .remoteAddress(new InetSocketAddress("192.168.1.1", 12345))
                .build();

        MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();

        StepVerifier.create(rateLimitFilter.filter(exchange, chain))
                .verifyComplete();

        verify(chain).filter(exchange);
        verify(globalRateLimiter).acquirePermission();
    }

    @Test
    void testFilter_WithoutUserId_ShouldSkipUserRateLimit() {
        when(globalRateLimiter.acquirePermission()).thenReturn(true);
        when(chain.filter(any())).thenReturn(Mono.empty());

        MockServerHttpRequest request = MockServerHttpRequest
                .get("/api/test")
                .remoteAddress(new InetSocketAddress("192.168.1.1", 12345))
                .build();

        MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();

        StepVerifier.create(rateLimitFilter.filter(exchange, chain))
                .verifyComplete();

        verify(chain).filter(exchange);
    }

    @Test
    void testGetClientIp_FromXForwardedFor() {
        when(globalRateLimiter.acquirePermission()).thenReturn(true);
        when(chain.filter(any())).thenReturn(Mono.empty());

        MockServerHttpRequest request = MockServerHttpRequest
                .get("/api/test")
                .header("X-Forwarded-For", "10.0.0.1, 192.168.1.1")
                .build();

        MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();

        StepVerifier.create(rateLimitFilter.filter(exchange, chain))
                .verifyComplete();

        verify(chain).filter(exchange);
    }

    @Test
    void testGetClientIp_FromXRealIP() {
        when(globalRateLimiter.acquirePermission()).thenReturn(true);
        when(chain.filter(any())).thenReturn(Mono.empty());

        MockServerHttpRequest request = MockServerHttpRequest
                .get("/api/test")
                .header("X-Real-IP", "10.0.0.2")
                .build();

        MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();

        StepVerifier.create(rateLimitFilter.filter(exchange, chain))
                .verifyComplete();

        verify(chain).filter(exchange);
    }

    @Test
    void testGetClientIp_FromRemoteAddress() {
        when(globalRateLimiter.acquirePermission()).thenReturn(true);
        when(chain.filter(any())).thenReturn(Mono.empty());

        MockServerHttpRequest request = MockServerHttpRequest
                .get("/api/test")
                .remoteAddress(new InetSocketAddress("192.168.1.100", 12345))
                .build();

        MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();

        StepVerifier.create(rateLimitFilter.filter(exchange, chain))
                .verifyComplete();

        verify(chain).filter(exchange);
    }

    @Test
    void testRateLimitHeaders_WhenExceeded() {
        when(globalRateLimiter.acquirePermission()).thenReturn(false);

        MockServerHttpRequest request = MockServerHttpRequest
                .get("/api/test")
                .remoteAddress(new InetSocketAddress("192.168.1.1", 12345))
                .build();

        MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();

        StepVerifier.create(rateLimitFilter.filter(exchange, chain))
                .verifyComplete();

        ServerHttpResponse response = exchange.getResponse();
        HttpHeaders headers = response.getHeaders();

        assertTrue(headers.containsKey("X-RateLimit-Limit"));
        assertTrue(headers.containsKey("X-RateLimit-Remaining"));
        assertTrue(headers.containsKey("Retry-After"));
        assertTrue(headers.containsKey("X-RateLimit-Type"));
    }

    @Test
    void testCounters_WhenRequestsProcessed() {
        when(globalRateLimiter.acquirePermission()).thenReturn(true);
        when(chain.filter(any())).thenReturn(Mono.empty());

        MockServerHttpRequest request = MockServerHttpRequest
                .get("/api/test")
                .remoteAddress(new InetSocketAddress("192.168.1.1", 12345))
                .build();

        MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();

        StepVerifier.create(rateLimitFilter.filter(exchange, chain))
                .verifyComplete();

        assertEquals(1, rateLimitFilter.getTotalRequests());
        assertEquals(0, rateLimitFilter.getBlockedRequests());
    }

    @Test
    void testCounters_WhenRequestsBlocked() {
        when(globalRateLimiter.acquirePermission()).thenReturn(false);

        MockServerHttpRequest request = MockServerHttpRequest
                .get("/api/test")
                .remoteAddress(new InetSocketAddress("192.168.1.1", 12345))
                .build();

        MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();

        StepVerifier.create(rateLimitFilter.filter(exchange, chain))
                .verifyComplete();

        assertEquals(1, rateLimitFilter.getTotalRequests());
        assertEquals(1, rateLimitFilter.getBlockedRequests());
    }

    @Test
    void testResetCounters() {
        when(globalRateLimiter.acquirePermission()).thenReturn(true);
        when(chain.filter(any())).thenReturn(Mono.empty());

        MockServerHttpRequest request = MockServerHttpRequest
                .get("/api/test")
                .remoteAddress(new InetSocketAddress("192.168.1.1", 12345))
                .build();

        MockServerWebExchange exchange = MockServerWebExchange.builder(request).build();

        StepVerifier.create(rateLimitFilter.filter(exchange, chain))
                .verifyComplete();

        assertEquals(1, rateLimitFilter.getTotalRequests());

        rateLimitFilter.resetCounters();

        assertEquals(0, rateLimitFilter.getTotalRequests());
        assertEquals(0, rateLimitFilter.getBlockedRequests());
    }

    @Test
    void testGetOrder() {
        int order = rateLimitFilter.getOrder();
        assertEquals(Ordered.HIGHEST_PRECEDENCE + 100, order);
    }
}
