package dev.lions.unionflow.server.api.dto.solidarite;

import static org.assertj.core.api.Assertions.assertThat;

import dev.lions.unionflow.server.api.dto.solidarite.PropositionAideDTO.StatutProposition;
import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
import java.math.BigDecimal;
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.ValueSource;

@DisplayName("Tests pour PropositionAideDTO")
class PropositionAideDTOTest {

  @Test
  @DisplayName("Test de base - classe peut être instanciée")
  void testClasseInstanciable() {
    PropositionAideDTO dto = new PropositionAideDTO();
    assertThat(dto).isNotNull();
  }

  @Nested
  @DisplayName("Tests de construction")
  class ConstructionTests {

    @Test
    @DisplayName("Constructeur par défaut")
    void testConstructeurParDefaut() {
      PropositionAideDTO dto = new PropositionAideDTO();

      assertThat(dto).isNotNull();
      assertThat(dto.getStatut()).isEqualTo(StatutProposition.ACTIVE);
      assertThat(dto.getEstDisponible()).isTrue();
      assertThat(dto.getEstRecurrente()).isFalse();
      assertThat(dto.getDateCreation()).isNotNull();
      assertThat(dto.getDateModification()).isNotNull();
      assertThat(dto.getDelaiReponseHeures()).isEqualTo(72);
      assertThat(dto.getDevise()).isEqualTo("FCFA");
      assertThat(dto.getNombreMaxBeneficiaires()).isEqualTo(1);
      assertThat(dto.getNombreDemandesTraitees()).isEqualTo(0);
      assertThat(dto.getNombreBeneficiairesAides()).isEqualTo(0);
      assertThat(dto.getMontantTotalVerse()).isEqualTo(0.0);
      assertThat(dto.getNombreEvaluations()).isEqualTo(0);
      assertThat(dto.getEstMiseEnAvant()).isFalse();
      assertThat(dto.getNombreVues()).isEqualTo(0);
      assertThat(dto.getNombreCandidatures()).isEqualTo(0);
    }

    @Test
    @DisplayName("Builder pattern")
    void testBuilder() {
      LocalDateTime dateCreation = LocalDateTime.of(2025, 1, 15, 10, 0);
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .id("id-123")
          .numeroReference("PA-2025-123456")
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Aide financière d'urgence")
          .description("Description détaillée de l'aide proposée")
          .montantMaximum(new BigDecimal("500000.00"))
          .nombreMaxBeneficiaires(5)
          .proposantId("proposant-123")
          .proposantNom("Proposant Test")
          .organisationId("org-123")
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .dateCreation(dateCreation)
          .build();

      assertThat(dto.getId()).isEqualTo("id-123");
      assertThat(dto.getNumeroReference()).isEqualTo("PA-2025-123456");
      assertThat(dto.getTypeAide()).isEqualTo(TypeAide.AIDE_FINANCIERE_URGENTE);
      assertThat(dto.getTitre()).isEqualTo("Aide financière d'urgence");
      assertThat(dto.getDescription()).isEqualTo("Description détaillée de l'aide proposée");
      assertThat(dto.getMontantMaximum()).isEqualTo(new BigDecimal("500000.00"));
      assertThat(dto.getNombreMaxBeneficiaires()).isEqualTo(5);
      assertThat(dto.getProposantId()).isEqualTo("proposant-123");
      assertThat(dto.getProposantNom()).isEqualTo("Proposant Test");
      assertThat(dto.getOrganisationId()).isEqualTo("org-123");
      assertThat(dto.getStatut()).isEqualTo(StatutProposition.ACTIVE);
      assertThat(dto.getEstDisponible()).isTrue();
      assertThat(dto.getDateCreation()).isEqualTo(dateCreation);
    }
  }

  @Nested
  @DisplayName("Tests enum StatutProposition")
  class StatutPropositionTests {

    @ParameterizedTest
    @EnumSource(StatutProposition.class)
    @DisplayName("StatutProposition - toutes les valeurs")
    void testToutesValeurs(StatutProposition statut) {
      assertThat(statut).isNotNull();
      assertThat(statut.getLibelle()).isNotNull().isNotEmpty();
    }

    @ParameterizedTest
    @CsvSource({
        "BROUILLON, Brouillon",
        "ACTIVE, Active",
        "SUSPENDUE, Suspendue",
        "EXPIREE, Expirée",
        "TERMINEE, Terminée",
        "ANNULEE, Annulée"
    })
    @DisplayName("StatutProposition - getLibelle")
    void testGetLibelle(StatutProposition statut, String expected) {
      assertThat(statut.getLibelle()).isEqualTo(expected);
    }
  }

  @Nested
  @DisplayName("Tests méthode isActiveEtDisponible")
  class IsActiveEtDisponibleTests {

    @Test
    @DisplayName("isActiveEtDisponible - active et disponible")
    void testIsActiveEtDisponibleTrue() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .dateExpiration(LocalDateTime.now().plusDays(30))
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      assertThat(dto.isActiveEtDisponible()).isTrue();
    }

    @Test
    @DisplayName("isActiveEtDisponible - statut non actif")
    void testIsActiveEtDisponibleStatutNonActif() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .statut(StatutProposition.SUSPENDUE)
          .estDisponible(true)
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      assertThat(dto.isActiveEtDisponible()).isFalse();
    }

    @Test
    @DisplayName("isActiveEtDisponible - non disponible")
    void testIsActiveEtDisponibleNonDisponible() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .statut(StatutProposition.ACTIVE)
          .estDisponible(false)
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      assertThat(dto.isActiveEtDisponible()).isFalse();
    }

    @Test
    @DisplayName("isActiveEtDisponible - expirée")
    void testIsActiveEtDisponibleExpiree() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .dateExpiration(LocalDateTime.now().minusDays(1))
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      assertThat(dto.isActiveEtDisponible()).isFalse();
    }
  }

  @Nested
  @DisplayName("Tests méthode isExpiree")
  class IsExpireeTests {

    @Test
    @DisplayName("isExpiree - date expiration passée")
    void testIsExpireeTrue() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .dateExpiration(LocalDateTime.now().minusHours(1))
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      assertThat(dto.isExpiree()).isTrue();
    }

    @Test
    @DisplayName("isExpiree - date expiration future")
    void testIsExpireeFalse() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .dateExpiration(LocalDateTime.now().plusDays(30))
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      assertThat(dto.isExpiree()).isFalse();
    }

    @Test
    @DisplayName("isExpiree - date expiration null")
    void testIsExpireeNull() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .dateExpiration(null)
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      assertThat(dto.isExpiree()).isFalse();
    }
  }

  @Nested
  @DisplayName("Tests méthode peutAccepterBeneficiaires")
  class PeutAccepterBeneficiairesTests {

    @Test
    @DisplayName("peutAccepterBeneficiaires - peut accepter")
    void testPeutAccepterBeneficiairesTrue() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(5)
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      assertThat(dto.peutAccepterBeneficiaires()).isTrue();
    }

    @Test
    @DisplayName("peutAccepterBeneficiaires - capacité atteinte")
    void testPeutAccepterBeneficiairesCapaciteAtteinte() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(10)
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      assertThat(dto.peutAccepterBeneficiaires()).isFalse();
    }

    @Test
    @DisplayName("peutAccepterBeneficiaires - non disponible")
    void testPeutAccepterBeneficiairesNonDisponible() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .statut(StatutProposition.ACTIVE)
          .estDisponible(false)
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(5)
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      assertThat(dto.peutAccepterBeneficiaires()).isFalse();
    }

    @Test
    @DisplayName("peutAccepterBeneficiaires - nombreBeneficiairesAides null (NPE attendu)")
    void testPeutAccepterBeneficiairesNombreNull() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(null)
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      // Le code utilise intValue() qui lance NPE si null
      // Ce test vérifie que le code lance bien NPE dans ce cas
      org.junit.jupiter.api.Assertions.assertThrows(
          NullPointerException.class, () -> dto.peutAccepterBeneficiaires());
    }

    @Test
    @DisplayName("peutAccepterBeneficiaires - nombreMaxBeneficiaires null (NPE attendu)")
    void testPeutAccepterBeneficiairesMaxNull() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .nombreMaxBeneficiaires(null)
          .nombreBeneficiairesAides(5)
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      // Le code utilise intValue() qui lance NPE si null
      // Ce test vérifie que le code lance bien NPE dans ce cas
      org.junit.jupiter.api.Assertions.assertThrows(
          NullPointerException.class, () -> dto.peutAccepterBeneficiaires());
    }
  }

  @Nested
  @DisplayName("Tests méthode getPourcentageCapaciteUtilisee")
  class GetPourcentageCapaciteUtiliseeTests {

    @Test
    @DisplayName("getPourcentageCapaciteUtilisee - 50%")
    void testGetPourcentageCapaciteUtilisee50() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(5)
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      assertThat(dto.getPourcentageCapaciteUtilisee()).isEqualTo(50.0);
    }

    @Test
    @DisplayName("getPourcentageCapaciteUtilisee - 100%")
    void testGetPourcentageCapaciteUtilisee100() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(10)
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      assertThat(dto.getPourcentageCapaciteUtilisee()).isEqualTo(100.0);
    }

    @Test
    @DisplayName("getPourcentageCapaciteUtilisee - nombreMaxBeneficiaires = 0")
    void testGetPourcentageCapaciteUtiliseeMaxZero() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .nombreMaxBeneficiaires(0)
          .nombreBeneficiairesAides(5)
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      assertThat(dto.getPourcentageCapaciteUtilisee()).isEqualTo(100.0);
    }

    @Test
    @DisplayName("getPourcentageCapaciteUtilisee - 0%")
    void testGetPourcentageCapaciteUtiliseeZero() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(0)
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      assertThat(dto.getPourcentageCapaciteUtilisee()).isEqualTo(0.0);
    }
  }

  @Nested
  @DisplayName("Tests méthode getPlacesRestantes")
  class GetPlacesRestantesTests {

    @Test
    @DisplayName("getPlacesRestantes - places disponibles")
    void testGetPlacesRestantes() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(3)
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      assertThat(dto.getPlacesRestantes()).isEqualTo(7);
    }

    @Test
    @DisplayName("getPlacesRestantes - aucune place")
    void testGetPlacesRestantesAucune() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(10)
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      assertThat(dto.getPlacesRestantes()).isEqualTo(0);
    }

    @Test
    @DisplayName("getPlacesRestantes - dépassement")
    void testGetPlacesRestantesDepassement() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(15)
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      assertThat(dto.getPlacesRestantes()).isEqualTo(0); // Math.max(0, ...)
    }

    @Test
    @DisplayName("getPlacesRestantes - nombreMaxBeneficiaires null (NPE attendu)")
    void testGetPlacesRestantesMaxNull() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .nombreMaxBeneficiaires(null)
          .nombreBeneficiairesAides(5)
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      // Le code utilise intValue() qui lance NPE si null
      // Ce test vérifie que le code lance bien NPE dans ce cas
      org.junit.jupiter.api.Assertions.assertThrows(
          NullPointerException.class, () -> dto.getPlacesRestantes());
    }

    @Test
    @DisplayName("getPlacesRestantes - nombreBeneficiairesAides null (NPE attendu)")
    void testGetPlacesRestantesNombreNull() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(null)
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      // Le code utilise intValue() qui lance NPE si null
      // Ce test vérifie que le code lance bien NPE dans ce cas
      org.junit.jupiter.api.Assertions.assertThrows(
          NullPointerException.class, () -> dto.getPlacesRestantes());
    }
  }

  @Nested
  @DisplayName("Tests méthode correspondAuType")
  class CorrespondAuTypeTests {

    @Test
    @DisplayName("correspondAuType - type exact")
    void testCorrespondAuTypeExact() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      assertThat(dto.correspondAuType(TypeAide.AIDE_FINANCIERE_URGENTE)).isTrue();
    }

    @Test
    @DisplayName("correspondAuType - même catégorie")
    void testCorrespondAuTypeMemeCategorie() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      // Supposons que AIDE_FINANCIERE_URGENTE et PRET_SANS_INTERET sont dans la même catégorie
      // Si ce n'est pas le cas, ce test peut échouer et devra être ajusté
      assertThat(dto.correspondAuType(TypeAide.PRET_SANS_INTERET)).isEqualTo(
          dto.getTypeAide().getCategorie().equals(TypeAide.PRET_SANS_INTERET.getCategorie())
              && dto.getTypeAide() != TypeAide.AUTRE);
    }

    @Test
    @DisplayName("correspondAuType - type différent")
    void testCorrespondAuTypeDifferent() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      assertThat(dto.correspondAuType(TypeAide.DON_MATERIEL)).isEqualTo(
          dto.getTypeAide().getCategorie().equals(TypeAide.DON_MATERIEL.getCategorie())
              && dto.getTypeAide() != TypeAide.AUTRE);
    }

    @Test
    @DisplayName("correspondAuType - AUTRE")
    void testCorrespondAuTypeAutre() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .typeAide(TypeAide.AUTRE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      // Si typeAide == AUTRE, correspondAuType retourne false même si la catégorie correspond
      assertThat(dto.correspondAuType(TypeAide.AIDE_FINANCIERE_URGENTE)).isFalse();
    }
  }

  @Nested
  @DisplayName("Tests méthode getScoreCompatibilite")
  class GetScoreCompatibiliteTests {

    @Test
    @DisplayName("getScoreCompatibilite - correspondance exacte")
    void testGetScoreCompatibiliteExacte() {
      PropositionAideDTO proposition = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .montantMaximum(new BigDecimal("500000.00"))
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(5)
          .dateCreation(LocalDateTime.now().minusDays(10))
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      DemandeAideDTO demande = new DemandeAideDTO();
      demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
      demande.setMontantDemande(new BigDecimal("300000.00"));

      double score = proposition.getScoreCompatibilite(demande);
      // Correspondance exacte (50) + Montant compatible (20) + Disponibilité (15) + Récence (5) = 90
      assertThat(score).isGreaterThanOrEqualTo(80.0);
      assertThat(score).isLessThanOrEqualTo(100.0);
    }

    @Test
    @DisplayName("getScoreCompatibilite - même catégorie")
    void testGetScoreCompatibiliteMemeCategorie() {
      PropositionAideDTO proposition = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .montantMaximum(new BigDecimal("500000.00"))
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(5)
          .dateCreation(LocalDateTime.now().minusDays(10))
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      DemandeAideDTO demande = new DemandeAideDTO();
      demande.setTypeAide(TypeAide.PRET_SANS_INTERET);
      demande.setMontantDemande(new BigDecimal("300000.00"));

      double score = proposition.getScoreCompatibilite(demande);
      // Même catégorie (30) + Montant compatible (20) + Disponibilité (15) + Récence (5) = 70
      assertThat(score).isGreaterThanOrEqualTo(60.0);
      assertThat(score).isLessThanOrEqualTo(100.0);
    }

    @Test
    @DisplayName("getScoreCompatibilite - montant incompatible")
    void testGetScoreCompatibiliteMontantIncompatible() {
      PropositionAideDTO proposition = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .montantMaximum(new BigDecimal("200000.00"))
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(5)
          .dateCreation(LocalDateTime.now().minusDays(10))
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      DemandeAideDTO demande = new DemandeAideDTO();
      demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
      demande.setMontantDemande(new BigDecimal("500000.00")); // Plus que le maximum

      double score = proposition.getScoreCompatibilite(demande);
      // Correspondance exacte (50) - Montant incompatible (10) + Disponibilité (15) + Récence (5) = 60
      assertThat(score).isGreaterThanOrEqualTo(0.0);
      assertThat(score).isLessThanOrEqualTo(100.0);
    }

    @Test
    @DisplayName("getScoreCompatibilite - avec bonne réputation")
    void testGetScoreCompatibiliteBonneReputation() {
      PropositionAideDTO proposition = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .montantMaximum(new BigDecimal("500000.00"))
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(5)
          .noteMoyenne(4.5)
          .dateCreation(LocalDateTime.now().minusDays(10))
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      DemandeAideDTO demande = new DemandeAideDTO();
      demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
      demande.setMontantDemande(new BigDecimal("300000.00"));

      double score = proposition.getScoreCompatibilite(demande);
      // Correspondance exacte (50) + Montant compatible (20) + Disponibilité (15) + Réputation (10) + Récence (5) = 100
      assertThat(score).isGreaterThanOrEqualTo(90.0);
      assertThat(score).isLessThanOrEqualTo(100.0);
    }

    @Test
    @DisplayName("getScoreCompatibilite - proposition récente")
    void testGetScoreCompatibiliteRecente() {
      PropositionAideDTO proposition = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .montantMaximum(new BigDecimal("500000.00"))
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(5)
          .dateCreation(LocalDateTime.now().minusDays(15))
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      DemandeAideDTO demande = new DemandeAideDTO();
      demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
      demande.setMontantDemande(new BigDecimal("300000.00"));

      double score = proposition.getScoreCompatibilite(demande);
      // Correspondance exacte (50) + Montant compatible (20) + Disponibilité (15) + Récence (5) = 90
      assertThat(score).isGreaterThanOrEqualTo(80.0);
      assertThat(score).isLessThanOrEqualTo(100.0);
    }

    @Test
    @DisplayName("getScoreCompatibilite - proposition ancienne")
    void testGetScoreCompatibiliteAncienne() {
      PropositionAideDTO proposition = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .montantMaximum(new BigDecimal("500000.00"))
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(5)
          .dateCreation(LocalDateTime.now().minusDays(60))
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      DemandeAideDTO demande = new DemandeAideDTO();
      demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
      demande.setMontantDemande(new BigDecimal("300000.00"));

      double score = proposition.getScoreCompatibilite(demande);
      // Correspondance exacte (50) + Montant compatible (20) + Disponibilité (15) = 85 (pas de bonus récence)
      assertThat(score).isGreaterThanOrEqualTo(80.0);
      assertThat(score).isLessThanOrEqualTo(100.0);
    }
  }

  @Nested
  @DisplayName("Tests valeurs par défaut")
  class ValeursParDefautTests {

    @Test
    @DisplayName("statut - valeur par défaut")
    void testStatutParDefaut() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();
      assertThat(dto.getStatut()).isEqualTo(StatutProposition.ACTIVE);
    }

    @ParameterizedTest
    @ValueSource(booleans = {true, false})
    @DisplayName("estDisponible - toutes les valeurs")
    void testEstDisponible(Boolean valeur) {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .estDisponible(valeur)
          .build();
      assertThat(dto.getEstDisponible()).isEqualTo(valeur);
    }

    @Test
    @DisplayName("dateCreation - initialisée par défaut")
    void testDateCreationParDefaut() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();
      assertThat(dto.getDateCreation()).isNotNull();
    }

    @Test
    @DisplayName("delaiReponseHeures - valeur par défaut")
    void testDelaiReponseHeuresParDefaut() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();
      assertThat(dto.getDelaiReponseHeures()).isEqualTo(72);
    }

    @Test
    @DisplayName("devise - valeur par défaut")
    void testDeviseParDefaut() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();
      assertThat(dto.getDevise()).isEqualTo("FCFA");
    }

    @Test
    @DisplayName("nombreMaxBeneficiaires - valeur par défaut")
    void testNombreMaxBeneficiairesParDefaut() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();
      assertThat(dto.getNombreMaxBeneficiaires()).isEqualTo(1);
    }
  }

  @Nested
  @DisplayName("Tests getters/setters complets")
  class GettersSettersTests {

    @Test
    @DisplayName("Test tous les champs de base")
    void testTousLesChampsBase() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .id("id-test")
          .numeroReference("PA-2025-123456")
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Titre de test")
          .description("Description détaillée de test")
          .conditions("Conditions de test")
          .montantMaximum(new BigDecimal("500000.00"))
          .nombreMaxBeneficiaires(5)
          .devise("XOF")
          .proposantId("proposant-123")
          .proposantNom("Proposant Test")
          .organisationId("org-123")
          .demandeAideId("demande-123")
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .estRecurrente(true)
          .frequenceRecurrence("Mensuel")
          .dateExpiration(LocalDateTime.now().plusDays(30))
          .delaiReponseHeures(48)
          .modeContactPrefere("EMAIL")
          .noteMoyenne(4.5)
          .scorePertinence(85.5)
          .build();

      assertThat(dto.getId()).isEqualTo("id-test");
      assertThat(dto.getNumeroReference()).isEqualTo("PA-2025-123456");
      assertThat(dto.getTypeAide()).isEqualTo(TypeAide.AIDE_FINANCIERE_URGENTE);
      assertThat(dto.getTitre()).isEqualTo("Titre de test");
      assertThat(dto.getDescription()).isEqualTo("Description détaillée de test");
      assertThat(dto.getConditions()).isEqualTo("Conditions de test");
      assertThat(dto.getMontantMaximum()).isEqualTo(new BigDecimal("500000.00"));
      assertThat(dto.getNombreMaxBeneficiaires()).isEqualTo(5);
      assertThat(dto.getDevise()).isEqualTo("XOF");
      assertThat(dto.getProposantId()).isEqualTo("proposant-123");
      assertThat(dto.getProposantNom()).isEqualTo("Proposant Test");
      assertThat(dto.getOrganisationId()).isEqualTo("org-123");
      assertThat(dto.getDemandeAideId()).isEqualTo("demande-123");
      assertThat(dto.getStatut()).isEqualTo(StatutProposition.ACTIVE);
      assertThat(dto.getEstDisponible()).isTrue();
      assertThat(dto.getEstRecurrente()).isTrue();
      assertThat(dto.getFrequenceRecurrence()).isEqualTo("Mensuel");
      assertThat(dto.getDateExpiration()).isNotNull();
      assertThat(dto.getDelaiReponseHeures()).isEqualTo(48);
      assertThat(dto.getModeContactPrefere()).isEqualTo("EMAIL");
      assertThat(dto.getNoteMoyenne()).isEqualTo(4.5);
      assertThat(dto.getScorePertinence()).isEqualTo(85.5);
    }

    @Test
    @DisplayName("Test champs de collections")
    void testChampsCollections() {
      List<CritereSelectionDTO> criteres = List.of(
          CritereSelectionDTO.builder()
              .nom("Critère Test")
              .type("age")
              .operateur("greater_than")
              .valeur("18")
              .build());
      List<String> zones = List.of("Dakar", "Thiès");
      List<String> groupes = List.of("Jeunes", "Femmes");
      List<String> competences = List.of("Médecine", "Éducation");
      List<CreneauDisponibiliteDTO> creneaux = List.of(
          CreneauDisponibiliteDTO.builder()
              .id("creneau1")
              .jourSemaine(DayOfWeek.MONDAY)
              .build());
      List<String> tags = List.of("urgent", "medical");
      Map<String, Object> donnees = Map.of("key1", "value1", "key2", 123);

      PropositionAideDTO dto = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .criteresSelection(criteres)
          .zonesGeographiques(zones)
          .groupesCibles(groupes)
          .competencesRessources(competences)
          .creneauxDisponibilite(creneaux)
          .tags(tags)
          .donneesPersonnalisees(donnees)
          .build();

      assertThat(dto.getCriteresSelection()).isEqualTo(criteres);
      assertThat(dto.getZonesGeographiques()).isEqualTo(zones);
      assertThat(dto.getGroupesCibles()).isEqualTo(groupes);
      assertThat(dto.getCompetencesRessources()).isEqualTo(competences);
      assertThat(dto.getCreneauxDisponibilite()).isEqualTo(creneaux);
      assertThat(dto.getTags()).isEqualTo(tags);
      assertThat(dto.getDonneesPersonnalisees()).isEqualTo(donnees);
    }

    @Test
    @DisplayName("Test champs de contact")
    void testChampsContact() {
      ContactProposantDTO contact = ContactProposantDTO.builder()
          .telephonePrincipal("+221771234567")
          .email("contact@test.com")
          .build();

      PropositionAideDTO dto = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .contactProposant(contact)
          .build();

      assertThat(dto.getContactProposant()).isEqualTo(contact);
    }

    @Test
    @DisplayName("Test champs de suivi")
    void testChampsSuivi() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .nombreDemandesTraitees(10)
          .nombreBeneficiairesAides(5)
          .montantTotalVerse(2500000.0)
          .nombreEvaluations(3)
          .nombreVues(100)
          .nombreCandidatures(15)
          .estMiseEnAvant(true)
          .build();

      assertThat(dto.getNombreDemandesTraitees()).isEqualTo(10);
      assertThat(dto.getNombreBeneficiairesAides()).isEqualTo(5);
      assertThat(dto.getMontantTotalVerse()).isEqualTo(2500000.0);
      assertThat(dto.getNombreEvaluations()).isEqualTo(3);
      assertThat(dto.getNombreVues()).isEqualTo(100);
      assertThat(dto.getNombreCandidatures()).isEqualTo(15);
      assertThat(dto.getEstMiseEnAvant()).isTrue();
    }
  }

  @Nested
  @DisplayName("Tests equals/hashCode/toString")
  class EqualsHashCodeToStringTests {

    @Test
    @DisplayName("equals - même objet")
    void testEqualsMemeObjet() {
      PropositionAideDTO dto = new PropositionAideDTO();
      assertThat(dto).isEqualTo(dto);
    }

    @Test
    @DisplayName("equals - objets égaux")
    void testEqualsObjetsEgaux() {
      PropositionAideDTO dto1 = PropositionAideDTO.builder()
          .id("id-1")
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();
      PropositionAideDTO dto2 = PropositionAideDTO.builder()
          .id("id-1")
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      assertThat(dto1).isEqualTo(dto2);
      assertThat(dto1.hashCode()).isEqualTo(dto2.hashCode());
    }

    @Test
    @DisplayName("toString - contient les informations essentielles")
    void testToString() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .id("id-123")
          .numeroReference("PA-2025-123456")
          .titre("Aide Test")
          .proposantNom("Proposant")
          .build();

      String toString = dto.toString();
      assertThat(toString).contains("PropositionAideDTO");
      assertThat(toString).contains("id-123");
    }
  }

  @Nested
  @DisplayName("Tests getters/setters supplémentaires")
  class GettersSettersSupplementairesTests {

    @Test
    @DisplayName("Test dateCreation et dateModification")
    void testDates() {
      LocalDateTime dateCreation = LocalDateTime.of(2025, 1, 15, 10, 0);
      LocalDateTime dateModification = LocalDateTime.of(2025, 1, 16, 11, 0);
      
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .dateCreation(dateCreation)
          .dateModification(dateModification)
          .build();

      assertThat(dto.getDateCreation()).isEqualTo(dateCreation);
      assertThat(dto.getDateModification()).isEqualTo(dateModification);
    }

    @Test
    @DisplayName("Test tous les champs manquants")
    void testTousLesChampsManquants() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .dateCreation(LocalDateTime.of(2025, 1, 15, 10, 0))
          .dateModification(LocalDateTime.of(2025, 1, 16, 11, 0))
          .dateExpiration(LocalDateTime.of(2025, 12, 31, 23, 59))
          .frequenceRecurrence("Mensuel")
          .modeContactPrefere("EMAIL")
          .noteMoyenne(4.5)
          .scorePertinence(85.5)
          .build();

      assertThat(dto.getDateCreation()).isNotNull();
      assertThat(dto.getDateModification()).isNotNull();
      assertThat(dto.getDateExpiration()).isNotNull();
      assertThat(dto.getFrequenceRecurrence()).isEqualTo("Mensuel");
      assertThat(dto.getModeContactPrefere()).isEqualTo("EMAIL");
      assertThat(dto.getNoteMoyenne()).isEqualTo(4.5);
      assertThat(dto.getScorePertinence()).isEqualTo(85.5);
    }

    @Test
    @DisplayName("Test tous les setters directs")
    void testTousLesSettersDirects() {
      PropositionAideDTO dto = new PropositionAideDTO();
      LocalDateTime dateCreation = LocalDateTime.of(2025, 1, 15, 10, 0);
      LocalDateTime dateModification = LocalDateTime.of(2025, 1, 16, 11, 0);
      LocalDateTime dateExpiration = LocalDateTime.of(2025, 12, 31, 23, 59);
      List<CritereSelectionDTO> criteres = List.of();
      List<String> zones = List.of("Dakar");
      List<String> groupes = List.of("Jeunes");
      List<String> competences = List.of("Médecine");
      List<CreneauDisponibiliteDTO> creneaux = List.of();
      List<String> tags = List.of("tag1");
      Map<String, Object> donnees = Map.of("key", "value");
      ContactProposantDTO contact = ContactProposantDTO.builder()
          .telephonePrincipal("+221771234567")
          .email("test@test.com")
          .build();

      dto.setId("id-test");
      dto.setNumeroReference("PA-2025-123456");
      dto.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
      dto.setTitre("Titre test");
      dto.setDescription("Description test");
      dto.setConditions("Conditions test");
      dto.setMontantMaximum(new BigDecimal("500000.00"));
      dto.setNombreMaxBeneficiaires(5);
      dto.setDevise("XOF");
      dto.setProposantId("proposant-123");
      dto.setProposantNom("Proposant Test");
      dto.setOrganisationId("org-123");
      dto.setDemandeAideId("demande-123");
      dto.setStatut(StatutProposition.ACTIVE);
      dto.setEstDisponible(true);
      dto.setEstRecurrente(true);
      dto.setFrequenceRecurrence("Mensuel");
      dto.setDateCreation(dateCreation);
      dto.setDateExpiration(dateExpiration);
      dto.setDateModification(dateModification);
      dto.setDelaiReponseHeures(48);
      dto.setCriteresSelection(criteres);
      dto.setZonesGeographiques(zones);
      dto.setGroupesCibles(groupes);
      dto.setCompetencesRessources(competences);
      dto.setContactProposant(contact);
      dto.setCreneauxDisponibilite(creneaux);
      dto.setModeContactPrefere("EMAIL");
      dto.setNombreDemandesTraitees(10);
      dto.setNombreBeneficiairesAides(5);
      dto.setMontantTotalVerse(2500000.0);
      dto.setNoteMoyenne(4.5);
      dto.setNombreEvaluations(3);
      dto.setTags(tags);
      dto.setDonneesPersonnalisees(donnees);
      dto.setEstMiseEnAvant(true);
      dto.setScorePertinence(85.5);
      dto.setNombreVues(100);
      dto.setNombreCandidatures(15);

      assertThat(dto.getId()).isEqualTo("id-test");
      assertThat(dto.getNumeroReference()).isEqualTo("PA-2025-123456");
      assertThat(dto.getTypeAide()).isEqualTo(TypeAide.AIDE_FINANCIERE_URGENTE);
      assertThat(dto.getTitre()).isEqualTo("Titre test");
      assertThat(dto.getDescription()).isEqualTo("Description test");
      assertThat(dto.getConditions()).isEqualTo("Conditions test");
      assertThat(dto.getMontantMaximum()).isEqualTo(new BigDecimal("500000.00"));
      assertThat(dto.getNombreMaxBeneficiaires()).isEqualTo(5);
      assertThat(dto.getDevise()).isEqualTo("XOF");
      assertThat(dto.getProposantId()).isEqualTo("proposant-123");
      assertThat(dto.getProposantNom()).isEqualTo("Proposant Test");
      assertThat(dto.getOrganisationId()).isEqualTo("org-123");
      assertThat(dto.getDemandeAideId()).isEqualTo("demande-123");
      assertThat(dto.getStatut()).isEqualTo(StatutProposition.ACTIVE);
      assertThat(dto.getEstDisponible()).isTrue();
      assertThat(dto.getEstRecurrente()).isTrue();
      assertThat(dto.getFrequenceRecurrence()).isEqualTo("Mensuel");
      assertThat(dto.getDateCreation()).isEqualTo(dateCreation);
      assertThat(dto.getDateExpiration()).isEqualTo(dateExpiration);
      assertThat(dto.getDateModification()).isEqualTo(dateModification);
      assertThat(dto.getDelaiReponseHeures()).isEqualTo(48);
      assertThat(dto.getCriteresSelection()).isEqualTo(criteres);
      assertThat(dto.getZonesGeographiques()).isEqualTo(zones);
      assertThat(dto.getGroupesCibles()).isEqualTo(groupes);
      assertThat(dto.getCompetencesRessources()).isEqualTo(competences);
      assertThat(dto.getContactProposant()).isEqualTo(contact);
      assertThat(dto.getCreneauxDisponibilite()).isEqualTo(creneaux);
      assertThat(dto.getModeContactPrefere()).isEqualTo("EMAIL");
      assertThat(dto.getNombreDemandesTraitees()).isEqualTo(10);
      assertThat(dto.getNombreBeneficiairesAides()).isEqualTo(5);
      assertThat(dto.getMontantTotalVerse()).isEqualTo(2500000.0);
      assertThat(dto.getNoteMoyenne()).isEqualTo(4.5);
      assertThat(dto.getNombreEvaluations()).isEqualTo(3);
      assertThat(dto.getTags()).isEqualTo(tags);
      assertThat(dto.getDonneesPersonnalisees()).isEqualTo(donnees);
      assertThat(dto.getEstMiseEnAvant()).isTrue();
      assertThat(dto.getScorePertinence()).isEqualTo(85.5);
      assertThat(dto.getNombreVues()).isEqualTo(100);
      assertThat(dto.getNombreCandidatures()).isEqualTo(15);
    }
  }

  @Nested
  @DisplayName("Tests getScoreCompatibilite - branches manquantes")
  class GetScoreCompatibiliteBranchesManquantesTests {

    @Test
    @DisplayName("getScoreCompatibilite - montant null")
    void testGetScoreCompatibiliteMontantNull() {
      PropositionAideDTO proposition = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .montantMaximum(null)
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(5)
          .dateCreation(LocalDateTime.now().minusDays(10))
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      DemandeAideDTO demande = new DemandeAideDTO();
      demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
      demande.setMontantDemande(new BigDecimal("300000.00"));

      double score = proposition.getScoreCompatibilite(demande);
      // Correspondance exacte (50) + Disponibilité (15) + Récence (5) = 70 (pas de bonus montant)
      assertThat(score).isGreaterThanOrEqualTo(60.0);
      assertThat(score).isLessThanOrEqualTo(100.0);
    }

    @Test
    @DisplayName("getScoreCompatibilite - demande montant null")
    void testGetScoreCompatibiliteDemandeMontantNull() {
      PropositionAideDTO proposition = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .montantMaximum(new BigDecimal("500000.00"))
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(5)
          .dateCreation(LocalDateTime.now().minusDays(10))
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      DemandeAideDTO demande = new DemandeAideDTO();
      demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
      demande.setMontantDemande(null);

      double score = proposition.getScoreCompatibilite(demande);
      // Correspondance exacte (50) + Disponibilité (15) + Récence (5) = 70 (pas de bonus montant)
      assertThat(score).isGreaterThanOrEqualTo(60.0);
      assertThat(score).isLessThanOrEqualTo(100.0);
    }

    @Test
    @DisplayName("getScoreCompatibilite - note moyenne < 4.0")
    void testGetScoreCompatibiliteNoteMoyenneFaible() {
      PropositionAideDTO proposition = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .montantMaximum(new BigDecimal("500000.00"))
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(5)
          .noteMoyenne(3.5)
          .dateCreation(LocalDateTime.now().minusDays(10))
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      DemandeAideDTO demande = new DemandeAideDTO();
      demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
      demande.setMontantDemande(new BigDecimal("300000.00"));

      double score = proposition.getScoreCompatibilite(demande);
      // Correspondance exacte (50) + Montant compatible (20) + Disponibilité (15) + Récence (5) = 90 (pas de bonus réputation)
      assertThat(score).isGreaterThanOrEqualTo(80.0);
      assertThat(score).isLessThanOrEqualTo(100.0);
    }

    @Test
    @DisplayName("getScoreCompatibilite - note moyenne null")
    void testGetScoreCompatibiliteNoteMoyenneNull() {
      PropositionAideDTO proposition = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .montantMaximum(new BigDecimal("500000.00"))
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(5)
          .noteMoyenne(null)
          .dateCreation(LocalDateTime.now().minusDays(10))
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      DemandeAideDTO demande = new DemandeAideDTO();
      demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
      demande.setMontantDemande(new BigDecimal("300000.00"));

      double score = proposition.getScoreCompatibilite(demande);
      // Correspondance exacte (50) + Montant compatible (20) + Disponibilité (15) + Récence (5) = 90 (pas de bonus réputation)
      assertThat(score).isGreaterThanOrEqualTo(80.0);
      assertThat(score).isLessThanOrEqualTo(100.0);
    }

    @Test
    @DisplayName("getScoreCompatibilite - score > 100 (plafonné)")
    void testGetScoreCompatibiliteScorePlafonne() {
      PropositionAideDTO proposition = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .montantMaximum(new BigDecimal("500000.00"))
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(5)
          .noteMoyenne(5.0)
          .dateCreation(LocalDateTime.now().minusDays(5))
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      DemandeAideDTO demande = new DemandeAideDTO();
      demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
      demande.setMontantDemande(new BigDecimal("300000.00"));

      double score = proposition.getScoreCompatibilite(demande);
      // Le score devrait être plafonné à 100.0
      assertThat(score).isLessThanOrEqualTo(100.0);
    }

    @Test
    @DisplayName("getScoreCompatibilite - score < 0 (plancher)")
    void testGetScoreCompatibiliteScorePlancher() {
      PropositionAideDTO proposition = PropositionAideDTO.builder()
          .typeAide(TypeAide.DON_MATERIEL)
          .montantMaximum(new BigDecimal("10000.00"))
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(5)
          .dateCreation(LocalDateTime.now().minusDays(100))
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      DemandeAideDTO demande = new DemandeAideDTO();
      demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
      demande.setMontantDemande(new BigDecimal("500000.00"));

      double score = proposition.getScoreCompatibilite(demande);
      // Type différent (0) - Montant incompatible (10) + Disponibilité (15) = 5, mais plancher à 0
      assertThat(score).isGreaterThanOrEqualTo(0.0);
    }
  }

  @Nested
  @DisplayName("Tests correspondAuType - branches manquantes")
  class CorrespondAuTypeBranchesManquantesTests {

    @Test
    @DisplayName("correspondAuType - même catégorie mais pas AUTRE")
    void testCorrespondAuTypeMemeCategoriePasAutre() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      // Si PRET_SANS_INTERET est dans la même catégorie que AIDE_FINANCIERE_URGENTE
      boolean result = dto.correspondAuType(TypeAide.PRET_SANS_INTERET);
      assertThat(result).isEqualTo(
          dto.getTypeAide().getCategorie().equals(TypeAide.PRET_SANS_INTERET.getCategorie())
              && dto.getTypeAide() != TypeAide.AUTRE);
    }

    @Test
    @DisplayName("correspondAuType - typeAide AUTRE avec même catégorie")
    void testCorrespondAuTypeAutreAvecMemeCategorie() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .typeAide(TypeAide.AUTRE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      // Même si la catégorie correspond, si typeAide == AUTRE, retourne false
      assertThat(dto.correspondAuType(TypeAide.AIDE_FINANCIERE_URGENTE)).isFalse();
    }

    @Test
    @DisplayName("correspondAuType - typeAide AUTRE avec type AUTRE")
    void testCorrespondAuTypeAutreAvecAutre() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .typeAide(TypeAide.AUTRE)
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      // typeAide == type (AUTRE == AUTRE) retourne true
      assertThat(dto.correspondAuType(TypeAide.AUTRE)).isTrue();
    }

    @Test
    @DisplayName("correspondAuType - catégories différentes")
    void testCorrespondAuTypeCategoriesDifferentes() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE) // catégorie "financiere"
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      // Type avec catégorie différente (par exemple "sociale")
      boolean result = dto.correspondAuType(TypeAide.AIDE_PERSONNES_AGEES);
      assertThat(result).isEqualTo(
          dto.getTypeAide().getCategorie().equals(TypeAide.AIDE_PERSONNES_AGEES.getCategorie())
              && dto.getTypeAide() != TypeAide.AUTRE);
    }
  }

  @Nested
  @DisplayName("Tests getScoreCompatibilite - cas limites supplémentaires")
  class GetScoreCompatibiliteCasLimitesTests {

    @Test
    @DisplayName("getScoreCompatibilite - peutAccepterBeneficiaires false")
    void testGetScoreCompatibiliteNePeutPasAccepter() {
      PropositionAideDTO proposition = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .montantMaximum(new BigDecimal("500000.00"))
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(10) // Capacité atteinte
          .dateCreation(LocalDateTime.now().minusDays(10))
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      DemandeAideDTO demande = new DemandeAideDTO();
      demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
      demande.setMontantDemande(new BigDecimal("300000.00"));

      double score = proposition.getScoreCompatibilite(demande);
      // Correspondance exacte (50) + Montant compatible (20) + Récence (5) = 75 (pas de bonus disponibilité)
      assertThat(score).isGreaterThanOrEqualTo(70.0);
      assertThat(score).isLessThanOrEqualTo(100.0);
    }

    @Test
    @DisplayName("getScoreCompatibilite - joursDepuisCreation > 30")
    void testGetScoreCompatibiliteAncienne() {
      PropositionAideDTO proposition = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .montantMaximum(new BigDecimal("500000.00"))
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(5)
          .dateCreation(LocalDateTime.now().minusDays(35)) // Plus de 30 jours
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      DemandeAideDTO demande = new DemandeAideDTO();
      demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
      demande.setMontantDemande(new BigDecimal("300000.00"));

      double score = proposition.getScoreCompatibilite(demande);
      // Correspondance exacte (50) + Montant compatible (20) + Disponibilité (15) = 85 (pas de bonus récence)
      assertThat(score).isGreaterThanOrEqualTo(80.0);
      assertThat(score).isLessThanOrEqualTo(100.0);
    }

    @Test
    @DisplayName("getScoreCompatibilite - joursDepuisCreation = 30")
    void testGetScoreCompatibiliteExactement30Jours() {
      PropositionAideDTO proposition = PropositionAideDTO.builder()
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .montantMaximum(new BigDecimal("500000.00"))
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .nombreMaxBeneficiaires(10)
          .nombreBeneficiairesAides(5)
          .dateCreation(LocalDateTime.now().minusDays(30)) // Exactement 30 jours
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      DemandeAideDTO demande = new DemandeAideDTO();
      demande.setTypeAide(TypeAide.AIDE_FINANCIERE_URGENTE);
      demande.setMontantDemande(new BigDecimal("300000.00"));

      double score = proposition.getScoreCompatibilite(demande);
      // Correspondance exacte (50) + Montant compatible (20) + Disponibilité (15) + Récence (5) = 90
      assertThat(score).isGreaterThanOrEqualTo(85.0);
      assertThat(score).isLessThanOrEqualTo(100.0);
    }
  }

  @Nested
  @DisplayName("Tests Builder complet - tous les champs")
  class BuilderCompletTests {

    @Test
    @DisplayName("Builder - tous les champs")
    void testBuilderTousChamps() {
      LocalDateTime now = LocalDateTime.now();
      List<CreneauDisponibiliteDTO> creneaux = Arrays.asList(
          CreneauDisponibiliteDTO.builder()
              .jourSemaine(DayOfWeek.MONDAY)
              .heureDebut(LocalTime.of(9, 0))
              .heureFin(LocalTime.of(17, 0))
              .build());
      ContactProposantDTO contact = ContactProposantDTO.builder()
          .telephonePrincipal("+221771234567")
          .email("contact@test.com")
          .build();
      List<CritereSelectionDTO> criteres = Arrays.asList(
          CritereSelectionDTO.builder()
              .nom("Critère test")
              .valeur("Valeur test")
              .build());
      Map<String, Object> donneesPersonnalisees = new HashMap<>();
      donneesPersonnalisees.put("key", "value");
      List<String> tags = Arrays.asList("tag1", "tag2");

      PropositionAideDTO dto = PropositionAideDTO.builder()
          .id("prop-123")
          .numeroReference("PA-2025-000001")
          .typeAide(TypeAide.AIDE_FINANCIERE_URGENTE)
          .titre("Proposition complète")
          .description("Description complète")
          .proposantId("proposant-123")
          .proposantNom("Proposant Test")
          .organisationId("org-123")
          .montantMaximum(new BigDecimal("500000.00"))
          .devise("FCFA")
          .statut(StatutProposition.ACTIVE)
          .estDisponible(true)
          .estRecurrente(true)
          .dateCreation(now)
          .dateModification(now)
          .dateExpiration(now.plusDays(30))
          .delaiReponseHeures(48)
          .nombreMaxBeneficiaires(5)
          .creneauxDisponibilite(creneaux)
          .contactProposant(contact)
          .criteresSelection(criteres)
          .donneesPersonnalisees(donneesPersonnalisees)
          .tags(tags)
          .nombreDemandesTraitees(10)
          .nombreBeneficiairesAides(20)
          .montantTotalVerse(2000000.0)
          .noteMoyenne(4.5)
          .nombreEvaluations(15)
          .estMiseEnAvant(true)
          .nombreVues(100)
          .nombreCandidatures(25)
          .build();

      assertThat(dto.getId()).isEqualTo("prop-123");
      assertThat(dto.getNumeroReference()).isEqualTo("PA-2025-000001");
      assertThat(dto.getTypeAide()).isEqualTo(TypeAide.AIDE_FINANCIERE_URGENTE);
      assertThat(dto.getTitre()).isEqualTo("Proposition complète");
      assertThat(dto.getDescription()).isEqualTo("Description complète");
      assertThat(dto.getProposantId()).isEqualTo("proposant-123");
      assertThat(dto.getProposantNom()).isEqualTo("Proposant Test");
      assertThat(dto.getOrganisationId()).isEqualTo("org-123");
      assertThat(dto.getMontantMaximum()).isEqualTo(new BigDecimal("500000.00"));
      assertThat(dto.getDevise()).isEqualTo("FCFA");
      assertThat(dto.getStatut()).isEqualTo(StatutProposition.ACTIVE);
      assertThat(dto.getEstDisponible()).isTrue();
      assertThat(dto.getEstRecurrente()).isTrue();
      assertThat(dto.getDateCreation()).isEqualTo(now);
      assertThat(dto.getDateModification()).isEqualTo(now);
      assertThat(dto.getDateExpiration()).isEqualTo(now.plusDays(30));
      assertThat(dto.getDelaiReponseHeures()).isEqualTo(48);
      assertThat(dto.getNombreMaxBeneficiaires()).isEqualTo(5);
      assertThat(dto.getCreneauxDisponibilite()).isEqualTo(creneaux);
      assertThat(dto.getContactProposant()).isEqualTo(contact);
      assertThat(dto.getCriteresSelection()).isEqualTo(criteres);
      assertThat(dto.getDonneesPersonnalisees()).isEqualTo(donneesPersonnalisees);
      assertThat(dto.getTags()).isEqualTo(tags);
      assertThat(dto.getNombreDemandesTraitees()).isEqualTo(10);
      assertThat(dto.getNombreBeneficiairesAides()).isEqualTo(20);
      assertThat(dto.getMontantTotalVerse()).isEqualTo(2000000.0);
      assertThat(dto.getNoteMoyenne()).isEqualTo(4.5);
      assertThat(dto.getNombreEvaluations()).isEqualTo(15);
      assertThat(dto.getEstMiseEnAvant()).isTrue();
      assertThat(dto.getNombreVues()).isEqualTo(100);
      assertThat(dto.getNombreCandidatures()).isEqualTo(25);
    }

    @Test
    @DisplayName("Builder - valeurs par défaut")
    void testBuilderValeursParDefaut() {
      PropositionAideDTO dto = PropositionAideDTO.builder()
          .titre("Test")
          .description("Description test")
          .proposantId("proposant")
          .organisationId("org")
          .build();

      assertThat(dto.getStatut()).isEqualTo(StatutProposition.ACTIVE);
      assertThat(dto.getEstDisponible()).isTrue();
      assertThat(dto.getEstRecurrente()).isFalse();
      assertThat(dto.getDateCreation()).isNotNull();
      assertThat(dto.getDateModification()).isNotNull();
      assertThat(dto.getDelaiReponseHeures()).isEqualTo(72);
      assertThat(dto.getDevise()).isEqualTo("FCFA");
      assertThat(dto.getNombreMaxBeneficiaires()).isEqualTo(1);
      assertThat(dto.getNombreDemandesTraitees()).isEqualTo(0);
      assertThat(dto.getNombreBeneficiairesAides()).isEqualTo(0);
      assertThat(dto.getMontantTotalVerse()).isEqualTo(0.0);
      assertThat(dto.getNombreEvaluations()).isEqualTo(0);
      assertThat(dto.getEstMiseEnAvant()).isFalse();
      assertThat(dto.getNombreVues()).isEqualTo(0);
      assertThat(dto.getNombreCandidatures()).isEqualTo(0);
    }
  }
}

