package dev.lions.unionflow.server.service;

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

import dev.lions.unionflow.server.api.dto.solidarite.response.DemandeAideResponse;
import dev.lions.unionflow.server.api.dto.solidarite.response.PropositionAideResponse;
import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide;
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
import dev.lions.unionflow.server.api.enums.solidarite.StatutProposition;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@QuarkusTest
class MatchingServiceTest {

    @Inject
    MatchingService matchingService;

    @InjectMock
    PropositionAideService propositionAideService;

    @InjectMock
    DemandeAideService demandeAideService;

    @Test
    @DisplayName("trouverPropositionsCompatibles retourne des propositions triées par score")
    void trouverPropositionsCompatibles_returnsSortedPropositions() {
        UUID demandeId = UUID.randomUUID();
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(demandeId);
        demande.setTypeAide(TypeAide.AIDE_FRAIS_MEDICAUX);
        demande.setMontantDemande(new BigDecimal("10000"));

        PropositionAideResponse prop1 = new PropositionAideResponse();
        prop1.setId(UUID.randomUUID());
        prop1.setTypeAide(TypeAide.AIDE_FRAIS_MEDICAUX);
        prop1.setStatut(StatutProposition.ACTIVE);
        prop1.setEstDisponible(true);
        prop1.setNombreMaxBeneficiaires(10);
        prop1.setNombreBeneficiairesAides(0);
        prop1.setMontantMaximum(new BigDecimal("50000"));
        prop1.setDateCreation(LocalDateTime.now().minusDays(10));
        prop1.setDonneesPersonnalisees(new HashMap<>());

        PropositionAideResponse prop2 = new PropositionAideResponse();
        prop2.setId(UUID.randomUUID());
        prop2.setTypeAide(TypeAide.AIDE_FRAIS_MEDICAUX);
        prop2.setStatut(StatutProposition.ACTIVE);
        prop2.setEstDisponible(true);
        prop2.setNombreMaxBeneficiaires(5);
        prop2.setNombreBeneficiairesAides(2);
        prop2.setMontantMaximum(new BigDecimal("5000"));
        prop2.setDateCreation(LocalDateTime.now().minusDays(5));
        prop2.setDonneesPersonnalisees(new HashMap<>());

        when(propositionAideService.obtenirPropositionsActives(TypeAide.AIDE_FRAIS_MEDICAUX))
                .thenReturn(List.of(prop1, prop2));

        List<PropositionAideResponse> resultats = matchingService.trouverPropositionsCompatibles(demande);

        assertThat(resultats).isNotEmpty();
        assertThat(resultats.get(0).getId()).isEqualTo(prop1.getId());
        assertThat(resultats.get(0).getDonneesPersonnalisees()).containsKey("scoreMatching");
    }

    @Test
    @DisplayName("trouverPropositionsCompatibles avec peu de candidats élargit la recherche par catégorie")
    void trouverPropositionsCompatibles_fewCandidates_expandsSearch() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);

        PropositionAideResponse prop1 = new PropositionAideResponse();
        prop1.setId(UUID.randomUUID());
        prop1.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);
        prop1.setStatut(StatutProposition.ACTIVE);
        prop1.setEstDisponible(true);
        prop1.setNombreMaxBeneficiaires(10);
        prop1.setNombreBeneficiairesAides(0);
        prop1.setDateCreation(LocalDateTime.now());

        when(propositionAideService.obtenirPropositionsActives(TypeAide.AIDE_ALIMENTAIRE))
                .thenReturn(List.of(prop1));
        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(Collections.emptyList());

        List<PropositionAideResponse> resultats = matchingService.trouverPropositionsCompatibles(demande);

        assertThat(resultats).isNotEmpty();
        verify(propositionAideService).rechercherAvecFiltres(any());
    }

    @Test
    @DisplayName("trouverPropositionsCompatibles gère les exceptions et retourne liste vide")
    void trouverPropositionsCompatibles_exception_returnsEmptyList() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AUTRE);

        when(propositionAideService.obtenirPropositionsActives(any()))
                .thenThrow(new RuntimeException("Service error"));

        List<PropositionAideResponse> resultats = matchingService.trouverPropositionsCompatibles(demande);

        assertThat(resultats).isEmpty();
    }

    @Test
    @DisplayName("trouverDemandesCompatibles retourne des demandes triées par score")
    void trouverDemandesCompatibles_returnsSortedDemandes() {
        PropositionAideResponse proposition = new PropositionAideResponse();
        proposition.setId(UUID.randomUUID());
        proposition.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        proposition.setMontantMaximum(new BigDecimal("100000"));

        DemandeAideResponse demande1 = new DemandeAideResponse();
        demande1.setId(UUID.randomUUID());
        demande1.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        demande1.setMontantDemande(new BigDecimal("50000"));
        demande1.setStatut(StatutAide.APPROUVEE);

        DemandeAideResponse demande2 = new DemandeAideResponse();
        demande2.setId(UUID.randomUUID());
        demande2.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        demande2.setMontantDemande(new BigDecimal("80000"));
        demande2.setStatut(StatutAide.EN_ATTENTE);

        when(demandeAideService.rechercherAvecFiltres(any())).thenReturn(List.of(demande1, demande2));

        List<DemandeAideResponse> resultats = matchingService.trouverDemandesCompatibles(proposition);

        assertThat(resultats).isNotEmpty();
        assertThat(resultats.get(0).getDonneesPersonnalisees()).containsKey("scoreMatching");
    }

    @Test
    @DisplayName("trouverDemandesCompatibles gère les exceptions")
    void trouverDemandesCompatibles_exception_returnsEmptyList() {
        PropositionAideResponse proposition = new PropositionAideResponse();
        proposition.setId(UUID.randomUUID());
        proposition.setTypeAide(TypeAide.AUTRE);

        when(demandeAideService.rechercherAvecFiltres(any())).thenThrow(new RuntimeException("Error"));

        List<DemandeAideResponse> resultats = matchingService.trouverDemandesCompatibles(proposition);

        assertThat(resultats).isEmpty();
    }

    @Test
    @DisplayName("rechercherProposantsFinanciers filtre les aides financières")
    void rechercherProposantsFinanciers_financialAid_returnsPropositions() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        demande.setMontantDemande(new BigDecimal("20000"));
        demande.setMontantApprouve(new BigDecimal("15000"));

        PropositionAideResponse prop1 = new PropositionAideResponse();
        prop1.setId(UUID.randomUUID());
        prop1.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        prop1.setMontantMaximum(new BigDecimal("50000"));
        prop1.setMontantTotalVerse(100000.0);
        prop1.setNombreDemandesTraitees(20);
        prop1.setDelaiReponseHeures(20);
        prop1.setDateCreation(LocalDateTime.now());

        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(List.of(prop1));

        List<PropositionAideResponse> resultats = matchingService.rechercherProposantsFinanciers(demande);

        assertThat(resultats).isNotEmpty();
        assertThat(resultats.get(0).getDonneesPersonnalisees()).containsKey("scoreFinancier");
    }

    @Test
    @DisplayName("rechercherProposantsFinanciers retourne liste vide pour aide non financière")
    void rechercherProposantsFinanciers_nonFinancial_returnsEmpty() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);

        List<PropositionAideResponse> resultats = matchingService.rechercherProposantsFinanciers(demande);

        assertThat(resultats).isEmpty();
        verify(propositionAideService, never()).rechercherAvecFiltres(any());
    }

    @Test
    @DisplayName("rechercherProposantsFinanciers utilise montantDemande si montantApprouve est null")
    void rechercherProposantsFinanciers_noApprovedAmount_usesDemandAmount() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        demande.setMontantDemande(new BigDecimal("10000"));

        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(Collections.emptyList());

        matchingService.rechercherProposantsFinanciers(demande);

        verify(propositionAideService).rechercherAvecFiltres(any());
    }

    @Test
    @DisplayName("matchingUrgence ajoute un bonus de score")
    void matchingUrgence_addsBonus() {
        UUID demandeId = UUID.randomUUID();
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(demandeId);
        demande.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);
        demande.setPriorite(PrioriteAide.URGENTE);

        PropositionAideResponse prop = new PropositionAideResponse();
        prop.setId(UUID.randomUUID());
        prop.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);
        prop.setStatut(StatutProposition.ACTIVE);
        prop.setEstDisponible(true);
        prop.setNombreMaxBeneficiaires(100);
        prop.setNombreBeneficiairesAides(0);
        prop.setDateCreation(LocalDateTime.now());
        prop.setDonneesPersonnalisees(new HashMap<>());

        when(propositionAideService.obtenirPropositionsActives(TypeAide.AIDE_ALIMENTAIRE))
                .thenReturn(List.of(prop));
        when(propositionAideService.obtenirPropositionsActives(TypeAide.AUTRE))
                .thenReturn(Collections.emptyList());
        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(Collections.emptyList());

        List<PropositionAideResponse> resultats = matchingService.matchingUrgence(demande);

        assertThat(resultats).isNotEmpty();
        Double score = (Double) resultats.get(0).getDonneesPersonnalisees().get("scoreUrgence");
        assertThat(score).isGreaterThan(20.0);
    }

    @Test
    @DisplayName("matchingUrgence recherche dans plusieurs catégories")
    void matchingUrgence_searchesMultipleCategories() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_FRAIS_MEDICAUX);

        PropositionAideResponse prop1 = new PropositionAideResponse();
        prop1.setId(UUID.randomUUID());
        prop1.setTypeAide(TypeAide.AIDE_FRAIS_MEDICAUX);
        prop1.setStatut(StatutProposition.ACTIVE);
        prop1.setEstDisponible(true);
        prop1.setNombreMaxBeneficiaires(10);
        prop1.setNombreBeneficiairesAides(0);
        prop1.setDateCreation(LocalDateTime.now());

        PropositionAideResponse prop2 = new PropositionAideResponse();
        prop2.setId(UUID.randomUUID());
        prop2.setTypeAide(TypeAide.AUTRE);
        prop2.setStatut(StatutProposition.ACTIVE);
        prop2.setEstDisponible(true);
        prop2.setNombreMaxBeneficiaires(20);
        prop2.setNombreBeneficiairesAides(0);
        prop2.setDateCreation(LocalDateTime.now());

        when(propositionAideService.obtenirPropositionsActives(TypeAide.AIDE_FRAIS_MEDICAUX))
                .thenReturn(List.of(prop1));
        when(propositionAideService.obtenirPropositionsActives(TypeAide.AUTRE))
                .thenReturn(List.of(prop2));
        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(Collections.emptyList());

        List<PropositionAideResponse> resultats = matchingService.matchingUrgence(demande);

        assertThat(resultats).hasSize(2);
        verify(propositionAideService).obtenirPropositionsActives(TypeAide.AIDE_FRAIS_MEDICAUX);
        verify(propositionAideService).obtenirPropositionsActives(TypeAide.AUTRE);
    }

    @Test
    @DisplayName("matchingUrgence filtre les doublons avec distinct()")
    void matchingUrgence_filtersDuplicates() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AUTRE);

        PropositionAideResponse prop = new PropositionAideResponse();
        prop.setId(UUID.randomUUID());
        prop.setTypeAide(TypeAide.AUTRE);
        prop.setStatut(StatutProposition.ACTIVE);
        prop.setEstDisponible(true);
        prop.setNombreMaxBeneficiaires(10);
        prop.setNombreBeneficiairesAides(0);
        prop.setDateCreation(LocalDateTime.now());

        when(propositionAideService.obtenirPropositionsActives(TypeAide.AUTRE))
                .thenReturn(List.of(prop));
        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(Collections.emptyList());

        List<PropositionAideResponse> resultats = matchingService.matchingUrgence(demande);

        assertThat(resultats).hasSize(1);
    }

    // ─── Tests pour les branches non couvertes de calculerScoreCompatibilite ──

    @Test
    @DisplayName("calculerScoreCompatibilite : catégories identiques mais types différents → score += 25")
    void calculerScoreCompatibilite_sameCategory_differentType_addsBonus() {
        // AIDE_ALIMENTAIRE (catégorie "urgence") et HEBERGEMENT_URGENCE (catégorie "urgence")
        // → même catégorie, type différent → branche ligne 280 score += 25.0
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);

        PropositionAideResponse prop = new PropositionAideResponse();
        prop.setId(UUID.randomUUID());
        // Type différent (HEBERGEMENT_URGENCE) mais même catégorie "urgence"
        prop.setTypeAide(TypeAide.HEBERGEMENT_URGENCE);
        prop.setStatut(StatutProposition.ACTIVE);
        prop.setEstDisponible(true);
        prop.setNombreMaxBeneficiaires(10);
        prop.setNombreBeneficiairesAides(5); // 5 * 5.0 = 25 → Math.min(15, 25) = 15 points expérience
        prop.setDateCreation(LocalDateTime.now());
        // noteMoyenne >= 3 et nombreEvaluations >= 3 → couvre ligne 313
        prop.setNoteMoyenne(4.0);
        prop.setNombreEvaluations(5);
        prop.setDonneesPersonnalisees(new HashMap<>());

        when(propositionAideService.obtenirPropositionsActives(TypeAide.AIDE_ALIMENTAIRE))
                .thenReturn(List.of(prop));
        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(Collections.emptyList());

        List<PropositionAideResponse> resultats = matchingService.trouverPropositionsCompatibles(demande);

        // Score = 25 (catégorie) + 15 (expérience) + bonus réputation = >= 30 minimum
        assertThat(resultats).isNotNull();
    }

    @Test
    @DisplayName("calculerScoreCompatibilite : noteMoyenne élevée et évaluations >= 3 → bonus réputation")
    void calculerScoreCompatibilite_withHighNoteMoyenne_addsReputationBonus() {
        // Un typeAide qui nécessite montant → AIDE_FINANCIERE_URGENTE
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_FRAIS_MEDICAUX);

        PropositionAideResponse prop = new PropositionAideResponse();
        prop.setId(UUID.randomUUID());
        prop.setTypeAide(TypeAide.AIDE_FRAIS_MEDICAUX);
        prop.setStatut(StatutProposition.ACTIVE);
        prop.setEstDisponible(true);
        prop.setNombreMaxBeneficiaires(10);
        prop.setNombreBeneficiairesAides(3);
        prop.setDateCreation(LocalDateTime.now());
        // Couvre ligne 313 : noteMoyenne = 4.5, nombreEvaluations = 5
        prop.setNoteMoyenne(4.5);
        prop.setNombreEvaluations(5);
        // Couvre ligne 353 : delaiReponseHeures <= 72
        prop.setDelaiReponseHeures(48);
        // Couvre ligne 402 : delaiReponseHeures > 168 → NON (c'est 48)
        prop.setDonneesPersonnalisees(new HashMap<>());

        when(propositionAideService.obtenirPropositionsActives(TypeAide.AIDE_FRAIS_MEDICAUX))
                .thenReturn(List.of(prop));
        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(Collections.emptyList());

        List<PropositionAideResponse> resultats = matchingService.trouverPropositionsCompatibles(demande);

        assertThat(resultats).isNotNull();
    }

    @Test
    @DisplayName("calculerMalusDelai : délai dépassé et délai réponse > 168h → malus complet")
    void calculerMalusDelai_delaiDepasse_longDelaiReponse_addsMalus() {
        // estDelaiDepasse() = true → dateLimiteTraitement dans le passé
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);
        demande.setDateLimiteTraitement(LocalDateTime.now().minusDays(5)); // passé → délai dépassé

        PropositionAideResponse prop = new PropositionAideResponse();
        prop.setId(UUID.randomUUID());
        prop.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);
        prop.setStatut(StatutProposition.ACTIVE);
        prop.setEstDisponible(true);
        prop.setNombreMaxBeneficiaires(10);
        prop.setNombreBeneficiairesAides(0);
        prop.setDateCreation(LocalDateTime.now());
        // delaiReponseHeures > 168 → couvre ligne 402
        prop.setDelaiReponseHeures(200);
        prop.setDonneesPersonnalisees(new HashMap<>());

        when(propositionAideService.obtenirPropositionsActives(TypeAide.AIDE_ALIMENTAIRE))
                .thenReturn(List.of(prop));
        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(Collections.emptyList());

        // Le malus est appliqué mais n'empêche pas le calcul
        List<PropositionAideResponse> resultats = matchingService.trouverPropositionsCompatibles(demande);

        assertThat(resultats).isNotNull();
    }

    @Test
    @DisplayName("rechercherProposantsFinanciers : tri par scoreFinancier avec 2+ propositions (comparator)")
    void rechercherProposantsFinanciers_multiplePropositions_sortedByFinancialScore() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        demande.setMontantDemande(new BigDecimal("10000"));

        // prop1 : score plus élevé (montant ok, historique)
        PropositionAideResponse prop1 = new PropositionAideResponse();
        prop1.setId(UUID.randomUUID());
        prop1.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        prop1.setStatut(StatutProposition.ACTIVE);
        prop1.setEstDisponible(true);
        prop1.setNombreMaxBeneficiaires(10);
        prop1.setNombreBeneficiairesAides(0);
        prop1.setMontantMaximum(new BigDecimal("50000"));
        prop1.setMontantTotalVerse(100000.0);
        prop1.setNombreDemandesTraitees(20);
        prop1.setDelaiReponseHeures(12);
        prop1.setDateCreation(LocalDateTime.now().minusDays(10));

        // prop2 : score moins élevé
        PropositionAideResponse prop2 = new PropositionAideResponse();
        prop2.setId(UUID.randomUUID());
        prop2.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        prop2.setStatut(StatutProposition.ACTIVE);
        prop2.setEstDisponible(true);
        prop2.setNombreMaxBeneficiaires(5);
        prop2.setNombreBeneficiairesAides(0);
        prop2.setMontantMaximum(new BigDecimal("50000"));
        prop2.setMontantTotalVerse(50000.0);
        prop2.setNombreDemandesTraitees(5);
        prop2.setDelaiReponseHeures(60);
        prop2.setDateCreation(LocalDateTime.now().minusDays(5));

        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(List.of(prop1, prop2));

        List<PropositionAideResponse> resultats = matchingService.rechercherProposantsFinanciers(demande);

        // Le comparateur (lignes 210-212) est exercé car 2+ propositions avec scoreFinancier
        assertThat(resultats).isNotNull();
        assertThat(resultats).hasSizeGreaterThanOrEqualTo(1);
    }

    @Test
    @DisplayName("calculerBonusGeographique : localisation et zonesGeographiques non-null → retourne boostGeographique")
    void calculerBonusGeographique_withLocalisation_returnsBoost() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);
        // Couvre ligne 365 : localisation non-null
        dev.lions.unionflow.server.api.dto.solidarite.LocalisationDTO loc = new dev.lions.unionflow.server.api.dto.solidarite.LocalisationDTO();
        demande.setLocalisation(loc);

        PropositionAideResponse prop = new PropositionAideResponse();
        prop.setId(UUID.randomUUID());
        prop.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);
        prop.setStatut(StatutProposition.ACTIVE);
        prop.setEstDisponible(true);
        prop.setNombreMaxBeneficiaires(10);
        prop.setNombreBeneficiairesAides(0);
        prop.setDateCreation(LocalDateTime.now());
        // zonesGeographiques non-null
        prop.setZonesGeographiques(List.of("Abidjan"));
        prop.setDonneesPersonnalisees(new HashMap<>());

        when(propositionAideService.obtenirPropositionsActives(TypeAide.AIDE_ALIMENTAIRE))
                .thenReturn(List.of(prop));
        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(Collections.emptyList());

        List<PropositionAideResponse> resultats = matchingService.trouverPropositionsCompatibles(demande);

        assertThat(resultats).isNotNull();
    }

    // =========================================================================
    // calculerScoreCompatibilite — branche typeAide == AUTRE → score += 15 (ligne 281-283)
    // =========================================================================

    @Test
    @DisplayName("calculerScoreCompatibilite : typeAide demande ≠ categorie mais proposition=AUTRE → score += 15")
    void calculerScoreCompatibilite_propositionTypeAUTRE_addsBonus15() {
        // Demande de type AIDE_FINANCIERE_URGENTE (catégorie "financier")
        // Proposition de type AUTRE (pas même type, pas même catégorie mais == AUTRE) → score += 15
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        demande.setMontantDemande(new BigDecimal("10000"));

        PropositionAideResponse prop = new PropositionAideResponse();
        prop.setId(UUID.randomUUID());
        prop.setTypeAide(TypeAide.AUTRE); // AUTRE → branche ligne 281: score += 15.0
        prop.setStatut(StatutProposition.ACTIVE);
        prop.setEstDisponible(true);
        prop.setNombreMaxBeneficiaires(10);
        prop.setNombreBeneficiairesAides(0);
        prop.setDateCreation(LocalDateTime.now());
        prop.setDonneesPersonnalisees(new HashMap<>());

        when(propositionAideService.obtenirPropositionsActives(TypeAide.AIDE_FINANCIERE_URGENTE))
                .thenReturn(List.of(prop));
        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(Collections.emptyList());

        List<PropositionAideResponse> resultats = matchingService.trouverPropositionsCompatibles(demande);

        // Score au moins 15 (typeAide AUTRE) — la proposition est dans les résultats si score >= seuil
        assertThat(resultats).isNotNull();
    }

    // =========================================================================
    // calculerScoreCompatibilite — branche montantDemande > montantMaximum → pénalité (ligne 295-298)
    // ET branche isNecessiteMontant && montantMaximum != null && montantDemande null → skip (ligne 291)
    // =========================================================================

    @Test
    @DisplayName("calculerScoreCompatibilite : montantApprouve null et montantDemande null → branch montantDemande null (ligne 291)")
    void calculerScoreCompatibilite_montantDemandeNull_skipsMontantBranch() {
        // TypeAide AIDE_FINANCIERE_URGENTE nécessite montant, mais montantApprouve=null et montantDemande=null
        // → montantDemande = null → branche ligne 291: if (montantDemande != null) skip
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        demande.setMontantApprouve(null);
        demande.setMontantDemande(null); // ← null pour déclencher la branche

        PropositionAideResponse prop = new PropositionAideResponse();
        prop.setId(UUID.randomUUID());
        prop.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        prop.setMontantMaximum(new BigDecimal("50000")); // != null → entre dans le if externe
        prop.setStatut(StatutProposition.ACTIVE);
        prop.setEstDisponible(true);
        prop.setNombreMaxBeneficiaires(10);
        prop.setNombreBeneficiairesAides(0);
        prop.setDateCreation(LocalDateTime.now());
        prop.setDonneesPersonnalisees(new HashMap<>());

        when(propositionAideService.obtenirPropositionsActives(TypeAide.AIDE_FINANCIERE_URGENTE))
                .thenReturn(List.of(prop));
        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(Collections.emptyList());

        List<PropositionAideResponse> resultats = matchingService.trouverPropositionsCompatibles(demande);

        // Pas d'exception — la branche null est correctement gérée
        assertThat(resultats).isNotNull();
    }

    // =========================================================================
    // rechercherProposantsFinanciers — branche montantApprouve null ET montantDemande null → BigDecimal.ZERO
    // =========================================================================

    @Test
    @DisplayName("rechercherProposantsFinanciers avec montantApprouve et montantDemande null → utilise BigDecimal.ZERO (branche ligne 192)")
    void rechercherProposantsFinanciers_bothAmountsNull_usesZero() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        demande.setMontantApprouve(null); // null
        demande.setMontantDemande(null);  // null → ternaire utilise BigDecimal.ZERO (ligne 192)

        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(Collections.emptyList());

        List<PropositionAideResponse> resultats = matchingService.rechercherProposantsFinanciers(demande);

        // Pas d'exception, résultat vide (aucune proposition trouvée)
        assertThat(resultats).isNotNull().isEmpty();
        verify(propositionAideService).rechercherAvecFiltres(any());
    }

    // =========================================================================
    // rechercherParCategorie — lambda$14 ligne 415 :
    // p -> p.getTypeAide().getCategorie().equals(categorie)
    // Déclenché via trouverPropositionsCompatibles quand < 3 candidats directs
    // et rechercherAvecFiltres retourne des propositions de catégorie correspondante
    // =========================================================================

    // =========================================================================
    // L151 — trouverDemandesCompatibles : branche FALSE du filtre score >= 30.0
    // Scénario : demande avec score=0 (type/catégorie incompatibles, montantMaximum=null)
    // =========================================================================

    @Test
    @DisplayName("trouverDemandesCompatibles : demande avec score < 30 est filtrée (L151 branche false)")
    void trouverDemandesCompatibles_lowScore_filteredOut() {
        // Proposition : DON_MATERIEL (category="materielle", necessiteMontant=false)
        // proposition pleine (max=5, aides=5 → peutAccepterBeneficiaires=false)
        PropositionAideResponse proposition = new PropositionAideResponse();
        proposition.setId(UUID.randomUUID());
        proposition.setTypeAide(TypeAide.DON_MATERIEL);
        proposition.setStatut(StatutProposition.ACTIVE);
        proposition.setEstDisponible(true);
        proposition.setNombreMaxBeneficiaires(0); // 0 < 0 = false → peutAccepterBeneficiaires=false → capacity=0
        proposition.setNombreBeneficiairesAides(0); // 0 not > 0 → experience=0
        proposition.setMontantMaximum(null);

        // Demande : AIDE_FINANCIERE_URGENTE (category="financiere")
        // type différent + catégorie différente → type score=0
        // isNecessiteMontant=true, montantMaximum=null → financial=0
        // experience=0 (bénéficiaires=0), capacity=0 (max=0)
        // Score total = 0 < 30 → filtrée
        DemandeAideResponse demandeBasScore = new DemandeAideResponse();
        demandeBasScore.setId(UUID.randomUUID());
        demandeBasScore.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);

        when(demandeAideService.rechercherAvecFiltres(any())).thenReturn(List.of(demandeBasScore));

        List<DemandeAideResponse> resultats = matchingService.trouverDemandesCompatibles(proposition);

        // La demande est filtrée car score < 30 → résultat vide
        assertThat(resultats).isEmpty();
    }

    // =========================================================================
    // L207 — rechercherProposantsFinanciers : branche FALSE du filtre scoreFinancier >= 40.0
    // Scénario : proposition avec scoreFinancier=0 (type incompatible, pas de bonuses)
    // =========================================================================

    @Test
    @DisplayName("rechercherProposantsFinanciers : proposition avec scoreFinancier < 40 est filtrée (L207 branche false)")
    void rechercherProposantsFinanciers_lowFinancialScore_filteredOut() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE); // isFinancier=true, necessiteMontant=true
        demande.setMontantDemande(new BigDecimal("10000"));

        // Proposition : DON_MATERIEL (catégorie différente, montantMaximum=null)
        // scoreCompatibilite = 0 (type+cat différents), scoreFinancier = 0 (pas de bonuses)
        // scoreFinancier = 0 < 40 → filtrée
        PropositionAideResponse propBasScore = new PropositionAideResponse();
        propBasScore.setId(UUID.randomUUID());
        propBasScore.setTypeAide(TypeAide.DON_MATERIEL); // category="materielle" ≠ "financiere"
        propBasScore.setMontantMaximum(null); // pas de financial score
        propBasScore.setNombreMaxBeneficiaires(0); // 0 < 0 = false → peutAccepterBeneficiaires=false → capacity=0
        propBasScore.setNombreBeneficiairesAides(0); // 0 not > 0 → experience=0
        // Pas de montantTotalVerse, nombreDemandesTraitees, delaiReponseHeures → bonuses financiers=0

        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(List.of(propBasScore));

        List<PropositionAideResponse> resultats = matchingService.rechercherProposantsFinanciers(demande);

        // La proposition est filtrée car scoreFinancier < 40 → résultat vide
        assertThat(resultats).isEmpty();
    }

    // =========================================================================
    // L255 — matchingUrgence : branche FALSE du filtre scoreUrgence >= 25.0
    // Scénario : scoreCompatibilite=0 → scoreUrgence = 0+20 = 20 < 25 → filtrée
    // =========================================================================

    @Test
    @DisplayName("matchingUrgence : proposition avec scoreUrgence < 25 est filtrée (L255 branche false)")
    void matchingUrgence_lowUrgenceScore_filteredOut() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE); // necessiteMontant=true, category="financiere"

        // Proposition active/disponible mais score base=0:
        // - type différent ("materielle" ≠ "financiere") → type=0
        // - demande.isNecessiteMontant=true, montantMaximum=null → financial=0
        // - nombreBeneficiairesAides=0 → experience=0 (pas > 0)
        // - nombreMaxBeneficiaires=0, nombreBeneficiairesAides=0 → 0 < 0 = false
        //   → peutAccepterBeneficiaires=false → capacity=0
        // - dateCreation=null → temporal=0, pas de géo, pas de malus
        // score base = 0 → scoreUrgence = 0 + 20 = 20 < 25 → filtrée
        PropositionAideResponse propBasUrgence = new PropositionAideResponse();
        propBasUrgence.setId(UUID.randomUUID());
        propBasUrgence.setTypeAide(TypeAide.DON_MATERIEL); // category="materielle" ≠ "financiere"
        propBasUrgence.setStatut(StatutProposition.ACTIVE);
        propBasUrgence.setEstDisponible(true); // passe isActiveEtDisponible()
        propBasUrgence.setNombreMaxBeneficiaires(0); // 0 < 0 = false → peutAccepterBeneficiaires=false
        propBasUrgence.setNombreBeneficiairesAides(0); // experience: 0 not > 0 → 0
        // montantMaximum=null → financial=0, dateCreation=null → temporal=0

        when(propositionAideService.obtenirPropositionsActives(TypeAide.AIDE_FINANCIERE_URGENTE))
                .thenReturn(List.of(propBasUrgence));
        when(propositionAideService.obtenirPropositionsActives(TypeAide.AUTRE))
                .thenReturn(Collections.emptyList());
        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(Collections.emptyList());

        List<PropositionAideResponse> resultats = matchingService.matchingUrgence(demande);

        // La proposition est filtrée car scoreUrgence = 20 < 25 → résultat vide
        assertThat(resultats).isEmpty();
    }

    @Test
    @DisplayName("rechercherParCategorie via trouverPropositionsCompatibles couvre lambda$14 (filtre catégorie)")
    void rechercherParCategorie_viaTrouver_couvreFilterCategorie() {
        // Demande avec type AIDE_ALIMENTAIRE (catégorie "alimentaire")
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);

        // Moins de 3 candidats directs → rechercherParCategorie() est appelé
        when(propositionAideService.obtenirPropositionsActives(TypeAide.AIDE_ALIMENTAIRE))
                .thenReturn(Collections.emptyList());

        // Retourner une proposition avec typeAide de même catégorie que AIDE_ALIMENTAIRE
        // → déclenche lambda$14 : p.getTypeAide().getCategorie().equals(categorie)
        PropositionAideResponse propCategorie = new PropositionAideResponse();
        propCategorie.setId(UUID.randomUUID());
        propCategorie.setTypeAide(TypeAide.AIDE_ALIMENTAIRE); // Même catégorie
        propCategorie.setStatut(StatutProposition.ACTIVE);
        propCategorie.setEstDisponible(true);
        propCategorie.setNombreMaxBeneficiaires(10);
        propCategorie.setNombreBeneficiairesAides(0);
        propCategorie.setDateCreation(LocalDateTime.now());
        propCategorie.setDonneesPersonnalisees(new HashMap<>());

        // Une proposition de catégorie différente (ne passera pas le filtre mais exécute la lambda)
        PropositionAideResponse propAutreCategorie = new PropositionAideResponse();
        propAutreCategorie.setId(UUID.randomUUID());
        propAutreCategorie.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE); // Catégorie différente
        propAutreCategorie.setStatut(StatutProposition.ACTIVE);
        propAutreCategorie.setEstDisponible(true);
        propAutreCategorie.setNombreMaxBeneficiaires(10);
        propAutreCategorie.setNombreBeneficiairesAides(0);
        propAutreCategorie.setDateCreation(LocalDateTime.now());
        propAutreCategorie.setDonneesPersonnalisees(new HashMap<>());

        when(propositionAideService.rechercherAvecFiltres(any()))
                .thenReturn(List.of(propCategorie, propAutreCategorie));

        // Act — déclenche rechercherParCategorie → lambda$14 évalué pour chaque proposition
        List<PropositionAideResponse> resultats = matchingService.trouverPropositionsCompatibles(demande);

        // Assert — résultat valide (lambda$14 a filtré par catégorie)
        assertThat(resultats).isNotNull();
        verify(propositionAideService).rechercherAvecFiltres(any());
    }

    // =========================================================================
    // calculerScoreFinancier — branches null (lignes 338, 343, 350)
    // Appelée depuis rechercherProposantsFinanciers (TypeAide.isFinancier() = true)
    // =========================================================================

    @Test
    @DisplayName("rechercherProposantsFinanciers avec montantTotalVerse null — skip bonus versements (branche L338 false)")
    void rechercherProposantsFinanciers_montantTotalVerseNull_skipsBonusVersements() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE); // isFinancier() = true
        demande.setMontantDemande(new BigDecimal("10000"));

        // Proposition sans montantTotalVerse (null) → branche L338 false → bonus versements non ajouté
        PropositionAideResponse prop = new PropositionAideResponse();
        prop.setId(UUID.randomUUID());
        prop.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        prop.setStatut(StatutProposition.ACTIVE);
        prop.setEstDisponible(true);
        prop.setNombreMaxBeneficiaires(10);
        prop.setNombreBeneficiairesAides(0);
        prop.setMontantMaximum(new BigDecimal("50000"));
        prop.setDateCreation(LocalDateTime.now());
        prop.setDonneesPersonnalisees(new HashMap<>());
        prop.setMontantTotalVerse(null); // null → branche false : pas de bonus versements

        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(List.of(prop));

        List<PropositionAideResponse> resultats = matchingService.rechercherProposantsFinanciers(demande);

        assertThat(resultats).isNotNull();
        // Le score est calculé sans le bonus de versements (montantTotalVerse null)
        verify(propositionAideService).rechercherAvecFiltres(any());
    }

    @Test
    @DisplayName("rechercherProposantsFinanciers avec nombreDemandesTraitees null — skip bonus fiabilité (branche L343 false)")
    void rechercherProposantsFinanciers_nombreDemandesTraiteesNull_skipsBonusFiabilite() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        demande.setMontantDemande(new BigDecimal("10000"));

        // Proposition sans nombreDemandesTraitees (null) → branche L343 false → bonus fiabilité non ajouté
        PropositionAideResponse prop = new PropositionAideResponse();
        prop.setId(UUID.randomUUID());
        prop.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        prop.setStatut(StatutProposition.ACTIVE);
        prop.setEstDisponible(true);
        prop.setNombreMaxBeneficiaires(10);
        prop.setNombreBeneficiairesAides(0);
        prop.setMontantMaximum(new BigDecimal("50000"));
        prop.setDateCreation(LocalDateTime.now());
        prop.setDonneesPersonnalisees(new HashMap<>());
        prop.setNombreDemandesTraitees(null); // null → branche false : pas de bonus fiabilité

        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(List.of(prop));

        List<PropositionAideResponse> resultats = matchingService.rechercherProposantsFinanciers(demande);

        assertThat(resultats).isNotNull();
        verify(propositionAideService).rechercherAvecFiltres(any());
    }

    @Test
    @DisplayName("rechercherProposantsFinanciers avec delaiReponseHeures null — skip bonus rapidité (branche L350 false)")
    void rechercherProposantsFinanciers_delaiReponseHeuresNull_skipsBonusRapidite() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        demande.setMontantDemande(new BigDecimal("10000"));

        // Proposition sans delaiReponseHeures (null) → branche L350 false et L352 false → aucun bonus rapidité
        PropositionAideResponse prop = new PropositionAideResponse();
        prop.setId(UUID.randomUUID());
        prop.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        prop.setStatut(StatutProposition.ACTIVE);
        prop.setEstDisponible(true);
        prop.setNombreMaxBeneficiaires(10);
        prop.setNombreBeneficiairesAides(0);
        prop.setMontantMaximum(new BigDecimal("50000"));
        prop.setDateCreation(LocalDateTime.now());
        prop.setDonneesPersonnalisees(new HashMap<>());
        prop.setDelaiReponseHeures(null); // null → branche L350 false et L352 false : pas de bonus rapidité

        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(List.of(prop));

        List<PropositionAideResponse> resultats = matchingService.rechercherProposantsFinanciers(demande);

        assertThat(resultats).isNotNull();
        verify(propositionAideService).rechercherAvecFiltres(any());
    }

    @Test
    @DisplayName("rechercherProposantsFinanciers avec delaiReponseHeures=48 (>24, <=72) — bonus rapidité partiel (L352 true)")
    void rechercherProposantsFinanciers_delaiReponseHeures48_bonusRapiditePartiel() {
        // Couvre L352: else if (delaiReponseHeures != null && delaiReponseHeures <= 72) → score += 5.0
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        demande.setMontantDemande(new BigDecimal("10000"));

        PropositionAideResponse prop = new PropositionAideResponse();
        prop.setId(UUID.randomUUID());
        prop.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        prop.setStatut(StatutProposition.ACTIVE);
        prop.setEstDisponible(true);
        prop.setNombreMaxBeneficiaires(10);
        prop.setNombreBeneficiairesAides(0);
        prop.setMontantMaximum(new BigDecimal("50000"));
        prop.setDateCreation(LocalDateTime.now());
        prop.setDonneesPersonnalisees(new HashMap<>());
        prop.setDelaiReponseHeures(48); // > 24 (L350 false) et <= 72 (L352 true) → bonus +5

        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(List.of(prop));

        List<PropositionAideResponse> resultats = matchingService.rechercherProposantsFinanciers(demande);

        assertThat(resultats).isNotNull();
        verify(propositionAideService).rechercherAvecFiltres(any());
    }

    @Test
    @DisplayName("calculerScoreCompatibilite — typeAide differ + categorie differ + not AUTRE → aucun bonus type (L281 false)")
    void calculerScoreCompatibilite_typeDiffer_categorieDiffer_notAutre_noBonusType() {
        // Couvre L281 false: type differ + category differ + typeAide != AUTRE → no bonus for type match
        // demande: AIDE_FINANCIERE_URGENTE (financiere), prop: DON_MATERIEL (materielle, not AUTRE)
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE); // category = financiere
        demande.setMontantDemande(new BigDecimal("5000"));

        PropositionAideResponse prop = new PropositionAideResponse();
        prop.setId(UUID.randomUUID());
        prop.setTypeAide(TypeAide.DON_MATERIEL); // category = materielle, not AUTRE
        prop.setStatut(StatutProposition.ACTIVE);
        prop.setEstDisponible(true);
        prop.setNombreMaxBeneficiaires(10);
        prop.setNombreBeneficiairesAides(0);
        prop.setDateCreation(LocalDateTime.now());
        prop.setDonneesPersonnalisees(new HashMap<>());

        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(List.of(prop));

        // trouverPropositionsCompatibles calls calculerScoreCompatibilite internally
        List<PropositionAideResponse> resultats = matchingService.trouverPropositionsCompatibles(demande);

        assertThat(resultats).isNotNull();
        // Score = 0 (no type match) + financial part + others → may be < 30 threshold, so filtered out
        // Either way, the method runs without error and L281 false branch is hit
    }

    // =========================================================================
    // L70 : trouverPropositionsCompatibles avec >= 3 candidats → pas d'expansion (branche false)
    // =========================================================================

    @Test
    @DisplayName("trouverPropositionsCompatibles avec >= 3 candidats → pas d'élargissement (branche L70 false)")
    void trouverPropositionsCompatibles_threePlusCandidats_noExpansion() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);

        // 3 propositions → size() >= 3 → branche false → pas d'appel rechercherParCategorie
        PropositionAideResponse p1 = creerProposition(TypeAide.AIDE_ALIMENTAIRE);
        PropositionAideResponse p2 = creerProposition(TypeAide.AIDE_ALIMENTAIRE);
        PropositionAideResponse p3 = creerProposition(TypeAide.AIDE_ALIMENTAIRE);

        when(propositionAideService.obtenirPropositionsActives(TypeAide.AIDE_ALIMENTAIRE))
                .thenReturn(List.of(p1, p2, p3));

        List<PropositionAideResponse> resultats = matchingService.trouverPropositionsCompatibles(demande);

        assertThat(resultats).isNotNull();
        // Pas d'appel à rechercherAvecFiltres car size >= 3
        verify(propositionAideService, never()).rechercherAvecFiltres(any());
    }

    private PropositionAideResponse creerProposition(TypeAide typeAide) {
        PropositionAideResponse prop = new PropositionAideResponse();
        prop.setId(UUID.randomUUID());
        prop.setTypeAide(typeAide);
        prop.setStatut(StatutProposition.ACTIVE);
        prop.setEstDisponible(true);
        prop.setNombreMaxBeneficiaires(10);
        prop.setNombreBeneficiairesAides(0);
        prop.setDateCreation(LocalDateTime.now());
        prop.setDonneesPersonnalisees(new HashMap<>());
        return prop;
    }

    // =========================================================================
    // L144 : trouverDemandesCompatibles avec demande ayant donneesPersonnalisees non-null
    // → branche false de `if (demande.getDonneesPersonnalisees() == null)`
    // =========================================================================

    @Test
    @DisplayName("trouverDemandesCompatibles avec demande.donneesPersonnalisees non-null → branche L144 false")
    void trouverDemandesCompatibles_donneesPersonnaliseesNonNull_brancheFalse() {
        PropositionAideResponse proposition = new PropositionAideResponse();
        proposition.setId(UUID.randomUUID());
        proposition.setTypeAide(TypeAide.AIDE_FRAIS_MEDICAUX);
        proposition.setMontantMaximum(new BigDecimal("50000"));

        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_FRAIS_MEDICAUX);
        demande.setMontantDemande(new BigDecimal("30000"));
        // donneesPersonnalisees non-null → branche L144 false (pas de setDonneesPersonnalisees)
        Map<String, Object> donneesExistantes = new HashMap<>();
        donneesExistantes.put("existingKey", "value");
        demande.setDonneesPersonnalisees(donneesExistantes);

        when(demandeAideService.rechercherAvecFiltres(any())).thenReturn(List.of(demande));

        List<DemandeAideResponse> resultats = matchingService.trouverDemandesCompatibles(proposition);

        assertThat(resultats).isNotNull();
        // donneesPersonnalisees non-null → skip setDonneesPersonnalisees (L144 false)
    }

    // =========================================================================
    // L311 : noteMoyenne non-null mais nombreEvaluations null → && short-circuits (branche missing)
    // L312 : nombreEvaluations < 3 → false branch
    // =========================================================================

    @Test
    @DisplayName("calculerScoreCompatibilite : noteMoyenne non-null mais nombreEvaluations null → skip réputation (L311 branch)")
    void calculerScoreCompatibilite_noteMoyenneNonNull_nombreEvaluationsNull_skipReputation() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);

        PropositionAideResponse prop = creerProposition(TypeAide.AIDE_ALIMENTAIRE);
        prop.setNoteMoyenne(4.5); // non-null
        prop.setNombreEvaluations(null); // null → && short-circuit L311 → skip L313

        when(propositionAideService.obtenirPropositionsActives(TypeAide.AIDE_ALIMENTAIRE))
                .thenReturn(List.of(prop, creerProposition(TypeAide.AIDE_ALIMENTAIRE), creerProposition(TypeAide.AIDE_ALIMENTAIRE)));

        List<PropositionAideResponse> resultats = matchingService.trouverPropositionsCompatibles(demande);
        assertThat(resultats).isNotNull();
    }

    @Test
    @DisplayName("calculerScoreCompatibilite : nombreEvaluations < 3 → skip réputation (branche L312 false)")
    void calculerScoreCompatibilite_nombreEvaluationsMoins3_skipReputation() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);

        PropositionAideResponse prop = creerProposition(TypeAide.AIDE_ALIMENTAIRE);
        prop.setNoteMoyenne(4.5);
        prop.setNombreEvaluations(2); // < 3 → false → skip réputation bonus (L312 false)

        when(propositionAideService.obtenirPropositionsActives(TypeAide.AIDE_ALIMENTAIRE))
                .thenReturn(List.of(prop, creerProposition(TypeAide.AIDE_ALIMENTAIRE), creerProposition(TypeAide.AIDE_ALIMENTAIRE)));

        List<PropositionAideResponse> resultats = matchingService.trouverPropositionsCompatibles(demande);
        assertThat(resultats).isNotNull();
    }

    // =========================================================================
    // L363 : calculerBonusGeographique — localisation null → branche false (retourne 0.0)
    // =========================================================================

    @Test
    @DisplayName("calculerBonusGeographique : localisation null → retourne 0.0 (branche L363 false)")
    void calculerBonusGeographique_localisationNull_returnsZero() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);
        demande.setLocalisation(null); // null → condition false → retourne 0.0

        PropositionAideResponse prop = creerProposition(TypeAide.AIDE_ALIMENTAIRE);
        prop.setZonesGeographiques(List.of("Abidjan"));

        when(propositionAideService.obtenirPropositionsActives(TypeAide.AIDE_ALIMENTAIRE))
                .thenReturn(List.of(prop, creerProposition(TypeAide.AIDE_ALIMENTAIRE), creerProposition(TypeAide.AIDE_ALIMENTAIRE)));

        List<PropositionAideResponse> resultats = matchingService.trouverPropositionsCompatibles(demande);
        assertThat(resultats).isNotNull();
    }

    // =========================================================================
    // L382 : calculerBonusTemporel — joursDepuisCreation > 30 → false branch (pas de bonus temporel)
    // =========================================================================

    @Test
    @DisplayName("calculerBonusTemporel : dateCreation > 30 jours → pas de bonus temporel (branche L382 false)")
    void calculerBonusTemporel_dateCreationOld_noBonusTemporel() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);

        PropositionAideResponse prop = creerProposition(TypeAide.AIDE_ALIMENTAIRE);
        // dateCreation > 30 jours → joursDepuisCreation > 30 → branche false → pas de bonus
        prop.setDateCreation(LocalDateTime.now().minusDays(45));

        when(propositionAideService.obtenirPropositionsActives(TypeAide.AIDE_ALIMENTAIRE))
                .thenReturn(List.of(prop, creerProposition(TypeAide.AIDE_ALIMENTAIRE), creerProposition(TypeAide.AIDE_ALIMENTAIRE)));

        List<PropositionAideResponse> resultats = matchingService.trouverPropositionsCompatibles(demande);
        assertThat(resultats).isNotNull();
    }

    // =========================================================================
    // L338: montantTotalVerse != null but == 0 → second condition false (branch missing)
    // =========================================================================

    @Test
    @DisplayName("calculerScoreFinancier: montantTotalVerse positif + nombreDemandesTraitees positif → branches L338+L343 true (bonus ajouté)")
    void calculerScoreFinancier_montantTotalVersePositif_nombreDemandesPositif_branchesTrue() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_FRAIS_MEDICAUX);
        demande.setMontantDemande(new BigDecimal("10000"));

        PropositionAideResponse prop = creerProposition(TypeAide.AIDE_FRAIS_MEDICAUX);
        prop.setMontantTotalVerse(500000.0); // non-null et > 0 → L338: true → bonus ajouté
        prop.setNombreDemandesTraitees(5); // non-null et > 0 → L343: true → bonus ajouté

        when(propositionAideService.obtenirPropositionsActives(TypeAide.AIDE_FRAIS_MEDICAUX))
                .thenReturn(List.of(prop, creerProposition(TypeAide.AIDE_FRAIS_MEDICAUX), creerProposition(TypeAide.AIDE_FRAIS_MEDICAUX)));

        List<PropositionAideResponse> resultats = matchingService.trouverPropositionsCompatibles(demande);
        assertThat(resultats).isNotNull();
    }

    // =========================================================================
    // L352: delaiReponseHeures != null but > 24 and <= 72 → second elif branch
    // =========================================================================

    @Test
    @DisplayName("calculerScoreFinancier: delaiReponseHeures=48 (25-72) → branche elif L352 true (bonus 5)")
    void calculerScoreFinancier_delaiReponse48h_branchemMoyen() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_FRAIS_MEDICAUX);

        PropositionAideResponse prop = creerProposition(TypeAide.AIDE_FRAIS_MEDICAUX);
        prop.setDelaiReponseHeures(48); // > 24 and <= 72 → elif true → score += 5

        when(propositionAideService.obtenirPropositionsActives(TypeAide.AIDE_FRAIS_MEDICAUX))
                .thenReturn(List.of(prop, creerProposition(TypeAide.AIDE_FRAIS_MEDICAUX), creerProposition(TypeAide.AIDE_FRAIS_MEDICAUX)));

        List<PropositionAideResponse> resultats = matchingService.trouverPropositionsCompatibles(demande);
        assertThat(resultats).isNotNull();
    }

    // =========================================================================
    // L363: localisation non-null AND zonesGeographiques non-null → true branch
    // =========================================================================

    @Test
    @DisplayName("calculerBonusGeographique: localisation et zonesGeographiques non-null → branche L363 true")
    void calculerBonusGeographique_localisationEtZonesNonNull_brancheTrue() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_ALIMENTAIRE);
        dev.lions.unionflow.server.api.dto.solidarite.LocalisationDTO localisation =
                new dev.lions.unionflow.server.api.dto.solidarite.LocalisationDTO();
        localisation.setVille("Abidjan");
        demande.setLocalisation(localisation); // non-null → première condition true

        PropositionAideResponse prop = creerProposition(TypeAide.AIDE_ALIMENTAIRE);
        prop.setZonesGeographiques(List.of("Abidjan", "Dakar")); // non-null → condition true → return boostGeographique

        when(propositionAideService.obtenirPropositionsActives(TypeAide.AIDE_ALIMENTAIRE))
                .thenReturn(List.of(prop, creerProposition(TypeAide.AIDE_ALIMENTAIRE), creerProposition(TypeAide.AIDE_ALIMENTAIRE)));

        List<PropositionAideResponse> resultats = matchingService.trouverPropositionsCompatibles(demande);
        assertThat(resultats).isNotNull();
    }

    // =========================================================================
    // L338: montantTotalVerse != null but == 0 → false branch (not > 0)
    // L343: nombreDemandesTraitees != null but == 0 → false branch
    // L352: delaiReponseHeures != null and > 72 → false branch (not <= 72)
    // =========================================================================

    @Test
    @DisplayName("calculerScoreFinancier: montantTotalVerse=0 → L338 false (non-null mais pas > 0)")
    void calculerScoreFinancier_montantTotalVerseZero_L338FalseNotGreaterZero() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        demande.setMontantDemande(new BigDecimal("5000"));

        PropositionAideResponse prop = new PropositionAideResponse();
        prop.setId(UUID.randomUUID());
        prop.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        prop.setStatut(StatutProposition.ACTIVE);
        prop.setEstDisponible(true);
        prop.setNombreMaxBeneficiaires(10);
        prop.setNombreBeneficiairesAides(0);
        prop.setMontantMaximum(new BigDecimal("50000"));
        prop.setDateCreation(LocalDateTime.now());
        prop.setDonneesPersonnalisees(new HashMap<>());
        prop.setMontantTotalVerse(0.0); // non-null but == 0 → L338: > 0 = false → no bonus

        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(List.of(prop));

        List<PropositionAideResponse> resultats = matchingService.rechercherProposantsFinanciers(demande);
        assertThat(resultats).isNotNull();
    }

    @Test
    @DisplayName("calculerScoreFinancier: nombreDemandesTraitees=0 → L343 false (non-null mais pas > 0)")
    void calculerScoreFinancier_nombreDemandesTraiteesZero_L343FalseNotGreaterZero() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        demande.setMontantDemande(new BigDecimal("5000"));

        PropositionAideResponse prop = new PropositionAideResponse();
        prop.setId(UUID.randomUUID());
        prop.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        prop.setStatut(StatutProposition.ACTIVE);
        prop.setEstDisponible(true);
        prop.setNombreMaxBeneficiaires(10);
        prop.setNombreBeneficiairesAides(0);
        prop.setMontantMaximum(new BigDecimal("50000"));
        prop.setDateCreation(LocalDateTime.now());
        prop.setDonneesPersonnalisees(new HashMap<>());
        prop.setNombreDemandesTraitees(0); // non-null but == 0 → L343: > 0 = false → no bonus

        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(List.of(prop));

        List<PropositionAideResponse> resultats = matchingService.rechercherProposantsFinanciers(demande);
        assertThat(resultats).isNotNull();
    }

    @Test
    @DisplayName("calculerScoreFinancier: delaiReponseHeures=100 (>72) → L352 false branch (non-null, > 72)")
    void calculerScoreFinancier_delaiReponseHeures100_L352FalseNotLessEq72() {
        DemandeAideResponse demande = new DemandeAideResponse();
        demande.setId(UUID.randomUUID());
        demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        demande.setMontantDemande(new BigDecimal("5000"));

        PropositionAideResponse prop = new PropositionAideResponse();
        prop.setId(UUID.randomUUID());
        prop.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
        prop.setStatut(StatutProposition.ACTIVE);
        prop.setEstDisponible(true);
        prop.setNombreMaxBeneficiaires(10);
        prop.setNombreBeneficiairesAides(0);
        prop.setMontantMaximum(new BigDecimal("50000"));
        prop.setDateCreation(LocalDateTime.now());
        prop.setDonneesPersonnalisees(new HashMap<>());
        prop.setDelaiReponseHeures(100); // non-null, > 72 → L350 false (>24), L352 false (>72) → no bonus

        when(propositionAideService.rechercherAvecFiltres(any())).thenReturn(List.of(prop));

        List<PropositionAideResponse> resultats = matchingService.rechercherProposantsFinanciers(demande);
        assertThat(resultats).isNotNull();
    }
}
