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

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

import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

/**
 * Tests unitaires pour TypeAide.
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-12-09
 */
@DisplayName("Tests TypeAide")
class TypeAideTest {

  @Test
  @DisplayName("Tous les types d'aide doivent avoir un libellé")
  void testTousLesTypesOntLibelle() {
    for (TypeAide type : TypeAide.values()) {
      assertThat(type.getLibelle()).isNotNull().isNotBlank();
    }
  }

  @Test
  @DisplayName("Tous les types d'aide doivent avoir une catégorie")
  void testTousLesTypesOntCategorie() {
    for (TypeAide type : TypeAide.values()) {
      assertThat(type.getCategorie()).isNotNull().isNotBlank();
    }
  }

  @Nested
  @DisplayName("Tests isUrgent")
  class IsUrgentTests {

    @Test
    @DisplayName("isUrgent doit retourner true pour les types avec priorite urgent")
    void testIsUrgent() {
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isUrgent()).isTrue();
      assertThat(TypeAide.AIDE_FRAIS_MEDICAUX.isUrgent()).isTrue();
      assertThat(TypeAide.HEBERGEMENT_URGENCE.isUrgent()).isTrue();
      assertThat(TypeAide.AIDE_ALIMENTAIRE.isUrgent()).isTrue();

      assertThat(TypeAide.DON_MATERIEL.isUrgent()).isFalse();
      assertThat(TypeAide.TRANSPORT.isUrgent()).isFalse();
      assertThat(TypeAide.AIDE_COTISATION.isUrgent()).isFalse();
    }
  }

  @Nested
  @DisplayName("Tests isFinancier")
  class IsFinancierTests {

    @Test
    @DisplayName("isFinancier doit retourner true pour les types financiers")
    void testIsFinancier() {
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isFinancier()).isTrue();
      assertThat(TypeAide.PRET_SANS_INTERET.isFinancier()).isTrue();
      assertThat(TypeAide.AIDE_COTISATION.isFinancier()).isTrue();
      assertThat(TypeAide.AIDE_FRAIS_MEDICAUX.isFinancier()).isTrue();
      assertThat(TypeAide.AIDE_FRAIS_SCOLARITE.isFinancier()).isTrue();

      assertThat(TypeAide.DON_MATERIEL.isFinancier()).isFalse();
      assertThat(TypeAide.TRANSPORT.isFinancier()).isFalse();
    }
  }

  @Nested
  @DisplayName("Tests isMateriel")
  class IsMaterielTests {

    @Test
    @DisplayName("isMateriel doit retourner true pour les types matériels")
    void testIsMateriel() {
      assertThat(TypeAide.DON_MATERIEL.isMateriel()).isTrue();
      assertThat(TypeAide.PRET_MATERIEL.isMateriel()).isTrue();
      assertThat(TypeAide.AIDE_DEMENAGEMENT.isMateriel()).isTrue();
      assertThat(TypeAide.AIDE_TRAVAUX.isMateriel()).isTrue();

      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMateriel()).isFalse();
      assertThat(TypeAide.TRANSPORT.isMateriel()).isFalse();
    }
  }

  @Nested
  @DisplayName("Tests isMontantValide")
  class IsMontantValideTests {

    @Test
    @DisplayName("isMontantValide doit retourner true si necessiteMontant est false")
    void testIsMontantValideNonNecessaire() {
      assertThat(TypeAide.DON_MATERIEL.isMontantValide(null)).isTrue();
      assertThat(TypeAide.DON_MATERIEL.isMontantValide(1000.0)).isTrue();
    }

    @Test
    @DisplayName("isMontantValide doit retourner true si montant est null et necessiteMontant est true")
    void testIsMontantValideNull() {
      // Si necessiteMontant est true mais montant est null, retourne true
      // (la validation du null se fait ailleurs)
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(null)).isTrue();
    }

    @Test
    @DisplayName("isMontantValide doit valider la fourchette")
    void testIsMontantValideFourchette() {
      // AIDE_FINANCIERE_URGENTE: montantMin=5000, montantMax=50000
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(10000.0)).isTrue();
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(5000.0)).isTrue();
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(50000.0)).isTrue();
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(4000.0)).isFalse();
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(51000.0)).isFalse();
    }

    @Test
    @DisplayName("isMontantValide doit gérer montantMin null")
    void testIsMontantValideMontantMinNull() {
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(1000.0)).isFalse();
    }

    @Test
    @DisplayName("isMontantValide skip montantMin quand null (reflection)")
    void testIsMontantValideMontantMinNullSkip() throws Exception {
      java.lang.reflect.Field minF = TypeAide.class.getDeclaredField("montantMin");
      minF.setAccessible(true);
      Object backup = minF.get(TypeAide.AIDE_FINANCIERE_URGENTE);
      try {
        minF.set(TypeAide.AIDE_FINANCIERE_URGENTE, null);
        assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(60000.0)).isFalse();
        assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(25000.0)).isTrue();
      } finally {
        minF.set(TypeAide.AIDE_FINANCIERE_URGENTE, backup);
      }
    }

    @Test
    @DisplayName("isMontantValide skip montantMax quand null (reflection)")
    void testIsMontantValideMontantMaxNullSkip() throws Exception {
      java.lang.reflect.Field maxF = TypeAide.class.getDeclaredField("montantMax");
      maxF.setAccessible(true);
      Object backup = maxF.get(TypeAide.AIDE_FINANCIERE_URGENTE);
      try {
        maxF.set(TypeAide.AIDE_FINANCIERE_URGENTE, null);
        assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(10000.0)).isTrue();
        assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(4000.0)).isFalse();
      } finally {
        maxF.set(TypeAide.AIDE_FINANCIERE_URGENTE, backup);
      }
    }
  }

  @Nested
  @DisplayName("Tests getNiveauPriorite")
  class GetNiveauPrioriteTests {

    @Test
    @DisplayName("getNiveauPriorite doit retourner le bon niveau")
    void testGetNiveauPriorite() {
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getNiveauPriorite()).isEqualTo(1);
      assertThat(TypeAide.PRET_SANS_INTERET.getNiveauPriorite()).isEqualTo(2);
      assertThat(TypeAide.AIDE_COTISATION.getNiveauPriorite()).isEqualTo(3);
      assertThat(TypeAide.DON_MATERIEL.getNiveauPriorite()).isEqualTo(3);
    }
  }

  @Nested
  @DisplayName("Tests getDateLimiteReponse")
  class GetDateLimiteReponseTests {

    @Test
    @DisplayName("getDateLimiteReponse doit retourner une date future")
    void testGetDateLimiteReponse() {
      var maintenant = java.time.LocalDateTime.now();
      var dateLimite = TypeAide.AIDE_FINANCIERE_URGENTE.getDateLimiteReponse();

      assertThat(dateLimite).isAfter(maintenant);
      assertThat(dateLimite).isBefore(maintenant.plusDays(8)); // 7 jours
    }
  }

  @Nested
  @DisplayName("Tests getParCategorie")
  class GetParCategorieTests {

    @Test
    @DisplayName("getParCategorie doit retourner les types de la catégorie")
    void testGetParCategorie() {
      List<TypeAide> financiers = TypeAide.getParCategorie("financiere");
      assertThat(financiers).isNotEmpty();
      assertThat(financiers).contains(TypeAide.AIDE_FINANCIERE_URGENTE);
      assertThat(financiers).contains(TypeAide.PRET_SANS_INTERET);
      assertThat(financiers).doesNotContain(TypeAide.DON_MATERIEL);

      List<TypeAide> materiels = TypeAide.getParCategorie("materielle");
      assertThat(materiels).isNotEmpty();
      assertThat(materiels).contains(TypeAide.DON_MATERIEL);
      assertThat(materiels).doesNotContain(TypeAide.AIDE_FINANCIERE_URGENTE);
    }
  }

  @Nested
  @DisplayName("Tests getUrgents")
  class GetUrgentsTests {

    @Test
    @DisplayName("getUrgents doit retourner tous les types urgents")
    void testGetUrgents() {
      List<TypeAide> urgents = TypeAide.getUrgents();

      assertThat(urgents).isNotEmpty();
      assertThat(urgents).contains(TypeAide.AIDE_FINANCIERE_URGENTE);
      assertThat(urgents).contains(TypeAide.AIDE_FRAIS_MEDICAUX);
      assertThat(urgents).contains(TypeAide.HEBERGEMENT_URGENCE);
      assertThat(urgents).contains(TypeAide.AIDE_ALIMENTAIRE);
      assertThat(urgents).doesNotContain(TypeAide.DON_MATERIEL);
    }
  }

  @Nested
  @DisplayName("Tests getFinanciers")
  class GetFinanciersTests {

    @Test
    @DisplayName("getFinanciers doit retourner tous les types financiers")
    void testGetFinanciers() {
      List<TypeAide> financiers = TypeAide.getFinanciers();

      assertThat(financiers).isNotEmpty();
      assertThat(financiers).contains(TypeAide.AIDE_FINANCIERE_URGENTE);
      assertThat(financiers).contains(TypeAide.PRET_SANS_INTERET);
      assertThat(financiers).contains(TypeAide.AIDE_COTISATION);
      assertThat(financiers).contains(TypeAide.AIDE_FRAIS_MEDICAUX);
      assertThat(financiers).contains(TypeAide.AIDE_FRAIS_SCOLARITE);
      assertThat(financiers).doesNotContain(TypeAide.DON_MATERIEL);
    }
  }

  @Nested
  @DisplayName("Tests getCategories")
  class GetCategoriesTests {

    @Test
    @DisplayName("getCategories doit retourner toutes les catégories uniques")
    void testGetCategories() {
      Set<String> categories = TypeAide.getCategories();

      assertThat(categories).isNotEmpty();
      assertThat(categories).contains("financiere");
      assertThat(categories).contains("materielle");
      assertThat(categories).contains("professionnelle");
      assertThat(categories).contains("sociale");
      assertThat(categories).contains("urgence");
      assertThat(categories).contains("specialisee");
      assertThat(categories).contains("autre");
    }
  }

  @Nested
  @DisplayName("Tests getLibelleCategorie")
  class GetLibelleCategorieTests {

    @Test
    @DisplayName("getLibelleCategorie doit retourner le bon libellé")
    void testGetLibelleCategorie() {
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getLibelleCategorie())
          .isEqualTo("Aide financière");
      assertThat(TypeAide.DON_MATERIEL.getLibelleCategorie()).isEqualTo("Aide matérielle");
      assertThat(TypeAide.AIDE_RECHERCHE_EMPLOI.getLibelleCategorie())
          .isEqualTo("Aide professionnelle");
      assertThat(TypeAide.GARDE_ENFANTS.getLibelleCategorie()).isEqualTo("Aide sociale");
      assertThat(TypeAide.HEBERGEMENT_URGENCE.getLibelleCategorie()).isEqualTo("Aide d'urgence");
      assertThat(TypeAide.SOUTIEN_PSYCHOLOGIQUE.getLibelleCategorie())
          .isEqualTo("Aide spécialisée");
      assertThat(TypeAide.AUTRE.getLibelleCategorie()).isEqualTo("Autre");
    }

    @Test
    @DisplayName("getLibelleCategorie retourne default (catégorie brute) pour catégorie inconnue")
    void testGetLibelleCategorieInconnue() throws Exception {
      java.lang.reflect.Field f = TypeAide.class.getDeclaredField("categorie");
      f.setAccessible(true);
      String backup = TypeAide.AUTRE.getCategorie();
      try {
        f.set(TypeAide.AUTRE, "inconnue");
        assertThat(TypeAide.AUTRE.getLibelleCategorie()).isEqualTo("inconnue");
      } finally {
        f.set(TypeAide.AUTRE, backup);
      }
    }
  }

  @Nested
  @DisplayName("getNiveauPriorite default")
  class GetNiveauPrioriteDefault {

    @Test
    @DisplayName("getNiveauPriorite retourne 3 (default) pour priorite inconnue")
    void testDefault() throws Exception {
      java.lang.reflect.Field f = TypeAide.class.getDeclaredField("priorite");
      f.setAccessible(true);
      String backup = TypeAide.AUTRE.getPriorite();
      try {
        f.set(TypeAide.AUTRE, "inconnu");
        assertThat(TypeAide.AUTRE.getNiveauPriorite()).isEqualTo(3);
      } finally {
        f.set(TypeAide.AUTRE, backup);
      }
    }
  }

  @Nested
  @DisplayName("Tests getUniteMontant")
  class GetUniteMontantTests {

    @Test
    @DisplayName("getUniteMontant doit retourner FCFA si necessiteMontant est true")
    void testGetUniteMontant() {
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getUniteMontant()).isEqualTo("FCFA");
      assertThat(TypeAide.PRET_SANS_INTERET.getUniteMontant()).isEqualTo("FCFA");
      assertThat(TypeAide.DON_MATERIEL.getUniteMontant()).isNull();
      assertThat(TypeAide.TRANSPORT.getUniteMontant()).isNull();
    }
  }

  @Nested
  @DisplayName("Tests getMessageValidationMontant")
  class GetMessageValidationMontantTests {

    @Test
    @DisplayName("getMessageValidationMontant doit retourner null si necessiteMontant est false")
    void testGetMessageValidationMontantNonNecessaire() {
      assertThat(TypeAide.DON_MATERIEL.getMessageValidationMontant(null)).isNull();
      assertThat(TypeAide.DON_MATERIEL.getMessageValidationMontant(1000.0)).isNull();
    }

    @Test
    @DisplayName("getMessageValidationMontant doit retourner message si montant est null")
    void testGetMessageValidationMontantNull() {
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(null))
          .isEqualTo("Le montant est obligatoire");
    }

    @Test
    @DisplayName("getMessageValidationMontant doit retourner message si montant < montantMin")
    void testGetMessageValidationMontantTropBas() {
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(4000.0))
          .contains("minimum");
    }

    @Test
    @DisplayName("getMessageValidationMontant doit retourner message si montant > montantMax")
    void testGetMessageValidationMontantTropHaut() {
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(51000.0))
          .contains("maximum");
    }

    @Test
    @DisplayName("getMessageValidationMontant doit retourner null si montant est valide")
    void testGetMessageValidationMontantValide() {
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(10000.0))
          .isNull();
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(5000.0))
          .isNull();
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(50000.0))
          .isNull();
    }

    @Test
    @DisplayName("getMessageValidationMontant skip montantMin quand null (reflection)")
    void testGetMessageValidationMontantMontantMinNull() throws Exception {
      java.lang.reflect.Field minF = TypeAide.class.getDeclaredField("montantMin");
      minF.setAccessible(true);
      Object backup = minF.get(TypeAide.AIDE_FINANCIERE_URGENTE);
      try {
        minF.set(TypeAide.AIDE_FINANCIERE_URGENTE, null);
        assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(60000.0))
            .contains("maximum");
        assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(25000.0))
            .isNull();
      } finally {
        minF.set(TypeAide.AIDE_FINANCIERE_URGENTE, backup);
      }
    }

    @Test
    @DisplayName("getMessageValidationMontant skip montantMax quand null (reflection)")
    void testGetMessageValidationMontantMontantMaxNull() throws Exception {
      java.lang.reflect.Field maxF = TypeAide.class.getDeclaredField("montantMax");
      maxF.setAccessible(true);
      Object backup = maxF.get(TypeAide.AIDE_FINANCIERE_URGENTE);
      try {
        maxF.set(TypeAide.AIDE_FINANCIERE_URGENTE, null);
        assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(4000.0))
            .contains("minimum");
        assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(10000.0))
            .isNull();
      } finally {
        maxF.set(TypeAide.AIDE_FINANCIERE_URGENTE, backup);
      }
    }
  }
}
