package dev.lions.unionflow.server.resource;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

import dev.lions.unionflow.server.api.enums.paiement.StatutIntentionPaiement;
import dev.lions.unionflow.server.entity.Cotisation;
import dev.lions.unionflow.server.entity.IntentionPaiement;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.repository.IntentionPaiementRepository;
import dev.lions.unionflow.server.service.mutuelle.epargne.TransactionEpargneService;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.mockito.InjectSpy;
import io.quarkus.test.junit.QuarkusTestProfile;
import io.quarkus.test.junit.TestProfile;
import jakarta.persistence.EntityManager;
import java.math.BigDecimal;
import java.util.Map;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
 * Tests pour WaveRedirectResource avec wave.mock.enabled=true.
 * Couvre les lignes de applyMockCompletion() non atteignables
 * avec le profil de test par défaut (mockEnabled=false).
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2026-03-21
 */
@QuarkusTest
@TestProfile(WaveRedirectResourceMockEnabledTest.MockEnabledProfile.class)
class WaveRedirectResourceMockEnabledTest {

    /**
     * Profil de test activant wave.mock.enabled=true.
     */
    public static class MockEnabledProfile implements QuarkusTestProfile {
        @Override
        public Map<String, String> getConfigOverrides() {
            return Map.of(
                    "wave.mock.enabled", "true",
                    "wave.api.key", "test-key",
                    "wave.api.secret", "test-secret"
            );
        }
    }

    private static final String BASE_PATH = "/api/wave-redirect";

    @InjectSpy
    IntentionPaiementRepository intentionPaiementRepository;

    @InjectMock
    EntityManager entityManager;

    @InjectMock
    TransactionEpargneService transactionEpargneService;

    // -----------------------------------------------------------------------
    // mock-complete avec mockEnabled=true + ref invalide (UUID invalide)
    // → applyMockCompletion déclenché, UUID.fromString échoue → catch (L138-139)
    // -----------------------------------------------------------------------

    @Test
    @DisplayName("GET /mock-complete avec ref invalide (non-UUID) → exception catchée, redirige vers success (L137-139)")
    void mockComplete_mockEnabled_invalidRef_catchesException() {
        String invalidRef = "not-a-valid-uuid";

        given()
                .redirects().follow(false)
                .queryParam("ref", invalidRef)
                .when()
                .get(BASE_PATH + "/mock-complete")
                .then()
                // Quand l'UUID parse échoue dans applyMockCompletion, l'exception est catchée (L137-139)
                // et le endpoint redirige quand même vers le deep link success (L89)
                .statusCode(anyOf(equalTo(301), equalTo(302), equalTo(303)));
    }

    // -----------------------------------------------------------------------
    // mock-complete avec mockEnabled=true + intention introuvable (L98-99)
    // -----------------------------------------------------------------------

    @Test
    @DisplayName("GET /mock-complete avec intention introuvable → LOG.warn + return (L98-99)")
    void mockComplete_mockEnabled_intentionNotFound() {
        String ref = UUID.randomUUID().toString();
        // entityManager.find returns null by default → findById returns null → LOG.warn + return

        given()
                .redirects().follow(false)
                .queryParam("ref", ref)
                .when()
                .get(BASE_PATH + "/mock-complete")
                .then()
                .statusCode(anyOf(equalTo(301), equalTo(302), equalTo(303)))
                .header("Location", containsString("success"));
    }

    // -----------------------------------------------------------------------
    // mock-complete avec mockEnabled=true + intention trouvée + objetsCibles null
    // Couvre L101-103 (setStatut, setDateCompletion, persist), L136 (LOG.infof)
    // -----------------------------------------------------------------------

    @Test
    @DisplayName("GET /mock-complete avec intention valide sans objetsCibles → marque COMPLETEE (L101-103, L136)")
    void mockComplete_mockEnabled_intentionFound_noObjetsCibles() {
        String ref = UUID.randomUUID().toString();
        UUID intentionId = UUID.fromString(ref);

        IntentionPaiement intention = buildFakeIntention(intentionId);
        intention.setObjetsCibles(null);

        when(entityManager.find(IntentionPaiement.class, intentionId)).thenReturn(intention);
        doNothing().when(intentionPaiementRepository).persist(any(IntentionPaiement.class));

        given()
                .redirects().follow(false)
                .queryParam("ref", ref)
                .when()
                .get(BASE_PATH + "/mock-complete")
                .then()
                .statusCode(anyOf(equalTo(301), equalTo(302), equalTo(303)))
                .header("Location", containsString("success"));

        verify(intentionPaiementRepository).persist(any(IntentionPaiement.class));
    }

    // -----------------------------------------------------------------------
    // mock-complete avec objetsCibles contenant une COTISATION
    // Couvre L107-119 (readTree, itération, find Cotisation, setStatut PAYEE, merge)
    // -----------------------------------------------------------------------

    @Test
    @DisplayName("GET /mock-complete avec objetsCibles COTISATION → marque cotisation PAYEE (L107-119)")
    void mockComplete_mockEnabled_withCotisationObjet() {
        UUID cotisationId = UUID.randomUUID();
        String ref = UUID.randomUUID().toString();
        UUID intentionId = UUID.fromString(ref);

        String objetsCibles = "[{\"type\":\"COTISATION\",\"id\":\"" + cotisationId + "\",\"montant\":\"5000\"}]";

        IntentionPaiement intention = buildFakeIntention(intentionId);
        intention.setObjetsCibles(objetsCibles);

        Cotisation cotisation = new Cotisation();
        cotisation.setMontantDu(new BigDecimal("5000"));
        cotisation.setStatut("EN_ATTENTE");

        when(entityManager.find(IntentionPaiement.class, intentionId)).thenReturn(intention);
        when(entityManager.find(eq(Cotisation.class), eq(cotisationId))).thenReturn(cotisation);
        when(entityManager.merge(any(Cotisation.class))).thenReturn(cotisation);
        doNothing().when(intentionPaiementRepository).persist(any(IntentionPaiement.class));

        given()
                .redirects().follow(false)
                .queryParam("ref", ref)
                .when()
                .get(BASE_PATH + "/mock-complete")
                .then()
                .statusCode(anyOf(equalTo(301), equalTo(302), equalTo(303)))
                .header("Location", containsString("success"));

        verify(entityManager).merge(any(Cotisation.class));
    }

    // -----------------------------------------------------------------------
    // mock-complete avec objetsCibles contenant un DEPOT_EPARGNE
    // Couvre L121-131 (compteId, montant, TransactionEpargneRequest, executerTransaction)
    // -----------------------------------------------------------------------

    @Test
    @DisplayName("GET /mock-complete avec objetsCibles DEPOT_EPARGNE → exécute transaction épargne (L121-131)")
    void mockComplete_mockEnabled_withDepotEpargneObjet() {
        UUID compteId = UUID.randomUUID();
        String ref = UUID.randomUUID().toString();
        UUID intentionId = UUID.fromString(ref);

        String objetsCibles = "[{\"type\":\"DEPOT_EPARGNE\",\"compteId\":\"" + compteId + "\",\"montant\":\"10000\"}]";

        IntentionPaiement intention = buildFakeIntention(intentionId);
        intention.setObjetsCibles(objetsCibles);

        when(entityManager.find(IntentionPaiement.class, intentionId)).thenReturn(intention);
        doNothing().when(intentionPaiementRepository).persist(any(IntentionPaiement.class));
        when(transactionEpargneService.executerTransaction(any())).thenReturn(null);

        given()
                .redirects().follow(false)
                .queryParam("ref", ref)
                .when()
                .get(BASE_PATH + "/mock-complete")
                .then()
                .statusCode(anyOf(equalTo(301), equalTo(302), equalTo(303)))
                .header("Location", containsString("success"));

        verify(transactionEpargneService).executerTransaction(any());
    }

    // -----------------------------------------------------------------------
    // success avec mockEnabled=true + ref valide + intention trouvée
    // Couvre le chemin GET /success → applyMockCompletion (L58-60)
    // -----------------------------------------------------------------------

    @Test
    @DisplayName("GET /success avec wave.mock.enabled=true + intention trouvée → marque COMPLETEE")
    void waveSuccess_mockEnabled_intentionFound() {
        String ref = UUID.randomUUID().toString();
        UUID intentionId = UUID.fromString(ref);

        IntentionPaiement intention = buildFakeIntention(intentionId);
        intention.setObjetsCibles(null);

        when(entityManager.find(IntentionPaiement.class, intentionId)).thenReturn(intention);
        doNothing().when(intentionPaiementRepository).persist(any(IntentionPaiement.class));

        given()
                .redirects().follow(false)
                .queryParam("ref", ref)
                .when()
                .get(BASE_PATH + "/success")
                .then()
                .statusCode(anyOf(equalTo(301), equalTo(302), equalTo(303)))
                .header("Location", containsString("success"));
    }

    // -----------------------------------------------------------------------
    // mock-complete avec mockEnabled=true + ref null → BAD_REQUEST (L85-87)
    // -----------------------------------------------------------------------

    @Test
    @DisplayName("GET /mock-complete avec wave.mock.enabled=true et ref null → 400 (L85-87)")
    void mockComplete_mockEnabled_nullRef_returns400() {
        given()
                .redirects().follow(false)
                .when()
                .get(BASE_PATH + "/mock-complete")
                .then()
                .statusCode(400);
    }

    // -----------------------------------------------------------------------
    // mock-complete avec mockEnabled=true + ref blank → BAD_REQUEST (L85-87, branche isBlank)
    // -----------------------------------------------------------------------

    @Test
    @DisplayName("GET /mock-complete avec wave.mock.enabled=true et ref blanc → 400 (branche ref.isBlank() L85)")
    void mockComplete_mockEnabled_blankRef_returns400() {
        // ref présente mais blank → ref.isBlank() == true → L86-87 BAD_REQUEST
        given()
                .redirects().follow(false)
                .queryParam("ref", "   ")
                .when()
                .get(BASE_PATH + "/mock-complete")
                .then()
                .statusCode(400)
                .body(containsString("ref requis"));
    }

    // -----------------------------------------------------------------------
    // mock-complete avec objetsCibles contenant un nœud JSON sans "type" reconnu
    // Couvre la branche else (node.has("type") mais type != COTISATION et != DEPOT_EPARGNE)
    // -----------------------------------------------------------------------

    @Test
    @DisplayName("GET /mock-complete avec objetsCibles type inconnu → ignoré, redirige vers success")
    void mockComplete_mockEnabled_withTypeInconnu_ignoreNode() {
        String ref = UUID.randomUUID().toString();
        UUID intentionId = UUID.fromString(ref);

        // Type inconnu → aucune branche COTISATION ni DEPOT_EPARGNE n'est prise
        String objetsCibles = "[{\"type\":\"AUTRE\",\"id\":\"" + UUID.randomUUID() + "\"}]";

        IntentionPaiement intention = buildFakeIntention(intentionId);
        intention.setObjetsCibles(objetsCibles);

        when(entityManager.find(IntentionPaiement.class, intentionId)).thenReturn(intention);
        doNothing().when(intentionPaiementRepository).persist(any(IntentionPaiement.class));

        given()
                .redirects().follow(false)
                .queryParam("ref", ref)
                .when()
                .get(BASE_PATH + "/mock-complete")
                .then()
                .statusCode(anyOf(equalTo(301), equalTo(302), equalTo(303)))
                .header("Location", containsString("success"));
    }

    // -----------------------------------------------------------------------
    // mock-complete avec objetsCibles COTISATION dont la cotisation est introuvable (L113 null check)
    // -----------------------------------------------------------------------

    @Test
    @DisplayName("GET /mock-complete COTISATION introuvable en DB → ignorée (L113 cotisation == null)")
    void mockComplete_mockEnabled_cotisationNotFound_ignored() {
        UUID cotisationId = UUID.randomUUID();
        String ref = UUID.randomUUID().toString();
        UUID intentionId = UUID.fromString(ref);

        String objetsCibles = "[{\"type\":\"COTISATION\",\"id\":\"" + cotisationId + "\"}]";

        IntentionPaiement intention = buildFakeIntention(intentionId);
        intention.setObjetsCibles(objetsCibles);

        EntityManager em = mock(EntityManager.class);
        when(entityManager.find(IntentionPaiement.class, intentionId)).thenReturn(intention);
        doNothing().when(intentionPaiementRepository).persist(any(IntentionPaiement.class));
        when(intentionPaiementRepository.getEntityManager()).thenReturn(em);
        // cotisation introuvable → null → L113 if-block non entré
        when(em.find(eq(Cotisation.class), eq(cotisationId))).thenReturn(null);

        given()
                .redirects().follow(false)
                .queryParam("ref", ref)
                .when()
                .get(BASE_PATH + "/mock-complete")
                .then()
                .statusCode(anyOf(equalTo(301), equalTo(302), equalTo(303)))
                .header("Location", containsString("success"));
    }

    // -----------------------------------------------------------------------
    // mock-complete avec COTISATION sans champ "montant" → utilise cotisation.getMontantDu() (L114 branche)
    // -----------------------------------------------------------------------

    @Test
    @DisplayName("GET /mock-complete COTISATION sans montant JSON → utilise getMontantDu() (L114 branche !node.has)")
    void mockComplete_mockEnabled_cotisationSansMontantJson_utiliseGetMontantDu() {
        UUID cotisationId = UUID.randomUUID();
        String ref = UUID.randomUUID().toString();
        UUID intentionId = UUID.fromString(ref);

        // Pas de champ "montant" → L114 prend branche : cotisation.getMontantDu()
        String objetsCibles = "[{\"type\":\"COTISATION\",\"id\":\"" + cotisationId + "\"}]";

        IntentionPaiement intention = buildFakeIntention(intentionId);
        intention.setObjetsCibles(objetsCibles);

        Cotisation cotisation = new Cotisation();
        cotisation.setMontantDu(new BigDecimal("7500"));
        cotisation.setStatut("EN_ATTENTE");

        when(entityManager.find(IntentionPaiement.class, intentionId)).thenReturn(intention);
        when(entityManager.find(eq(Cotisation.class), eq(cotisationId))).thenReturn(cotisation);
        when(entityManager.merge(any(Cotisation.class))).thenReturn(cotisation);
        doNothing().when(intentionPaiementRepository).persist(any(IntentionPaiement.class));

        given()
                .redirects().follow(false)
                .queryParam("ref", ref)
                .when()
                .get(BASE_PATH + "/mock-complete")
                .then()
                .statusCode(anyOf(equalTo(301), equalTo(302), equalTo(303)))
                .header("Location", containsString("success"));

        verify(entityManager).merge(any(Cotisation.class));
    }

    // -----------------------------------------------------------------------
    // success() avec mockEnabled=true + ref blank → ne doit PAS appeler applyMockCompletion
    // Couvre la branche: mockEnabled && ref != null && !ref.isBlank() = false (L58)
    // -----------------------------------------------------------------------

    @Test
    @DisplayName("GET /success avec wave.mock.enabled=true et ref blank → skip applyMockCompletion, redirige (L58 branche false)")
    void waveSuccess_mockEnabled_blankRef_skipsApplyMockCompletion() {
        // ref non null mais blank → condition (mockEnabled && ref != null && !ref.isBlank()) = false
        // → applyMockCompletion NON appelé → simple redirection
        given()
                .redirects().follow(false)
                .queryParam("ref", "   ")
                .when()
                .get(BASE_PATH + "/success")
                .then()
                .statusCode(anyOf(equalTo(301), equalTo(302), equalTo(303)));
    }

    @Test
    @DisplayName("GET /success avec wave.mock.enabled=true et ref null → skip applyMockCompletion, redirige (L58 branche false)")
    void waveSuccess_mockEnabled_nullRef_skipsApplyMockCompletion() {
        // ref null → condition (mockEnabled && ref != null && !ref.isBlank()) = false
        // → applyMockCompletion NON appelé
        given()
                .redirects().follow(false)
                .when()
                .get(BASE_PATH + "/success")
                .then()
                .statusCode(anyOf(equalTo(301), equalTo(302), equalTo(303)));
    }

    // -----------------------------------------------------------------------
    // applyMockCompletion() : objetsCibles non-null mais non-JSON (erreur parse)
    // Couvre le catch block via une exception Jackson
    // -----------------------------------------------------------------------

    @Test
    @DisplayName("GET /mock-complete avec objetsCibles invalide JSON → exception catchée (L137-139)")
    void mockComplete_mockEnabled_invalidJsonObjetsCibles_catchesException() {
        String ref = UUID.randomUUID().toString();
        UUID intentionId = UUID.fromString(ref);

        IntentionPaiement intention = buildFakeIntention(intentionId);
        // objetsCibles non vide mais JSON invalide → Jackson.readTree lève une exception → catch(Exception)
        intention.setObjetsCibles("not-valid-json-[[[");

        when(entityManager.find(IntentionPaiement.class, intentionId)).thenReturn(intention);
        doNothing().when(intentionPaiementRepository).persist(any(IntentionPaiement.class));

        given()
                .redirects().follow(false)
                .queryParam("ref", ref)
                .when()
                .get(BASE_PATH + "/mock-complete")
                .then()
                // Exception catchée → log.error → redirection quand même vers success
                .statusCode(anyOf(equalTo(301), equalTo(302), equalTo(303)))
                .header("Location", containsString("success"));
    }

    // -----------------------------------------------------------------------
    // applyMockCompletion() L106 : objetsCibles non-null mais blank → branche isBlank()=true → skip
    // -----------------------------------------------------------------------

    @Test
    @DisplayName("GET /mock-complete avec objetsCibles blank → skip itération (L106 branche isBlank=true)")
    void mockComplete_mockEnabled_blankObjetsCibles_skipsIteration() {
        String ref = UUID.randomUUID().toString();
        UUID intentionId = UUID.fromString(ref);

        IntentionPaiement intention = buildFakeIntention(intentionId);
        // objetsCibles non-null mais blank → condition !objetsCibles.isBlank() = false → skip
        intention.setObjetsCibles("   ");

        when(entityManager.find(IntentionPaiement.class, intentionId)).thenReturn(intention);
        doNothing().when(intentionPaiementRepository).persist(any(IntentionPaiement.class));

        given()
                .redirects().follow(false)
                .queryParam("ref", ref)
                .when()
                .get(BASE_PATH + "/mock-complete")
                .then()
                .statusCode(anyOf(equalTo(301), equalTo(302), equalTo(303)))
                .header("Location", containsString("success"));

        verify(intentionPaiementRepository).persist(any(IntentionPaiement.class));
    }

    // -----------------------------------------------------------------------
    // applyMockCompletion() L108 : objetsCibles JSON valide mais non-tableau → arr.isArray()=false
    // -----------------------------------------------------------------------

    @Test
    @DisplayName("GET /mock-complete avec objetsCibles JSON non-tableau → arr.isArray()=false, skip (L108 branche false)")
    void mockComplete_mockEnabled_jsonObjectNotArray_skipsIteration() {
        String ref = UUID.randomUUID().toString();
        UUID intentionId = UUID.fromString(ref);

        IntentionPaiement intention = buildFakeIntention(intentionId);
        // JSON valide mais c'est un objet, pas un tableau → arr.isArray()=false → branche false
        intention.setObjetsCibles("{\"type\":\"COTISATION\",\"id\":\"" + UUID.randomUUID() + "\"}");

        when(entityManager.find(IntentionPaiement.class, intentionId)).thenReturn(intention);
        doNothing().when(intentionPaiementRepository).persist(any(IntentionPaiement.class));

        given()
                .redirects().follow(false)
                .queryParam("ref", ref)
                .when()
                .get(BASE_PATH + "/mock-complete")
                .then()
                .statusCode(anyOf(equalTo(301), equalTo(302), equalTo(303)))
                .header("Location", containsString("success"));

        verify(intentionPaiementRepository).persist(any(IntentionPaiement.class));
    }

    // -----------------------------------------------------------------------
    // applyMockCompletion() L110 : nœud sans champ "type" → node.has("type")=false
    // -----------------------------------------------------------------------

    @Test
    @DisplayName("GET /mock-complete nœud sans champ 'type' → node.has(\"type\")=false, ignoré (L110 branche false première clause)")
    void mockComplete_mockEnabled_nodeSansType_ignored() {
        String ref = UUID.randomUUID().toString();
        UUID intentionId = UUID.fromString(ref);

        // Nœud sans champ "type" → node.has("type")=false → court-circuit L110 → couvre la branche
        String objetsCibles = "[{\"id\":\"" + UUID.randomUUID() + "\",\"montant\":\"5000\"}]";

        IntentionPaiement intention = buildFakeIntention(intentionId);
        intention.setObjetsCibles(objetsCibles);

        when(entityManager.find(IntentionPaiement.class, intentionId)).thenReturn(intention);
        doNothing().when(intentionPaiementRepository).persist(any(IntentionPaiement.class));

        given()
                .redirects().follow(false)
                .queryParam("ref", ref)
                .when()
                .get(BASE_PATH + "/mock-complete")
                .then()
                .statusCode(anyOf(equalTo(301), equalTo(302), equalTo(303)))
                .header("Location", containsString("success"));

        verify(intentionPaiementRepository).persist(any(IntentionPaiement.class));
    }

    // -----------------------------------------------------------------------
    // applyMockCompletion() L110 : nœud COTISATION sans champ "id" → branche node.has("id")=false
    // -----------------------------------------------------------------------

    @Test
    @DisplayName("GET /mock-complete COTISATION sans champ 'id' → branche node.has(\"id\")=false, ignoré (L110)")
    void mockComplete_mockEnabled_cotisationSansId_ignored() {
        String ref = UUID.randomUUID().toString();
        UUID intentionId = UUID.fromString(ref);

        // type=COTISATION mais pas de champ "id" → node.has("id")=false → branche non prise
        String objetsCibles = "[{\"type\":\"COTISATION\",\"montant\":\"5000\"}]";

        IntentionPaiement intention = buildFakeIntention(intentionId);
        intention.setObjetsCibles(objetsCibles);

        when(entityManager.find(IntentionPaiement.class, intentionId)).thenReturn(intention);
        doNothing().when(intentionPaiementRepository).persist(any(IntentionPaiement.class));

        given()
                .redirects().follow(false)
                .queryParam("ref", ref)
                .when()
                .get(BASE_PATH + "/mock-complete")
                .then()
                .statusCode(anyOf(equalTo(301), equalTo(302), equalTo(303)))
                .header("Location", containsString("success"));

        verify(intentionPaiementRepository).persist(any(IntentionPaiement.class));
    }

    // -----------------------------------------------------------------------
    // applyMockCompletion() L121 : nœud DEPOT_EPARGNE sans champ "compteId" → branche false
    // -----------------------------------------------------------------------

    @Test
    @DisplayName("GET /mock-complete DEPOT_EPARGNE sans champ 'compteId' → ignoré (L121 branche node.has(\"compteId\")=false)")
    void mockComplete_mockEnabled_depotEpargneSansCompteId_ignored() {
        String ref = UUID.randomUUID().toString();
        UUID intentionId = UUID.fromString(ref);

        // type=DEPOT_EPARGNE mais pas de champ "compteId" → node.has("compteId")=false → branche non prise
        String objetsCibles = "[{\"type\":\"DEPOT_EPARGNE\",\"montant\":\"10000\"}]";

        IntentionPaiement intention = buildFakeIntention(intentionId);
        intention.setObjetsCibles(objetsCibles);

        when(entityManager.find(IntentionPaiement.class, intentionId)).thenReturn(intention);
        doNothing().when(intentionPaiementRepository).persist(any(IntentionPaiement.class));

        given()
                .redirects().follow(false)
                .queryParam("ref", ref)
                .when()
                .get(BASE_PATH + "/mock-complete")
                .then()
                .statusCode(anyOf(equalTo(301), equalTo(302), equalTo(303)))
                .header("Location", containsString("success"));

        verify(intentionPaiementRepository).persist(any(IntentionPaiement.class));
    }

    // -----------------------------------------------------------------------
    // applyMockCompletion() L121 : nœud DEPOT_EPARGNE sans champ "montant" → branche false
    // -----------------------------------------------------------------------

    @Test
    @DisplayName("GET /mock-complete DEPOT_EPARGNE sans champ 'montant' → ignoré (L121 branche node.has(\"montant\")=false)")
    void mockComplete_mockEnabled_depotEpargneSansMontant_ignored() {
        String ref = UUID.randomUUID().toString();
        UUID intentionId = UUID.fromString(ref);

        // type=DEPOT_EPARGNE, a "compteId" mais pas de "montant" → node.has("montant")=false → branche non prise
        String objetsCibles = "[{\"type\":\"DEPOT_EPARGNE\",\"compteId\":\"" + UUID.randomUUID() + "\"}]";

        IntentionPaiement intention = buildFakeIntention(intentionId);
        intention.setObjetsCibles(objetsCibles);

        when(entityManager.find(IntentionPaiement.class, intentionId)).thenReturn(intention);
        doNothing().when(intentionPaiementRepository).persist(any(IntentionPaiement.class));

        given()
                .redirects().follow(false)
                .queryParam("ref", ref)
                .when()
                .get(BASE_PATH + "/mock-complete")
                .then()
                .statusCode(anyOf(equalTo(301), equalTo(302), equalTo(303)))
                .header("Location", containsString("success"));

        verify(intentionPaiementRepository).persist(any(IntentionPaiement.class));
    }

    // -----------------------------------------------------------------------
    // success() L58 : mockEnabled=true + ref non-null et non-blank → branche true déjà couverte
    // Manquante : la branche où ref != null est false — mais ce cas couvre mockEnabled=true + ref=valide
    // Ajout d'un test avec un ref valide UUID mais intention introuvable pour couvrir
    // la sous-condition ref!=null dans le court-circuit (node différent du test existant)
    // -----------------------------------------------------------------------

    @Test
    @DisplayName("GET /success avec wave.mock.enabled=true + ref UUID valide + intention introuvable → redirige success (L58 toutes branches)")
    void waveSuccess_mockEnabled_validRef_intentionNotFound_redirects() {
        String ref = UUID.randomUUID().toString();
        // entityManager.find retourne null → findById=null → LOG.warn + return
        // Ce test s'assure que ref!=null && !ref.isBlank() → true → applyMockCompletion appelé

        given()
                .redirects().follow(false)
                .queryParam("ref", ref)
                .when()
                .get(BASE_PATH + "/success")
                .then()
                .statusCode(anyOf(equalTo(301), equalTo(302), equalTo(303)))
                .header("Location", containsString("success"));
    }

    // -----------------------------------------------------------------------
    // Helpers
    // -----------------------------------------------------------------------

    private IntentionPaiement buildFakeIntention(UUID id) {
        Membre utilisateur = new Membre();
        utilisateur.setId(UUID.randomUUID());

        IntentionPaiement intention = new IntentionPaiement();
        intention.setId(id);
        intention.setStatut(StatutIntentionPaiement.INITIEE);
        intention.setMontantTotal(new BigDecimal("5000"));
        intention.setCodeDevise("XOF");
        intention.setUtilisateur(utilisateur);
        return intention;
    }
}
