package dev.lions.unionflow.server.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;

import dev.lions.unionflow.server.api.dto.solidarite.response.DemandeAideResponse;
import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide;
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
import dev.lions.unionflow.server.entity.Cotisation;
import dev.lions.unionflow.server.entity.DemandeAide;
import dev.lions.unionflow.server.entity.Evenement;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.mapper.DemandeAideMapper;
import dev.lions.unionflow.server.repository.CotisationRepository;
import dev.lions.unionflow.server.repository.DemandeAideRepository;
import dev.lions.unionflow.server.repository.EvenementRepository;
import dev.lions.unionflow.server.repository.FeedbackEvenementRepository;
import dev.lions.unionflow.server.repository.InscriptionEvenementRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
 * Tests complémentaires avec mocks pour ExportService, EvenementService et DemandeAideService
 * — cas limites de cotisation absente, permissions, organisation null, titre dupliqué.
 */
@QuarkusTest
class JacocoBranchMockCoverageTest {

    // ===========================================================================
    // CotisationRepository mock — utilisé par ExportService
    // ===========================================================================

    @Inject
    ExportService exportService;

    @InjectMock
    CotisationRepository cotisationRepository;

    // ===========================================================================
    // DemandeAideRepository mock — utilisé par DemandeAideService
    // ===========================================================================

    @Inject
    DemandeAideService demandeAideService;

    @InjectMock
    DemandeAideRepository demandeAideRepository;

    @InjectMock
    DemandeAideMapper demandeAideMapper;

    // ===========================================================================
    // EvenementService mocks
    // ===========================================================================

    @Inject
    EvenementService evenementService;

    @InjectMock
    KeycloakService keycloakService;

    @InjectMock
    EvenementRepository evenementRepository;

    @InjectMock
    MembreRepository membreRepository;

    @InjectMock
    OrganisationRepository organisationRepository;

    @InjectMock
    InscriptionEvenementRepository inscriptionRepository;

    @InjectMock
    FeedbackEvenementRepository feedbackRepository;

    @BeforeEach
    void setupDefaultMocks() {
        when(keycloakService.getCurrentUserEmail()).thenReturn("test@unionflow.dev");
        when(keycloakService.isAuthenticated()).thenReturn(true);
        when(keycloakService.hasRole(any(String.class))).thenReturn(false);
        when(keycloakService.hasAnyRole(any(String[].class))).thenReturn(false);
    }

    // ===========================================================================
    // ExportService.genererRecuPaiement:98 — 5I, 1B
    // branche cotisationOpt.isEmpty() = true → retourne "Cotisation non trouvée"
    // ===========================================================================

    @Test
    @DisplayName("genererRecuPaiement — cotisation non trouvée → retourne message 'Cotisation non trouvée' (branche isEmpty=true)")
    void genererRecuPaiement_cotisationNonTrouvee_retourneMessageErreur() {
        UUID idInexistant = UUID.randomUUID();
        when(cotisationRepository.findByIdOptional(idInexistant)).thenReturn(Optional.empty());

        byte[] result = exportService.genererRecuPaiement(idInexistant);

        assertThat(result).isNotNull();
        // Utiliser le charset par défaut pour être cohérent avec getBytes() sans charset
        String message = new String(result);
        assertThat(message).contains("Cotisation");
    }

    @Test
    @DisplayName("genererRecuPaiement — cotisation trouvée avec membre non null → section membre affichée")
    void genererRecuPaiement_cotisationAvecMembre_sectionMembreAffichee() {
        UUID id = UUID.randomUUID();
        Membre membre = new Membre();
        membre.setNom("Koné");
        membre.setPrenom("Awa");
        membre.setNumeroMembre("M-RECU-001");

        Cotisation c = new Cotisation();
        c.setId(id);
        c.setNumeroReference("REF-RECU-001");
        c.setMembre(membre);
        c.setTypeCotisation("ANNUELLE");
        c.setPeriode("2026");
        c.setMontantDu(BigDecimal.valueOf(10000));
        c.setMontantPaye(BigDecimal.valueOf(10000));
        c.setStatut("PAYEE");
        c.setDatePaiement(LocalDateTime.of(2026, 1, 15, 10, 0));

        when(cotisationRepository.findByIdOptional(id)).thenReturn(Optional.of(c));

        byte[] result = exportService.genererRecuPaiement(id);
        String contenu = new String(result, java.nio.charset.StandardCharsets.UTF_8);

        assertThat(contenu).contains("REÇU DE PAIEMENT");
        assertThat(contenu).contains("Koné");
        assertThat(contenu).contains("Awa");
        assertThat(contenu).contains("M-RECU-001");
        assertThat(contenu).contains("ANNUELLE");
    }

    // ===========================================================================
    // ExportService.exporterCotisationsCSV:38 — 1I, 1B
    // branche cotisationOpt.isPresent() = false → ligne CSV ignorée
    // ===========================================================================

    @Test
    @DisplayName("exporterCotisationsCSV — ID absent du repo → ligne CSV ignorée (branche isPresent=false)")
    void exporterCotisationsCSV_idAbsent_ligneCsvIgnoree() {
        UUID idAbsent = UUID.randomUUID();
        when(cotisationRepository.findByIdOptional(idAbsent)).thenReturn(Optional.empty());

        byte[] csv = exportService.exporterCotisationsCSV(List.of(idAbsent));
        String contenu = new String(csv, java.nio.charset.StandardCharsets.UTF_8);

        // Header présent
        assertThat(contenu).contains("Numéro Référence");
        // Aucune ligne de données (seulement header)
        String[] lines = contenu.trim().split("\n");
        assertThat(lines).hasSize(1);
    }

    @Test
    @DisplayName("exporterCotisationsCSV — mélange présent/absent → seulement la cotisation présente exportée")
    void exporterCotisationsCSV_melangePresentsAbsents_seulementPresentsExportes() {
        UUID idPresent = UUID.randomUUID();
        UUID idAbsent = UUID.randomUUID();

        Cotisation c = new Cotisation();
        c.setId(idPresent);
        c.setNumeroReference("REF-CSV-001");
        c.setStatut("EN_ATTENTE");
        c.setMontantDu(BigDecimal.valueOf(5000));
        c.setMontantPaye(BigDecimal.ZERO);

        when(cotisationRepository.findByIdOptional(idPresent)).thenReturn(Optional.of(c));
        when(cotisationRepository.findByIdOptional(idAbsent)).thenReturn(Optional.empty());

        byte[] csv = exportService.exporterCotisationsCSV(List.of(idPresent, idAbsent));
        String contenu = new String(csv, java.nio.charset.StandardCharsets.UTF_8);

        assertThat(contenu).contains("REF-CSV-001");
        // Header + 1 ligne de données seulement
        String[] lines = contenu.trim().split("\n");
        assertThat(lines).hasSize(2);
    }

    // ===========================================================================
    // EvenementService.supprimerEvenement:204 — 5I, 1B
    // ===========================================================================

    @Test
    @DisplayName("supprimerEvenement — rôle ORGANISATEUR_EVENEMENT + aucun inscrit → suppression logique réussie")
    void supprimerEvenement_roleOrganisateurEvenement_suppressionReussie() {
        UUID eventId = UUID.randomUUID();
        Evenement e = Evenement.builder()
                .titre("Event Suppression Org " + UUID.randomUUID())
                .dateDebut(LocalDateTime.now().plusDays(5))
                .statut("PLANIFIE")
                .build();
        e.setId(eventId);
        e.setActif(true);
        // getNombreInscrits() → taille de inscriptions (vide par builder → 0)

        when(keycloakService.hasRole("ADMIN")).thenReturn(false);
        when(keycloakService.hasRole("ORGANISATEUR_EVENEMENT")).thenReturn(true);
        when(evenementRepository.findByIdOptional(eventId)).thenReturn(Optional.of(e));
        when(evenementRepository.update(any(Evenement.class))).thenReturn(e);

        assertThatCode(() -> evenementService.supprimerEvenement(eventId))
                .doesNotThrowAnyException();

        assertThat(e.getActif()).isFalse();
    }

    @Test
    @DisplayName("supprimerEvenement — rôle ADMIN + aucun inscrit → setActif(false) et update appelés")
    void supprimerEvenement_roleAdmin_aucunInscrit_setActifFalse() {
        UUID eventId = UUID.randomUUID();
        Evenement e = Evenement.builder()
                .titre("Event Admin Suppr " + UUID.randomUUID())
                .dateDebut(LocalDateTime.now().plusDays(3))
                .statut("PLANIFIE")
                .build();
        e.setId(eventId);
        e.setActif(true);

        when(keycloakService.hasRole("ADMIN")).thenReturn(true);
        when(evenementRepository.findByIdOptional(eventId)).thenReturn(Optional.of(e));
        when(evenementRepository.update(any(Evenement.class))).thenReturn(e);

        assertThatCode(() -> evenementService.supprimerEvenement(eventId))
                .doesNotThrowAnyException();

        assertThat(e.getActif()).isFalse();
    }

    @Test
    @DisplayName("supprimerEvenement — événement non trouvé → NotFoundException")
    void supprimerEvenement_nonTrouve_notFoundException() {
        UUID idInexistant = UUID.randomUUID();
        when(evenementRepository.findByIdOptional(idInexistant)).thenReturn(Optional.empty());

        assertThatThrownBy(() -> evenementService.supprimerEvenement(idInexistant))
                .isInstanceOf(jakarta.ws.rs.NotFoundException.class)
                .hasMessageContaining("Événement non trouvé");
    }

    @Test
    @DisplayName("supprimerEvenement — pas de permissions (non créateur, non admin) → SecurityException")
    void supprimerEvenement_sansPermissions_securityException() {
        UUID eventId = UUID.randomUUID();
        Evenement e = Evenement.builder()
                .titre("Event No Perm " + UUID.randomUUID())
                .dateDebut(LocalDateTime.now().plusDays(3))
                .statut("PLANIFIE")
                .build();
        e.setId(eventId);
        e.setActif(true);
        e.setCreePar("autre@test.com"); // pas l'utilisateur courant

        when(keycloakService.hasRole("ADMIN")).thenReturn(false);
        when(keycloakService.hasRole("ORGANISATEUR_EVENEMENT")).thenReturn(false);
        when(evenementRepository.findByIdOptional(eventId)).thenReturn(Optional.of(e));

        assertThatThrownBy(() -> evenementService.supprimerEvenement(eventId))
                .isInstanceOf(SecurityException.class)
                .hasMessageContaining("permissions");
    }

    // ===========================================================================
    // EvenementService.creerEvenement:68 — 3I, 2B
    // ===========================================================================

    @Test
    @DisplayName("creerEvenement — organisation null → pas de vérification d'unicité du titre (branche if org!=null = false)")
    void creerEvenement_organisationNull_pasDeVerifUnicite() {
        Evenement e = Evenement.builder()
                .titre("Event Sans Org " + UUID.randomUUID())
                .dateDebut(LocalDateTime.now().plusDays(2))
                .build();
        // organisation = null → le if `evenement.getOrganisation() != null` est false
        // → findByTitre() n'est pas appelé

        doNothing().when(evenementRepository).persist(any(Evenement.class));

        Evenement result = evenementService.creerEvenement(e);

        assertThat(result).isNotNull();
        assertThat(result.getStatut()).isEqualTo("PLANIFIE");
        assertThat(result.getActif()).isTrue();
        assertThat(result.getVisiblePublic()).isTrue();
        // @Builder.Default inscriptionRequise = false → la valeur n'est pas null → service ne la change pas
        assertThat(result.getInscriptionRequise()).isFalse();
    }

    @Test
    @DisplayName("creerEvenement — titre existant dans la même organisation → IllegalArgumentException")
    void creerEvenement_titreDejaExistantMemeOrg_throwsIllegalArgument() {
        UUID orgId = UUID.randomUUID();
        Organisation org = new Organisation();
        org.setId(orgId);

        String titre = "Titre Dupliqué " + UUID.randomUUID();

        Evenement existant = Evenement.builder()
                .titre(titre)
                .dateDebut(LocalDateTime.now().plusDays(3))
                .statut("PLANIFIE")
                .build();
        existant.setId(UUID.randomUUID());
        existant.setOrganisation(org);

        Evenement nouvel = Evenement.builder()
                .titre(titre)
                .dateDebut(LocalDateTime.now().plusDays(5))
                .build();
        nouvel.setOrganisation(org); // même orgId → exception

        when(evenementRepository.findByTitre(titre)).thenReturn(Optional.of(existant));

        assertThatThrownBy(() -> evenementService.creerEvenement(nouvel))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("existe déjà");
    }

    @Test
    @DisplayName("creerEvenement — titre existant mais organisation différente → création autorisée (branche else du check unicité)")
    void creerEvenement_titreExistantOrgDifferente_creationAutorisee() {
        UUID orgId1 = UUID.randomUUID();
        UUID orgId2 = UUID.randomUUID();

        Organisation orgExistante = new Organisation();
        orgExistante.setId(orgId1);

        Organisation orgNouvelle = new Organisation();
        orgNouvelle.setId(orgId2);

        String titre = "Titre Autre Org " + UUID.randomUUID();

        Evenement existant = Evenement.builder()
                .titre(titre)
                .dateDebut(LocalDateTime.now().plusDays(3))
                .statut("PLANIFIE")
                .build();
        existant.setId(UUID.randomUUID());
        existant.setOrganisation(orgExistante); // org1

        Evenement nouvel = Evenement.builder()
                .titre(titre)
                .dateDebut(LocalDateTime.now().plusDays(5))
                .build();
        nouvel.setOrganisation(orgNouvelle); // org2 ≠ org1 → pas de doublon

        when(evenementRepository.findByTitre(titre)).thenReturn(Optional.of(existant));
        doNothing().when(evenementRepository).persist(any(Evenement.class));

        // Doit créer sans lever d'exception
        assertThatCode(() -> evenementService.creerEvenement(nouvel))
                .doesNotThrowAnyException();
    }

    @Test
    @DisplayName("creerEvenement — statut/actif/visiblePublic/inscriptionRequise déjà renseignés → valeurs préservées (branches if==null false)")
    void creerEvenement_champsDejaRenseignes_branchesIfNullFalse() {
        Evenement e = Evenement.builder()
                .titre("Event Champs Renseignes " + UUID.randomUUID())
                .dateDebut(LocalDateTime.now().plusDays(1))
                .statut("CONFIRME")   // != null → branche if(statut==null) = false → "PLANIFIE" pas assigné
                .build();
        e.setActif(false);            // != null → if(actif==null) = false
        e.setVisiblePublic(false);    // != null → if(visiblePublic==null) = false
        e.setInscriptionRequise(false); // != null

        doNothing().when(evenementRepository).persist(any(Evenement.class));

        Evenement result = evenementService.creerEvenement(e);

        assertThat(result.getStatut()).isEqualTo("CONFIRME");
        assertThat(result.getActif()).isFalse();
        assertThat(result.getVisiblePublic()).isFalse();
        assertThat(result.getInscriptionRequise()).isFalse();
    }

    // ===========================================================================
    // DemandeAideService.correspondAuxFiltres:317 — 4I, 4B
    // via rechercherAvecFiltres()
    // ===========================================================================

    @Test
    @DisplayName("correspondAuxFiltres — filtre demandeurId avec membreDemandeurId null → demande exclue")
    void correspondAuxFiltres_demandeurIdNull_demandeExclue() {
        UUID orgId = UUID.randomUUID();
        UUID demandeurId = UUID.randomUUID();

        DemandeAideResponse response = new DemandeAideResponse();
        response.setAssociationId(orgId);
        response.setStatut(StatutAide.EN_ATTENTE);
        response.setMembreDemandeurId(null); // null → branche `getMembreDemandeurId() == null` = true → return false

        DemandeAide entity = new DemandeAide();
        when(demandeAideRepository.listAll()).thenReturn(List.of(entity));
        when(demandeAideMapper.toDTO(any(DemandeAide.class))).thenReturn(response);

        List<DemandeAideResponse> results = demandeAideService.rechercherAvecFiltres(
                Map.of("demandeurId", demandeurId));

        assertThat(results).isEmpty();
    }

    @Test
    @DisplayName("correspondAuxFiltres — filtre demandeurId avec membreDemandeurId différent → demande exclue")
    void correspondAuxFiltres_demandeurIdDifferent_demandeExclue() {
        UUID orgId = UUID.randomUUID();
        UUID demandeurRecherche = UUID.randomUUID();
        UUID demandeurReel = UUID.randomUUID(); // ≠ demandeurRecherche

        DemandeAideResponse response = new DemandeAideResponse();
        response.setAssociationId(orgId);
        response.setStatut(StatutAide.EN_ATTENTE);
        response.setMembreDemandeurId(demandeurReel); // !equals(demandeurRecherche) → return false

        DemandeAide entity = new DemandeAide();
        when(demandeAideRepository.listAll()).thenReturn(List.of(entity));
        when(demandeAideMapper.toDTO(any(DemandeAide.class))).thenReturn(response);

        List<DemandeAideResponse> results = demandeAideService.rechercherAvecFiltres(
                Map.of("demandeurId", demandeurRecherche));

        assertThat(results).isEmpty();
    }

    @Test
    @DisplayName("correspondAuxFiltres — filtre demandeurId correspondant → demande incluse (branche passe)")
    void correspondAuxFiltres_demandeurIdCorrespondant_demandeIncluse() {
        UUID orgId = UUID.randomUUID();
        UUID demandeurId = UUID.randomUUID();

        DemandeAideResponse response = new DemandeAideResponse();
        response.setAssociationId(orgId);
        response.setStatut(StatutAide.EN_ATTENTE);
        response.setMembreDemandeurId(demandeurId); // égal → passe le filtre
        response.setPriorite(PrioriteAide.NORMALE);

        DemandeAide entity = new DemandeAide();
        when(demandeAideRepository.listAll()).thenReturn(List.of(entity));
        when(demandeAideMapper.toDTO(any(DemandeAide.class))).thenReturn(response);

        List<DemandeAideResponse> results = demandeAideService.rechercherAvecFiltres(
                Map.of("demandeurId", demandeurId));

        assertThat(results).hasSize(1);
        assertThat(results.get(0).getMembreDemandeurId()).isEqualTo(demandeurId);
    }

    @Test
    @DisplayName("correspondAuxFiltres — filtre organisationId non correspondant → demande exclue")
    void correspondAuxFiltres_organisationNonCorrespondante_demandeExclue() {
        UUID orgId = UUID.randomUUID();
        UUID autreOrgId = UUID.randomUUID();

        DemandeAideResponse response = new DemandeAideResponse();
        response.setAssociationId(orgId); // ≠ autreOrgId
        response.setStatut(StatutAide.EN_ATTENTE);

        DemandeAide entity = new DemandeAide();
        when(demandeAideRepository.listAll()).thenReturn(List.of(entity));
        when(demandeAideMapper.toDTO(any(DemandeAide.class))).thenReturn(response);

        List<DemandeAideResponse> results = demandeAideService.rechercherAvecFiltres(
                Map.of("organisationId", autreOrgId));

        assertThat(results).isEmpty();
    }

    // ===========================================================================
    // DemandeAideService.comparerParPriorite:361 — 2I, 1B
    // via rechercherAvecFiltres() qui trie par comparerParPriorite
    // ===========================================================================

    @Test
    @DisplayName("comparerParPriorite — deux demandes avec même score → tri par dateCreation (branche comparaisonScore==0)")
    void comparerParPriorite_memeScore_trieParDateCreation() {
        UUID orgId = UUID.randomUUID();

        DemandeAideResponse d1 = new DemandeAideResponse();
        d1.setAssociationId(orgId);
        d1.setStatut(StatutAide.EN_ATTENTE);
        d1.setScorePriorite(2.0);
        d1.setDateCreation(LocalDateTime.of(2026, 1, 1, 8, 0)); // plus ancien
        d1.setPriorite(PrioriteAide.NORMALE);

        DemandeAideResponse d2 = new DemandeAideResponse();
        d2.setAssociationId(orgId);
        d2.setStatut(StatutAide.EN_ATTENTE);
        d2.setScorePriorite(2.0); // même score → fallback sur dateCreation
        d2.setDateCreation(LocalDateTime.of(2026, 3, 1, 8, 0)); // plus récent
        d2.setPriorite(PrioriteAide.NORMALE);

        DemandeAide e1 = new DemandeAide();
        e1.setId(UUID.randomUUID()); // ID distinct pour éviter equals() null == null
        DemandeAide e2 = new DemandeAide();
        e2.setId(UUID.randomUUID());

        when(demandeAideRepository.listAll()).thenReturn(List.of(e2, e1));
        when(demandeAideMapper.toDTO(e1)).thenReturn(d1);
        when(demandeAideMapper.toDTO(e2)).thenReturn(d2);

        List<DemandeAideResponse> results = demandeAideService.rechercherAvecFiltres(
                Map.of("organisationId", orgId));

        assertThat(results).hasSize(2);
        // d1 (plus ancien) doit être en premier
        assertThat(results.get(0).getDateCreation()).isEqualTo(LocalDateTime.of(2026, 1, 1, 8, 0));
        assertThat(results.get(1).getDateCreation()).isEqualTo(LocalDateTime.of(2026, 3, 1, 8, 0));
    }

    @Test
    @DisplayName("comparerParPriorite — dateCreation null dans d1 → LocalDateTime.MIN utilisé pour d1 (branche null d1)")
    void comparerParPriorite_dateCreationNullDansD1_minUtilise() {
        UUID orgId = UUID.randomUUID();

        DemandeAideResponse d1 = new DemandeAideResponse();
        d1.setAssociationId(orgId);
        d1.setStatut(StatutAide.EN_ATTENTE);
        d1.setScorePriorite(2.0);
        d1.setDateCreation(null); // null → LocalDateTime.MIN → sort avant d2
        d1.setPriorite(PrioriteAide.NORMALE);

        DemandeAideResponse d2 = new DemandeAideResponse();
        d2.setAssociationId(orgId);
        d2.setStatut(StatutAide.EN_ATTENTE);
        d2.setScorePriorite(2.0); // même score
        d2.setDateCreation(LocalDateTime.of(2026, 2, 15, 12, 0));
        d2.setPriorite(PrioriteAide.NORMALE);

        DemandeAide e1 = new DemandeAide();
        e1.setId(UUID.randomUUID());
        DemandeAide e2 = new DemandeAide();
        e2.setId(UUID.randomUUID());

        when(demandeAideRepository.listAll()).thenReturn(List.of(e1, e2));
        when(demandeAideMapper.toDTO(e1)).thenReturn(d1);
        when(demandeAideMapper.toDTO(e2)).thenReturn(d2);

        List<DemandeAideResponse> results = demandeAideService.rechercherAvecFiltres(
                Map.of("organisationId", orgId));

        assertThat(results).hasSize(2);
        // d1 (null → MIN) vient avant d2
        assertThat(results.get(0).getDateCreation()).isNull();
        assertThat(results.get(1).getDateCreation()).isNotNull();
    }

    @Test
    @DisplayName("comparerParPriorite — dateCreation null dans d2 → LocalDateTime.MIN pour d2 (branche null d2)")
    void comparerParPriorite_dateCreationNullDansD2_minUtilisePourD2() {
        UUID orgId = UUID.randomUUID();

        DemandeAideResponse d1 = new DemandeAideResponse();
        d1.setAssociationId(orgId);
        d1.setStatut(StatutAide.EN_ATTENTE);
        d1.setScorePriorite(3.0);
        d1.setDateCreation(LocalDateTime.of(2026, 1, 1, 0, 0));
        d1.setPriorite(PrioriteAide.NORMALE);

        DemandeAideResponse d2 = new DemandeAideResponse();
        d2.setAssociationId(orgId);
        d2.setStatut(StatutAide.EN_ATTENTE);
        d2.setScorePriorite(3.0); // même score
        d2.setDateCreation(null);  // null → MIN → sort avant d1
        d2.setPriorite(PrioriteAide.NORMALE);

        DemandeAide e1 = new DemandeAide();
        e1.setId(UUID.randomUUID());
        DemandeAide e2 = new DemandeAide();
        e2.setId(UUID.randomUUID());

        when(demandeAideRepository.listAll()).thenReturn(List.of(e1, e2));
        when(demandeAideMapper.toDTO(e1)).thenReturn(d1);
        when(demandeAideMapper.toDTO(e2)).thenReturn(d2);

        List<DemandeAideResponse> results = demandeAideService.rechercherAvecFiltres(
                Map.of("organisationId", orgId));

        assertThat(results).hasSize(2);
        // d2 (null → MIN) vient avant d1 (2026-01-01)
        assertThat(results.get(0).getDateCreation()).isNull();
        assertThat(results.get(1).getDateCreation()).isEqualTo(LocalDateTime.of(2026, 1, 1, 0, 0));
    }

    @Test
    @DisplayName("comparerParPriorite — scorePriorite null dans les deux → Double.MAX_VALUE → tri par dateCreation")
    void comparerParPriorite_scoreNullDeuxDemandes_fallbackDateCreation() {
        UUID orgId = UUID.randomUUID();

        DemandeAideResponse d1 = new DemandeAideResponse();
        d1.setAssociationId(orgId);
        d1.setStatut(StatutAide.EN_ATTENTE);
        d1.setScorePriorite(null); // null → Double.MAX_VALUE
        d1.setDateCreation(LocalDateTime.of(2026, 1, 10, 0, 0));
        d1.setPriorite(PrioriteAide.NORMALE);

        DemandeAideResponse d2 = new DemandeAideResponse();
        d2.setAssociationId(orgId);
        d2.setStatut(StatutAide.EN_ATTENTE);
        d2.setScorePriorite(null); // null → Double.MAX_VALUE (identique → comparaisonScore == 0 → fallback)
        d2.setDateCreation(LocalDateTime.of(2026, 3, 1, 0, 0));
        d2.setPriorite(PrioriteAide.NORMALE);

        DemandeAide e1 = new DemandeAide();
        e1.setId(UUID.randomUUID());
        DemandeAide e2 = new DemandeAide();
        e2.setId(UUID.randomUUID());

        when(demandeAideRepository.listAll()).thenReturn(List.of(e2, e1));
        when(demandeAideMapper.toDTO(e1)).thenReturn(d1);
        when(demandeAideMapper.toDTO(e2)).thenReturn(d2);

        List<DemandeAideResponse> results = demandeAideService.rechercherAvecFiltres(
                Map.of("organisationId", orgId));

        assertThat(results).hasSize(2);
        // Pas de NPE → branches null bien gérées
        assertThat(results.get(0).getDateCreation()).isEqualTo(LocalDateTime.of(2026, 1, 10, 0, 0));
    }
}
