package cn.novalon.gym.manage.gateway.integration;

import cn.novalon.gym.manage.gateway.filter.RbacAuthorizationFilter;
import cn.novalon.gym.manage.gateway.service.PermissionService;
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.HttpStatus;
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.ArgumentMatchers.*;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class RbacIntegrationTest {

    @Mock
    private PermissionService permissionService;

    @Mock
    private GatewayFilterChain chain;

    private RbacAuthorizationFilter filter;

    @BeforeEach
    void setUp() {
        filter = new RbacAuthorizationFilter(permissionService);
    }

    @Test
    void testEndToEnd_AdminUserFullAccess() {
        Long adminUserId = 1L;
        String adminPath = "/api/admin/users";
        String adminMethod = "GET";

        when(permissionService.hasPermission(eq(adminUserId), eq(adminPath), eq(adminMethod))).thenReturn(true);
        when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());

        MockServerHttpRequest request = MockServerHttpRequest.get(adminPath)
                .header("X-User-Id", adminUserId.toString())
                .build();
        ServerWebExchange exchange = MockServerWebExchange.from(request);

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

        StepVerifier.create(result)
                .verifyComplete();

        assert exchange.getResponse().getStatusCode() == null || exchange.getResponse().getStatusCode() == HttpStatus.OK;
    }

    @Test
    void testEndToEnd_RegularUserLimitedAccess() {
        Long regularUserId = 2L;
        String adminPath = "/api/admin/users";
        String userPath = "/api/users/profile";

        when(permissionService.hasPermission(eq(regularUserId), eq(adminPath), eq("GET"))).thenReturn(false);
        when(permissionService.hasPermission(eq(regularUserId), eq(userPath), eq("GET"))).thenReturn(true);
        when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());

        MockServerHttpRequest adminRequest = MockServerHttpRequest.get(adminPath)
                .header("X-User-Id", regularUserId.toString())
                .build();
        ServerWebExchange adminExchange = MockServerWebExchange.from(adminRequest);

        Mono<Void> adminResult = filter.apply(new RbacAuthorizationFilter.Config())
                .filter(adminExchange, chain);

        StepVerifier.create(adminResult)
                .verifyComplete();

        assert adminExchange.getResponse().getStatusCode() == HttpStatus.FORBIDDEN;

        MockServerHttpRequest userRequest = MockServerHttpRequest.get(userPath)
                .header("X-User-Id", regularUserId.toString())
                .build();
        ServerWebExchange userExchange = MockServerWebExchange.from(userRequest);

        Mono<Void> userResult = filter.apply(new RbacAuthorizationFilter.Config())
                .filter(userExchange, chain);

        StepVerifier.create(userResult)
                .verifyComplete();

        assert userExchange.getResponse().getStatusCode() == null || userExchange.getResponse().getStatusCode() == HttpStatus.OK;
    }

    @Test
    void testEndToEnd_MultipleHttpMethods() {
        Long userId = 3L;
        String basePath = "/api/users";

        when(permissionService.hasPermission(eq(userId), eq(basePath), eq("GET"))).thenReturn(true);
        when(permissionService.hasPermission(eq(userId), eq(basePath), eq("POST"))).thenReturn(true);
        when(permissionService.hasPermission(eq(userId), eq(basePath), eq("PUT"))).thenReturn(false);
        when(permissionService.hasPermission(eq(userId), eq(basePath), eq("DELETE"))).thenReturn(false);
        when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());

        MockServerHttpRequest getRequest = MockServerHttpRequest.get(basePath)
                .header("X-User-Id", userId.toString())
                .build();
        ServerWebExchange getExchange = MockServerWebExchange.from(getRequest);

        Mono<Void> getResult = filter.apply(new RbacAuthorizationFilter.Config())
                .filter(getExchange, chain);

        StepVerifier.create(getResult)
                .verifyComplete();

        assert getExchange.getResponse().getStatusCode() == null || getExchange.getResponse().getStatusCode() == HttpStatus.OK;

        MockServerHttpRequest postRequest = MockServerHttpRequest.post(basePath)
                .header("X-User-Id", userId.toString())
                .build();
        ServerWebExchange postExchange = MockServerWebExchange.from(postRequest);

        Mono<Void> postResult = filter.apply(new RbacAuthorizationFilter.Config())
                .filter(postExchange, chain);

        StepVerifier.create(postResult)
                .verifyComplete();

        assert postExchange.getResponse().getStatusCode() == null || postExchange.getResponse().getStatusCode() == HttpStatus.OK;

        MockServerHttpRequest putRequest = MockServerHttpRequest.put(basePath)
                .header("X-User-Id", userId.toString())
                .build();
        ServerWebExchange putExchange = MockServerWebExchange.from(putRequest);

        Mono<Void> putResult = filter.apply(new RbacAuthorizationFilter.Config())
                .filter(putExchange, chain);

        StepVerifier.create(putResult)
                .verifyComplete();

        assert putExchange.getResponse().getStatusCode() == HttpStatus.FORBIDDEN;

        MockServerHttpRequest deleteRequest = MockServerHttpRequest.delete(basePath)
                .header("X-User-Id", userId.toString())
                .build();
        ServerWebExchange deleteExchange = MockServerWebExchange.from(deleteRequest);

        Mono<Void> deleteResult = filter.apply(new RbacAuthorizationFilter.Config())
                .filter(deleteExchange, chain);

        StepVerifier.create(deleteResult)
                .verifyComplete();

        assert deleteExchange.getResponse().getStatusCode() == HttpStatus.FORBIDDEN;
    }

    @Test
    void testEndToEnd_PathMatchingScenarios() {
        Long userId = 4L;

        when(permissionService.hasPermission(eq(userId), eq("/api/users"), eq("GET"))).thenReturn(true);
        when(permissionService.hasPermission(eq(userId), eq("/api/users/123"), eq("GET"))).thenReturn(true);
        when(permissionService.hasPermission(eq(userId), eq("/api/users/123/profile"), eq("GET"))).thenReturn(true);
        when(permissionService.hasPermission(eq(userId), eq("/api/admin"), eq("GET"))).thenReturn(false);
        when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());

        String[] allowedPaths = {"/api/users", "/api/users/123", "/api/users/123/profile"};
        for (String path : allowedPaths) {
            MockServerHttpRequest request = MockServerHttpRequest.get(path)
                    .header("X-User-Id", userId.toString())
                    .build();
            ServerWebExchange exchange = MockServerWebExchange.from(request);

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

            StepVerifier.create(result)
                    .verifyComplete();

            assert exchange.getResponse().getStatusCode() == null || exchange.getResponse().getStatusCode() == HttpStatus.OK;
        }

        MockServerHttpRequest adminRequest = MockServerHttpRequest.get("/api/admin")
                .header("X-User-Id", userId.toString())
                .build();
        ServerWebExchange adminExchange = MockServerWebExchange.from(adminRequest);

        Mono<Void> adminResult = filter.apply(new RbacAuthorizationFilter.Config())
                .filter(adminExchange, chain);

        StepVerifier.create(adminResult)
                .verifyComplete();

        assert adminExchange.getResponse().getStatusCode() == HttpStatus.FORBIDDEN;
    }

    @Test
    void testEndToEnd_PublicPathsBypass() {
        String[] publicPaths = {
                "/api/auth/login",
                "/api/auth/register",
                "/actuator/health",
                "/actuator/info"
        };

        for (String path : publicPaths) {
            MockServerHttpRequest request = MockServerHttpRequest.get(path).build();
            ServerWebExchange exchange = MockServerWebExchange.from(request);

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

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

            StepVerifier.create(result)
                    .verifyComplete();

            assert exchange.getResponse().getStatusCode() == null || exchange.getResponse().getStatusCode() == HttpStatus.OK;
        }
    }

    @Test
    void testEndToEnd_ErrorScenarios() {
        MockServerHttpRequest noHeaderRequest = MockServerHttpRequest.get("/api/users").build();
        ServerWebExchange noHeaderExchange = MockServerWebExchange.from(noHeaderRequest);

        Mono<Void> noHeaderResult = filter.apply(new RbacAuthorizationFilter.Config())
                .filter(noHeaderExchange, chain);

        StepVerifier.create(noHeaderResult)
                .verifyComplete();

        assert noHeaderExchange.getResponse().getStatusCode() == HttpStatus.UNAUTHORIZED;

        MockServerHttpRequest invalidIdRequest = MockServerHttpRequest.get("/api/users")
                .header("X-User-Id", "invalid")
                .build();
        ServerWebExchange invalidIdExchange = MockServerWebExchange.from(invalidIdRequest);

        Mono<Void> invalidIdResult = filter.apply(new RbacAuthorizationFilter.Config())
                .filter(invalidIdExchange, chain);

        StepVerifier.create(invalidIdResult)
                .verifyComplete();

        assert invalidIdExchange.getResponse().getStatusCode() == HttpStatus.UNAUTHORIZED;
    }
}