package dev.lions.unionflow.server.security;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;

import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.core.UriInfo;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@QuarkusTest
@DisplayName("RoleDebugFilter")
class RoleDebugFilterTest {

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("filter logs debug info for /api/ paths with JWT")
    void filter_apiPath_withJwt() {
        given()
            .when()
            .get("/api/status")
            .then()
            .statusCode(200);
    }

    @Test
    @DisplayName("filter handles /api/ paths without JWT (anonymous)")
    void filter_apiPath_withoutJwt() {
        given()
            .when()
            .get("/api/status")
            .then()
            .statusCode(200);
    }

    @Test
    @TestSecurity(user = "user@test.com", roles = {"MEMBRE", "USER"})
    @DisplayName("filter logs multiple roles")
    void filter_multipleRoles() {
        given()
            .when()
            .get("/api/status")
            .then()
            .statusCode(200);
    }

    @Test
    @DisplayName("filter skips non-api paths")
    void filter_nonApiPath() {
        given()
            .when()
            .get("/q/health")
            .then()
            .statusCode(anyOf(equalTo(200), equalTo(404)));
    }

    // ── Tests unitaires purs pour couvrir toutes les branches internes ──────────

    /**
     * Couvre : jwt != null, realmAccess != null, realmAccess instanceof Map,
     * rolesObj != null, resourceAccess != null, securityIdentity != null.
     * Toutes les branches "happy path" internes du filtre.
     */
    @Test
    @DisplayName("filter unit: jwt non-null avec realm_access Map et resource_access non-null")
    void filter_unit_jwtWithRealmAccessMapAndResourceAccess() throws Exception {
        RoleDebugFilter filter = new RoleDebugFilter();

        // Préparer realm_access comme une Map<String, Object>
        Map<String, Object> realmAccessMap = new HashMap<>();
        realmAccessMap.put("roles", java.util.List.of("ADMIN", "MEMBRE"));

        // Mock JWT avec realm_access = Map et resource_access non-null
        JsonWebToken mockJwt = mock(JsonWebToken.class);
        when(mockJwt.getSubject()).thenReturn("user-subject");
        when(mockJwt.getName()).thenReturn("admin@test.com");
        when(mockJwt.getClaim("realm_access")).thenReturn(realmAccessMap);
        when(mockJwt.getClaim("resource_access")).thenReturn(Map.of("unionflow-mobile", Map.of()));

        // Mock SecurityIdentity
        SecurityIdentity mockIdentity = mock(SecurityIdentity.class);
        when(mockIdentity.getRoles()).thenReturn(Set.of("ADMIN"));
        java.security.Principal mockPrincipal = mock(java.security.Principal.class);
        when(mockPrincipal.getName()).thenReturn("admin@test.com");
        when(mockIdentity.getPrincipal()).thenReturn(mockPrincipal);
        when(mockIdentity.isAnonymous()).thenReturn(false);

        // Injecter via réflexion
        injectField(filter, "jwt", mockJwt);
        injectField(filter, "securityIdentity", mockIdentity);

        // Mock ContainerRequestContext avec path /api/test
        ContainerRequestContext ctx = mockContext("/api/test");

        // Exécuter le filtre — ne doit pas lancer d'exception
        filter.filter(ctx);
    }

    /**
     * Couvre : realmAccess != null mais PAS instanceof Map (ex: String).
     */
    @Test
    @DisplayName("filter unit: jwt avec realm_access non-Map (String)")
    void filter_unit_realmAccessNotMap() throws Exception {
        RoleDebugFilter filter = new RoleDebugFilter();

        JsonWebToken mockJwt = mock(JsonWebToken.class);
        when(mockJwt.getSubject()).thenReturn("user");
        when(mockJwt.getName()).thenReturn("user@test.com");
        // realm_access retourne une String (pas une Map) → branche "instanceof Map" = false
        when(mockJwt.getClaim("realm_access")).thenReturn("some-string-value");
        when(mockJwt.getClaim("resource_access")).thenReturn(null);

        SecurityIdentity mockIdentity = mock(SecurityIdentity.class);
        when(mockIdentity.getRoles()).thenReturn(Set.of());
        java.security.Principal mockPrincipal = mock(java.security.Principal.class);
        when(mockPrincipal.getName()).thenReturn("user@test.com");
        when(mockIdentity.getPrincipal()).thenReturn(mockPrincipal);
        when(mockIdentity.isAnonymous()).thenReturn(true);

        injectField(filter, "jwt", mockJwt);
        injectField(filter, "securityIdentity", mockIdentity);

        ContainerRequestContext ctx = mockContext("/api/test");
        filter.filter(ctx);
    }

    /**
     * Couvre : realmAccess == null et resourceAccess == null.
     */
    @Test
    @DisplayName("filter unit: jwt avec realm_access null et resource_access null")
    void filter_unit_allClaimsNull() throws Exception {
        RoleDebugFilter filter = new RoleDebugFilter();

        JsonWebToken mockJwt = mock(JsonWebToken.class);
        when(mockJwt.getSubject()).thenReturn("user");
        when(mockJwt.getName()).thenReturn("user@test.com");
        when(mockJwt.getClaim("realm_access")).thenReturn(null);
        when(mockJwt.getClaim("resource_access")).thenReturn(null);

        SecurityIdentity mockIdentity = mock(SecurityIdentity.class);
        when(mockIdentity.getRoles()).thenReturn(Set.of());
        java.security.Principal mockPrincipal = mock(java.security.Principal.class);
        when(mockPrincipal.getName()).thenReturn("user@test.com");
        when(mockIdentity.getPrincipal()).thenReturn(mockPrincipal);
        when(mockIdentity.isAnonymous()).thenReturn(false);

        injectField(filter, "jwt", mockJwt);
        injectField(filter, "securityIdentity", mockIdentity);

        ContainerRequestContext ctx = mockContext("/api/test");
        filter.filter(ctx);
    }

    /**
     * Couvre : jwt == null → branche "JWT est null".
     */
    @Test
    @DisplayName("filter unit: jwt null couvre la branche jwt==null")
    void filter_unit_jwtNull() throws Exception {
        RoleDebugFilter filter = new RoleDebugFilter();

        // jwt reste null
        injectField(filter, "jwt", null);

        SecurityIdentity mockIdentity = mock(SecurityIdentity.class);
        when(mockIdentity.getRoles()).thenReturn(Set.of());
        java.security.Principal mockPrincipal = mock(java.security.Principal.class);
        when(mockPrincipal.getName()).thenReturn("anon");
        when(mockIdentity.getPrincipal()).thenReturn(mockPrincipal);
        when(mockIdentity.isAnonymous()).thenReturn(true);

        injectField(filter, "securityIdentity", mockIdentity);

        ContainerRequestContext ctx = mockContext("/api/test");
        filter.filter(ctx);
    }

    /**
     * Couvre : securityIdentity == null → branche "SecurityIdentity est null".
     */
    @Test
    @DisplayName("filter unit: securityIdentity null couvre la branche securityIdentity==null")
    void filter_unit_securityIdentityNull() throws Exception {
        RoleDebugFilter filter = new RoleDebugFilter();

        JsonWebToken mockJwt = mock(JsonWebToken.class);
        when(mockJwt.getSubject()).thenReturn("user");
        when(mockJwt.getName()).thenReturn("user@test.com");
        when(mockJwt.getClaim("realm_access")).thenReturn(null);
        when(mockJwt.getClaim("resource_access")).thenReturn(null);

        injectField(filter, "jwt", mockJwt);
        injectField(filter, "securityIdentity", null);

        ContainerRequestContext ctx = mockContext("/api/test");
        filter.filter(ctx);
    }

    /**
     * Couvre : getClaim("realm_access") lève une exception → branche catch realm_access.
     */
    @Test
    @DisplayName("filter unit: exception lors de getClaim realm_access couvre le catch")
    void filter_unit_realmAccessThrowsException() throws Exception {
        RoleDebugFilter filter = new RoleDebugFilter();

        JsonWebToken mockJwt = mock(JsonWebToken.class);
        when(mockJwt.getSubject()).thenReturn("user");
        when(mockJwt.getName()).thenReturn("user@test.com");
        when(mockJwt.getClaim("realm_access")).thenThrow(new RuntimeException("test error realm"));
        when(mockJwt.getClaim("resource_access")).thenThrow(new RuntimeException("test error resource"));

        SecurityIdentity mockIdentity = mock(SecurityIdentity.class);
        when(mockIdentity.getRoles()).thenReturn(Set.of());
        java.security.Principal mockPrincipal = mock(java.security.Principal.class);
        when(mockPrincipal.getName()).thenReturn("user@test.com");
        when(mockIdentity.getPrincipal()).thenReturn(mockPrincipal);
        when(mockIdentity.isAnonymous()).thenReturn(false);

        injectField(filter, "jwt", mockJwt);
        injectField(filter, "securityIdentity", mockIdentity);

        ContainerRequestContext ctx = mockContext("/api/test");
        filter.filter(ctx);
    }

    /**
     * Couvre : path ne commence PAS par /api/ → le corps du if n'est pas exécuté.
     */
    @Test
    @DisplayName("filter unit: path non-api ne déclenche pas les logs")
    void filter_unit_nonApiPath() throws Exception {
        RoleDebugFilter filter = new RoleDebugFilter();

        // Pas besoin d'injecter jwt/securityIdentity car le corps est ignoré
        ContainerRequestContext ctx = mockContext("/health");
        filter.filter(ctx);
    }

    /**
     * Couvre : securityIdentity non-null mais principal == null.
     */
    @Test
    @DisplayName("filter unit: securityIdentity avec principal null")
    void filter_unit_securityIdentityNullPrincipal() throws Exception {
        RoleDebugFilter filter = new RoleDebugFilter();

        JsonWebToken mockJwt = mock(JsonWebToken.class);
        when(mockJwt.getSubject()).thenReturn("user");
        when(mockJwt.getName()).thenReturn("user@test.com");
        when(mockJwt.getClaim("realm_access")).thenReturn(null);
        when(mockJwt.getClaim("resource_access")).thenReturn(null);

        SecurityIdentity mockIdentity = mock(SecurityIdentity.class);
        when(mockIdentity.getRoles()).thenReturn(Set.of());
        when(mockIdentity.getPrincipal()).thenReturn(null);
        when(mockIdentity.isAnonymous()).thenReturn(false);

        injectField(filter, "jwt", mockJwt);
        injectField(filter, "securityIdentity", mockIdentity);

        ContainerRequestContext ctx = mockContext("/api/test");
        filter.filter(ctx);
    }

    // ── Utilitaires ──────────────────────────────────────────────────────────────

    private ContainerRequestContext mockContext(String path) {
        ContainerRequestContext ctx = mock(ContainerRequestContext.class);
        UriInfo uriInfo = mock(UriInfo.class);
        when(uriInfo.getPath()).thenReturn(path);
        when(ctx.getUriInfo()).thenReturn(uriInfo);
        return ctx;
    }

    /** Injecte un champ par réflexion dans l'objet cible, même si private. */
    private void injectField(Object target, String fieldName, Object value) throws Exception {
        Class<?> clazz = target.getClass();
        while (clazz != null) {
            try {
                Field field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);
                field.set(target, value);
                return;
            } catch (NoSuchFieldException e) {
                clazz = clazz.getSuperclass();
            }
        }
        throw new NoSuchFieldException("Field '" + fieldName + "' not found in " + target.getClass());
    }
}
