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 io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.MultivaluedMap;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
 * Tests pour les branches de {@link OidcTokenPropagationHeadersFactory#update}.
 */
@QuarkusTest
@DisplayName("OidcTokenPropagationHeadersFactory — branches restantes (5I, 5B)")
class OidcTokenPropagationHeadersFactoryBranchesTest {

    @Inject
    OidcTokenPropagationHeadersFactory factory;

    @InjectMock
    SecurityIdentity securityIdentity;

    @Test
    @DisplayName("update avec identity null → warn + résultat vide")
    void update_identityNull_avertissementEtResultatVide() {
        // when(securityIdentity.isAnonymous()) n'est pas appelé car identity est null
        // mais l'implémentation vérifie identity != null avant isAnonymous()
        when(securityIdentity.isAnonymous()).thenReturn(false);
        when(securityIdentity.getPrincipal()).thenReturn(null);
        // L'identité retourne un principal null — ce n'est pas null mais le principal l'est
        // ce qui provoque une NPE dans "getPrincipal() instanceof OidcJwtCallerPrincipal"
        // → la branche est atteinte via securityIdentity.getPrincipal() = null
        // mais le code vérifie "identity != null && !identity.isAnonymous()" d'abord.
        // Si identity n'est pas null mais getPrincipal() retourne null :
        // → "identity.getPrincipal() instanceof OidcJwtCallerPrincipal" = false (null instanceof = false)

        MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
        // Pas d'Authorization header
        MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();

        MultivaluedMap<String, String> result = factory.update(incoming, outgoing);

        assertThat(result).isNotNull();
        assertThat(result.containsKey("Authorization")).isFalse();
    }

    @Test
    @DisplayName("update avec identité anonyme → warn + résultat vide (branche isAnonymous)")
    void update_identiteAnonyme_avertissementEtResultatVide() {
        when(securityIdentity.isAnonymous()).thenReturn(true);

        MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
        MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();

        MultivaluedMap<String, String> result = factory.update(incoming, outgoing);

        assertThat(result).isNotNull();
        assertThat(result.containsKey("Authorization")).isFalse();
    }

    @Test
    @DisplayName("update avec incomingHeaders null → stratégie 1 sautée, stratégie 2 évaluée")
    void update_incomingHeadersNull_strategie1Sautee() {
        when(securityIdentity.isAnonymous()).thenReturn(true);

        MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();

        MultivaluedMap<String, String> result = factory.update(null, outgoing);

        assertThat(result).isNotNull();
        assertThat(result.containsKey("Authorization")).isFalse();
    }

    @Test
    @DisplayName("update avec OidcJwtCallerPrincipal token null → warn + résultat vide")
    void update_oidcPrincipalTokenNull_avertissementEtResultatVide() {
        OidcJwtCallerPrincipal mockPrincipal = mock(OidcJwtCallerPrincipal.class);
        when(mockPrincipal.getRawToken()).thenReturn(null);

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

        MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
        MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();

        MultivaluedMap<String, String> result = factory.update(incoming, outgoing);

        assertThat(result).isNotNull();
        assertThat(result.containsKey("Authorization")).isFalse();
    }

    @Test
    @DisplayName("update avec Authorization null dans incomingHeaders → passe à stratégie 2")
    void update_incomingAuthorizationNullValue_passeAStrategie2() {
        MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
        incoming.add("Authorization", null);

        when(securityIdentity.isAnonymous()).thenReturn(true);

        MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();

        MultivaluedMap<String, String> result = factory.update(incoming, outgoing);

        assertThat(result).isNotNull();
        assertThat(result.containsKey("Authorization")).isFalse();
    }

    @Test
    @DisplayName("update avec principal non-OidcJwtCallerPrincipal → warn + résultat vide")
    void update_nonOidcPrincipal_avertissementEtResultatVide() {
        java.security.Principal nonOidcPrincipal = () -> "simple-principal";

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

        MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
        MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();

        MultivaluedMap<String, String> result = factory.update(incoming, outgoing);

        assertThat(result).isNotNull();
        assertThat(result.containsKey("Authorization")).isFalse();
    }

    @Test
    @DisplayName("update avec Authorization valide + autres headers → copie uniquement Authorization")
    void update_avecAutresHeaders_copieUniquementAuthorization() {
        MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
        incoming.add("Authorization", "Bearer valid-token-abc");
        incoming.add("Content-Type", "application/json");
        incoming.add("Accept", "application/json");

        MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();

        MultivaluedMap<String, String> result = factory.update(incoming, outgoing);

        assertThat(result.getFirst("Authorization")).isEqualTo("Bearer valid-token-abc");
        assertThat(result.size()).isEqualTo(1);
    }

    @Test
    @DisplayName("update avec OidcJwtCallerPrincipal token non-blank → 'Bearer ' préfixé au token")
    void update_oidcPrincipalTokenValide_bearerPrefixe() {
        OidcJwtCallerPrincipal mockPrincipal = mock(OidcJwtCallerPrincipal.class);
        String rawToken = "eyJhbGciOiJSUzI1NiJ9.payload.signature";
        when(mockPrincipal.getRawToken()).thenReturn(rawToken);

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

        MultivaluedMap<String, String> incoming = new MultivaluedHashMap<>();
        MultivaluedMap<String, String> outgoing = new MultivaluedHashMap<>();

        MultivaluedMap<String, String> result = factory.update(incoming, outgoing);

        assertThat(result.getFirst("Authorization")).isEqualTo("Bearer " + rawToken);
    }
}
