package cn.novalon.manage.gateway.filter;

import cn.novalon.manage.gateway.util.JwtUtil;
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.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import static org.mockito.ArgumentCaptor.forClass;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class GatewayJwtAuthenticationFilterTest {

    @Mock
    private JwtUtil jwtUtil;

    @Mock
    private GatewayFilterChain chain;

    private JwtAuthenticationFilter filter;
    private ServerWebExchange exchange;

    @BeforeEach
    void setUp() {
        filter = new JwtAuthenticationFilter(jwtUtil);
    }

    @Test
    void testPublicPath_AllowAccess() {
        MockServerHttpRequest request = MockServerHttpRequest.get("/api/auth/login").build();
        exchange = MockServerWebExchange.from(request);

        when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());

        Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
                .filter(exchange, chain);

        StepVerifier.create(result)
                .verifyComplete();

        verify(chain).filter(any(ServerWebExchange.class));
        verify(jwtUtil, never()).validateToken(anyString());
    }

    @Test
    void testPublicPath_Register() {
        MockServerHttpRequest request = MockServerHttpRequest.post("/api/auth/register").build();
        exchange = MockServerWebExchange.from(request);

        when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());

        Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
                .filter(exchange, chain);

        StepVerifier.create(result)
                .verifyComplete();

        verify(chain).filter(any(ServerWebExchange.class));
        verify(jwtUtil, never()).validateToken(anyString());
    }

    @Test
    void testPublicPath_ActuatorHealth() {
        MockServerHttpRequest request = MockServerHttpRequest.get("/actuator/health").build();
        exchange = MockServerWebExchange.from(request);

        when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());

        Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
                .filter(exchange, chain);

        StepVerifier.create(result)
                .verifyComplete();

        verify(chain).filter(any(ServerWebExchange.class));
        verify(jwtUtil, never()).validateToken(anyString());
    }

    @Test
    void testPublicPath_ActuatorInfo() {
        MockServerHttpRequest request = MockServerHttpRequest.get("/actuator/info").build();
        exchange = MockServerWebExchange.from(request);

        when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());

        Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
                .filter(exchange, chain);

        StepVerifier.create(result)
                .verifyComplete();

        verify(chain).filter(any(ServerWebExchange.class));
        verify(jwtUtil, never()).validateToken(anyString());
    }

    @Test
    void testProtectedPath_NoAuthHeader() {
        MockServerHttpRequest request = MockServerHttpRequest.get("/api/users").build();
        exchange = MockServerWebExchange.from(request);

        Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
                .filter(exchange, chain);

        StepVerifier.create(result)
                .verifyComplete();

        assert exchange.getResponse().getStatusCode() == HttpStatus.UNAUTHORIZED;
        verify(chain, never()).filter(any(ServerWebExchange.class));
        verify(jwtUtil, never()).validateToken(anyString());
    }

    @Test
    void testProtectedPath_InvalidAuthHeader() {
        MockServerHttpRequest request = MockServerHttpRequest.get("/api/users")
                .header(HttpHeaders.AUTHORIZATION, "InvalidToken")
                .build();
        exchange = MockServerWebExchange.from(request);

        Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
                .filter(exchange, chain);

        StepVerifier.create(result)
                .verifyComplete();

        assert exchange.getResponse().getStatusCode() == HttpStatus.UNAUTHORIZED;
        verify(chain, never()).filter(any(ServerWebExchange.class));
        verify(jwtUtil, never()).validateToken(anyString());
    }

    @Test
    void testProtectedPath_WithBearerPrefix() {
        String validToken = "valid.jwt.token";
        MockServerHttpRequest request = MockServerHttpRequest.get("/api/users")
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + validToken)
                .build();
        exchange = MockServerWebExchange.from(request);

        when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
        when(jwtUtil.validateToken(validToken)).thenReturn(true);
        when(jwtUtil.isTokenExpired(validToken)).thenReturn(false);
        when(jwtUtil.getUsernameFromToken(validToken)).thenReturn("testuser");
        when(jwtUtil.getUserIdFromToken(validToken)).thenReturn(1L);

        Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
                .filter(exchange, chain);

        StepVerifier.create(result)
                .verifyComplete();

        verify(jwtUtil).validateToken(validToken);
        verify(jwtUtil).isTokenExpired(validToken);
        verify(jwtUtil).getUsernameFromToken(validToken);
        verify(jwtUtil).getUserIdFromToken(validToken);
        verify(chain).filter(any(ServerWebExchange.class));
    }

    @Test
    void testProtectedPath_InvalidToken() {
        String invalidToken = "invalid.jwt.token";
        MockServerHttpRequest request = MockServerHttpRequest.get("/api/users")
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + invalidToken)
                .build();
        exchange = MockServerWebExchange.from(request);

        when(jwtUtil.validateToken(invalidToken)).thenReturn(false);

        Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
                .filter(exchange, chain);

        StepVerifier.create(result)
                .verifyComplete();

        assert exchange.getResponse().getStatusCode() == HttpStatus.UNAUTHORIZED;
        verify(jwtUtil).validateToken(invalidToken);
        verify(jwtUtil, never()).isTokenExpired(anyString());
        verify(chain, never()).filter(any(ServerWebExchange.class));
    }

    @Test
    void testProtectedPath_ExpiredToken() {
        String expiredToken = "expired.jwt.token";
        MockServerHttpRequest request = MockServerHttpRequest.get("/api/users")
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + expiredToken)
                .build();
        exchange = MockServerWebExchange.from(request);

        when(jwtUtil.validateToken(expiredToken)).thenReturn(true);
        when(jwtUtil.isTokenExpired(expiredToken)).thenReturn(true);

        Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
                .filter(exchange, chain);

        StepVerifier.create(result)
                .verifyComplete();

        assert exchange.getResponse().getStatusCode() == HttpStatus.UNAUTHORIZED;
        verify(jwtUtil).validateToken(expiredToken);
        verify(jwtUtil).isTokenExpired(expiredToken);
        verify(chain, never()).filter(any(ServerWebExchange.class));
    }

    @Test
    void testProtectedPath_ValidToken() {
        String validToken = "valid.jwt.token";
        MockServerHttpRequest request = MockServerHttpRequest.get("/api/users/1")
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + validToken)
                .build();
        exchange = MockServerWebExchange.from(request);

        when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
        when(jwtUtil.validateToken(validToken)).thenReturn(true);
        when(jwtUtil.isTokenExpired(validToken)).thenReturn(false);
        when(jwtUtil.getUsernameFromToken(validToken)).thenReturn("testuser");
        when(jwtUtil.getUserIdFromToken(validToken)).thenReturn(1L);

        Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
                .filter(exchange, chain);

        StepVerifier.create(result)
                .verifyComplete();

        verify(jwtUtil).validateToken(validToken);
        verify(jwtUtil).isTokenExpired(validToken);
        verify(jwtUtil).getUsernameFromToken(validToken);
        verify(jwtUtil).getUserIdFromToken(validToken);
        verify(chain).filter(any(ServerWebExchange.class));
    }

    @Test
    void testHeadersAdded_ValidToken() {
        String validToken = "valid.jwt.token";
        MockServerHttpRequest request = MockServerHttpRequest.get("/api/users")
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + validToken)
                .build();
        exchange = MockServerWebExchange.from(request);

        when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
        when(jwtUtil.validateToken(validToken)).thenReturn(true);
        when(jwtUtil.isTokenExpired(validToken)).thenReturn(false);
        when(jwtUtil.getUsernameFromToken(validToken)).thenReturn("testuser");
        when(jwtUtil.getUserIdFromToken(validToken)).thenReturn(1L);

        Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
                .filter(exchange, chain);

        StepVerifier.create(result)
                .verifyComplete();

        var exchangeCaptor = forClass(ServerWebExchange.class);
        verify(chain).filter(exchangeCaptor.capture());
        ServerHttpRequest modifiedRequest = exchangeCaptor.getValue().getRequest();
        assert modifiedRequest.getHeaders().getFirst("X-User-Id").equals("1");
        assert modifiedRequest.getHeaders().getFirst("X-Username").equals("testuser");
    }

    @Test
    void testMixedPath_AuthPath() {
        MockServerHttpRequest request = MockServerHttpRequest.get("/api/auth/logout").build();
        exchange = MockServerWebExchange.from(request);

        when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());

        Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
                .filter(exchange, chain);

        StepVerifier.create(result)
                .verifyComplete();

        verify(chain).filter(any(ServerWebExchange.class));
        verify(jwtUtil, never()).validateToken(anyString());
    }

    @Test
    void testActuatorPath_Metrics() {
        String validToken = "valid.jwt.token";
        MockServerHttpRequest request = MockServerHttpRequest.get("/actuator/metrics")
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + validToken)
                .build();
        exchange = MockServerWebExchange.from(request);

        when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
        when(jwtUtil.validateToken(validToken)).thenReturn(true);
        when(jwtUtil.isTokenExpired(validToken)).thenReturn(false);
        when(jwtUtil.getUsernameFromToken(validToken)).thenReturn("testuser");
        when(jwtUtil.getUserIdFromToken(validToken)).thenReturn(1L);

        Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
                .filter(exchange, chain);

        StepVerifier.create(result)
                .verifyComplete();

        verify(jwtUtil).validateToken(validToken);
        verify(jwtUtil).isTokenExpired(validToken);
        verify(jwtUtil).getUsernameFromToken(validToken);
        verify(jwtUtil).getUserIdFromToken(validToken);
        verify(chain).filter(any(ServerWebExchange.class));
    }
}
