package dev.lions.unionflow.server.client;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import io.quarkus.oidc.runtime.OidcJwtCallerPrincipal;
import io.quarkus.security.identity.SecurityIdentity;
import jakarta.ws.rs.client.ClientRequestContext;
import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.MultivaluedMap;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URI;
import java.security.Principal;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
 * Tests pour {@link JwtPropagationFilter} — pur Mockito (pas de @QuarkusTest).
 *
 * <p>Le filtre est instancié directement et {@code securityIdentity} est injecté
 * par réflexion. Cela évite le démarrage Quarkus complet (Postgres/OIDC) pour
 * un simple test unitaire de filtre REST Client.
 *
 * <p>Couvre toutes les branches de {@code filter()} :
 * <ul>
 *   <li>{@code securityIdentity == null} → log warn, pas de header (branche L41)</li>
 *   <li>identité anonyme → pas de propagation</li>
 *   <li>OidcJwtCallerPrincipal avec token valide → header Authorization propagé</li>
 *   <li>OidcJwtCallerPrincipal avec token null/blank → pas de propagation</li>
 *   <li>JsonWebToken (non OidcJwtCallerPrincipal) avec token valide → header propagé</li>
 *   <li>JsonWebToken avec token null/blank → pas de propagation</li>
 *   <li>Principal générique (ni OidcJwtCallerPrincipal ni JsonWebToken) → log warn, pas de header</li>
 * </ul>
 */
class JwtPropagationFilterTest {

    private JwtPropagationFilter filter;
    private SecurityIdentity securityIdentity;

    @BeforeEach
    void setUp() throws Exception {
        filter = new JwtPropagationFilter();
        securityIdentity = mock(SecurityIdentity.class);
        Field f = JwtPropagationFilter.class.getDeclaredField("securityIdentity");
        f.setAccessible(true);
        f.set(filter, securityIdentity);
    }

    private ClientRequestContext buildMockContext() {
        MultivaluedMap<String, Object> headers = new MultivaluedHashMap<>();
        ClientRequestContext ctx = mock(ClientRequestContext.class);
        when(ctx.getHeaders()).thenReturn(headers);
        when(ctx.getUri()).thenReturn(URI.create("http://localhost/api/test"));
        return ctx;
    }

    // ─── Branch: securityIdentity == null ────────────────────────────────────

    @Test
    @DisplayName("filter : securityIdentity null → warn, pas de header (branche L41)")
    void filter_securityIdentityNull_doesNotPropagate() throws Exception {
        // Remettre le champ à null pour cette branche spécifique
        Field f = JwtPropagationFilter.class.getDeclaredField("securityIdentity");
        f.setAccessible(true);
        f.set(filter, null);

        ClientRequestContext ctx = buildMockContext();
        filter.filter(ctx);

        assertThat(ctx.getHeaders().getFirst("Authorization")).isNull();
    }

    // ─── Branch: securityIdentity.isAnonymous() = true → skip ────────────────

    @Test
    @DisplayName("filter : identité anonyme → pas de header Authorization ajouté")
    void filter_anonymousIdentity_doesNotPropagateToken() throws IOException {
        when(securityIdentity.isAnonymous()).thenReturn(true);

        ClientRequestContext ctx = buildMockContext();
        filter.filter(ctx);

        assertThat(ctx.getHeaders().getFirst("Authorization")).isNull();
    }

    // ─── Branch: OidcJwtCallerPrincipal avec token valide ────────────────────

    @Test
    @DisplayName("filter : OidcJwtCallerPrincipal avec token valide → Authorization propagé")
    void filter_oidcPrincipalWithValidToken_propagatesToken() throws IOException {
        OidcJwtCallerPrincipal principal = mock(OidcJwtCallerPrincipal.class);
        when(principal.getRawToken()).thenReturn("valid-jwt-token");

        when(securityIdentity.isAnonymous()).thenReturn(false);
        when(securityIdentity.getPrincipal()).thenReturn(principal);

        ClientRequestContext ctx = buildMockContext();
        filter.filter(ctx);

        Object authHeader = ctx.getHeaders().getFirst("Authorization");
        assertThat(authHeader).isNotNull();
        assertThat(authHeader.toString()).isEqualTo("Bearer valid-jwt-token");
    }

    // ─── Branch: OidcJwtCallerPrincipal avec token blank ─────────────────────

    @Test
    @DisplayName("filter : OidcJwtCallerPrincipal avec token blank → pas de propagation")
    void filter_oidcPrincipalWithBlankToken_doesNotPropagate() throws IOException {
        OidcJwtCallerPrincipal principal = mock(OidcJwtCallerPrincipal.class);
        when(principal.getRawToken()).thenReturn("   ");

        when(securityIdentity.isAnonymous()).thenReturn(false);
        when(securityIdentity.getPrincipal()).thenReturn(principal);

        ClientRequestContext ctx = buildMockContext();
        filter.filter(ctx);

        assertThat(ctx.getHeaders().getFirst("Authorization")).isNull();
    }

    // ─── Branch: OidcJwtCallerPrincipal avec token null ──────────────────────

    @Test
    @DisplayName("filter : OidcJwtCallerPrincipal avec token null → pas de propagation")
    void filter_oidcPrincipalWithNullToken_doesNotPropagate() throws IOException {
        OidcJwtCallerPrincipal principal = mock(OidcJwtCallerPrincipal.class);
        when(principal.getRawToken()).thenReturn(null);

        when(securityIdentity.isAnonymous()).thenReturn(false);
        when(securityIdentity.getPrincipal()).thenReturn(principal);

        ClientRequestContext ctx = buildMockContext();
        filter.filter(ctx);

        assertThat(ctx.getHeaders().getFirst("Authorization")).isNull();
    }

    // ─── Branch: JsonWebToken (NOT OidcJwtCallerPrincipal) ───────────────────

    @Test
    @DisplayName("filter : JsonWebToken principal (non-OIDC) avec token valide → Authorization propagé")
    void filter_jsonWebTokenPrincipalWithValidToken_propagatesToken() throws IOException {
        JsonWebToken jwt = mock(JsonWebToken.class);
        when(jwt.getRawToken()).thenReturn("valid-jwt-from-JsonWebToken");

        when(securityIdentity.isAnonymous()).thenReturn(false);
        when(securityIdentity.getPrincipal()).thenReturn(jwt);

        ClientRequestContext ctx = buildMockContext();
        filter.filter(ctx);

        Object authHeader = ctx.getHeaders().getFirst("Authorization");
        assertThat(authHeader).isNotNull();
        assertThat(authHeader.toString()).isEqualTo("Bearer valid-jwt-from-JsonWebToken");
    }

    @Test
    @DisplayName("filter : JsonWebToken principal avec token null → pas de propagation")
    void filter_jsonWebTokenWithNullToken_doesNotPropagate() throws IOException {
        JsonWebToken jwt = mock(JsonWebToken.class);
        when(jwt.getRawToken()).thenReturn(null);

        when(securityIdentity.isAnonymous()).thenReturn(false);
        when(securityIdentity.getPrincipal()).thenReturn(jwt);

        ClientRequestContext ctx = buildMockContext();
        filter.filter(ctx);

        assertThat(ctx.getHeaders().getFirst("Authorization")).isNull();
    }

    @Test
    @DisplayName("filter : JsonWebToken principal avec token blank → pas de propagation")
    void filter_jsonWebTokenWithBlankToken_doesNotPropagate() throws IOException {
        JsonWebToken jwt = mock(JsonWebToken.class);
        when(jwt.getRawToken()).thenReturn("   ");

        when(securityIdentity.isAnonymous()).thenReturn(false);
        when(securityIdentity.getPrincipal()).thenReturn(jwt);

        ClientRequestContext ctx = buildMockContext();
        filter.filter(ctx);

        assertThat(ctx.getHeaders().getFirst("Authorization")).isNull();
    }

    // ─── Branch: principal ni OidcJwtCallerPrincipal ni JsonWebToken ─────────

    @Test
    @DisplayName("filter : principal inconnu (ni OIDC ni JWT) → log warn, pas de header Authorization")
    void filter_unknownPrincipalType_doesNotPropagate() throws IOException {
        Principal genericPrincipal = mock(Principal.class);
        when(genericPrincipal.getName()).thenReturn("some-user");

        when(securityIdentity.isAnonymous()).thenReturn(false);
        when(securityIdentity.getPrincipal()).thenReturn(genericPrincipal);

        ClientRequestContext ctx = buildMockContext();
        filter.filter(ctx);

        assertThat(ctx.getHeaders().getFirst("Authorization")).isNull();
    }
}
