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;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.EnumSource;

@DisplayName("Tests pour TypeAide")
class TypeAideTest {

  @Nested
  @DisplayName("Tests des valeurs enum")
  class TestsValeursEnum {

    @Test
    @DisplayName("Test toutes les valeurs enum")
    void testToutesValeurs() {
      TypeAide[] values = TypeAide.values();
      assertThat(values).hasSize(24);
    }

    @ParameterizedTest
    @EnumSource(TypeAide.class)
    @DisplayName("Test getters de base pour toutes les valeurs")
    void testGettersBase(TypeAide type) {
      assertThat(type.getLibelle()).isNotNull().isNotEmpty();
      assertThat(type.getCategorie()).isNotNull().isNotEmpty();
      assertThat(type.getPriorite()).isNotNull().isNotEmpty();
      assertThat(type.getDescription()).isNotNull().isNotEmpty();
      assertThat(type.getIcone()).isNotNull().isNotEmpty();
      assertThat(type.getCouleur()).isNotNull().matches("#[0-9A-Fa-f]{6}");
      assertThat(type.getDelaiReponseJours()).isPositive();
    }

    @ParameterizedTest
    @CsvSource({
        "AIDE_FINANCIERE_URGENTE, true",
        "PRET_SANS_INTERET, true",
        "AIDE_COTISATION, true",
        "DON_MATERIEL, false",
        "PRET_MATERIEL, false"
    })
    @DisplayName("isNecessiteMontant - tous les types")
    void testIsNecessiteMontant(TypeAide type, Boolean expected) {
      assertThat(type.isNecessiteMontant()).isEqualTo(expected);
    }

    @ParameterizedTest
    @CsvSource({
        "AIDE_FINANCIERE_URGENTE, true",
        "PRET_SANS_INTERET, true",
        "AIDE_COTISATION, false",
        "DON_MATERIEL, false",
        "PRET_MATERIEL, false"
    })
    @DisplayName("isNecessiteValidation - tous les types")
    void testIsNecessiteValidation(TypeAide type, Boolean expected) {
      assertThat(type.isNecessiteValidation()).isEqualTo(expected);
    }

    @ParameterizedTest
    @CsvSource({
        "AIDE_FINANCIERE_URGENTE, 5000.0",
        "PRET_SANS_INTERET, 10000.0",
        "AIDE_COTISATION, 1000.0",
        "DON_MATERIEL,",
        "PRET_MATERIEL,"
    })
    @DisplayName("getMontantMin - tous les types")
    void testGetMontantMin(TypeAide type, String expectedStr) {
      if (expectedStr == null || expectedStr.isEmpty()) {
        assertThat(type.getMontantMin()).isNull();
      } else {
        assertThat(type.getMontantMin()).isEqualTo(Double.parseDouble(expectedStr));
      }
    }

    @ParameterizedTest
    @CsvSource({
        "AIDE_FINANCIERE_URGENTE, 50000.0",
        "PRET_SANS_INTERET, 100000.0",
        "AIDE_COTISATION, 10000.0",
        "DON_MATERIEL,",
        "PRET_MATERIEL,"
    })
    @DisplayName("getMontantMax - tous les types")
    void testGetMontantMax(TypeAide type, String expectedStr) {
      if (expectedStr == null || expectedStr.isEmpty()) {
        assertThat(type.getMontantMax()).isNull();
      } else {
        assertThat(type.getMontantMax()).isEqualTo(Double.parseDouble(expectedStr));
      }
    }
  }

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

    @ParameterizedTest
    @CsvSource({
        "AIDE_FINANCIERE_URGENTE, true",
        "AIDE_FRAIS_MEDICAUX, true",
        "HEBERGEMENT_URGENCE, true",
        "AIDE_ALIMENTAIRE, true",
        "PRET_SANS_INTERET, false",
        "DON_MATERIEL, false"
    })
    @DisplayName("isUrgent - types urgents")
    void testIsUrgent(TypeAide type, Boolean expected) {
      assertThat(type.isUrgent()).isEqualTo(expected);
    }
  }

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

    @ParameterizedTest
    @CsvSource({
        "AIDE_FINANCIERE_URGENTE, true",
        "PRET_SANS_INTERET, true",
        "AIDE_COTISATION, true",
        "DON_MATERIEL, false",
        "AIDE_RECHERCHE_EMPLOI, false"
    })
    @DisplayName("isFinancier - types financiers")
    void testIsFinancier(TypeAide type, Boolean expected) {
      assertThat(type.isFinancier()).isEqualTo(expected);
    }
  }

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

    @ParameterizedTest
    @CsvSource({
        "DON_MATERIEL, true",
        "PRET_MATERIEL, true",
        "AIDE_DEMENAGEMENT, true",
        "AIDE_TRAVAUX, true",
        "AIDE_FINANCIERE_URGENTE, false"
    })
    @DisplayName("isMateriel - types matériels")
    void testIsMateriel(TypeAide type, Boolean expected) {
      assertThat(type.isMateriel()).isEqualTo(expected);
    }
  }

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

    @Test
    @DisplayName("isMontantValide - type ne nécessitant pas de montant")
    void testIsMontantValideTypeSansMontant() {
      assertThat(TypeAide.DON_MATERIEL.isMontantValide(null)).isTrue();
      assertThat(TypeAide.DON_MATERIEL.isMontantValide(1000.0)).isTrue();
    }

    @Test
    @DisplayName("isMontantValide - montant dans la fourchette")
    void testIsMontantValideDansFourchette() {
      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();
    }

    @Test
    @DisplayName("isMontantValide - montant en dessous du minimum")
    void testIsMontantValideEnDessousMinimum() {
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(1000.0)).isFalse();
    }

    @Test
    @DisplayName("isMontantValide - montant au-dessus du maximum")
    void testIsMontantValideAuDessusMaximum() {
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(60000.0)).isFalse();
    }

    @Test
    @DisplayName("isMontantValide - montant null avec necessiteMontant true")
    void testIsMontantValideMontantNullAvecNecessiteMontant() {
      // Quand necessiteMontant est true mais montant est null, retourne true
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(null)).isTrue();
    }

    @Test
    @DisplayName("isMontantValide - montantMin null (pas de minimum)")
    void testIsMontantValideMontantMinNull() {
      // Tous les types avec necessiteMontant ont un montantMin
      // On utilise la réflexion pour simuler montantMin null
      try {
        java.lang.reflect.Field montantMinField = TypeAide.class.getDeclaredField("montantMin");
        montantMinField.setAccessible(true);
        Double montantMinOriginal = (Double) montantMinField.get(TypeAide.AIDE_FINANCIERE_URGENTE);
        
        // Modifier temporairement montantMin à null
        montantMinField.set(TypeAide.AIDE_FINANCIERE_URGENTE, null);
        
        // Maintenant avec montantMin null, un montant bas ne devrait pas être rejeté par la condition montantMin
        // Mais si montantMax est dépassé, il sera rejeté
        assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(1000.0)).isTrue(); // En dessous du max
        assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(60000.0)).isFalse(); // Au-dessus du max
        
        // Restaurer montantMin original
        montantMinField.set(TypeAide.AIDE_FINANCIERE_URGENTE, montantMinOriginal);
      } catch (Exception e) {
        org.junit.jupiter.api.Assumptions.assumeTrue(false, "Réflexion non disponible");
      }
    }

    @Test
    @DisplayName("isMontantValide - montantMax null (pas de maximum)")
    void testIsMontantValideMontantMaxNull() {
      // Tous les types avec necessiteMontant ont un montantMax
      // On utilise la réflexion pour simuler montantMax null
      try {
        java.lang.reflect.Field montantMaxField = TypeAide.class.getDeclaredField("montantMax");
        montantMaxField.setAccessible(true);
        Double montantMaxOriginal = (Double) montantMaxField.get(TypeAide.AIDE_FINANCIERE_URGENTE);
        
        // Modifier temporairement montantMax à null
        montantMaxField.set(TypeAide.AIDE_FINANCIERE_URGENTE, null);
        
        // Maintenant avec montantMax null, un montant élevé ne devrait pas être rejeté par la condition montantMax
        // Mais si montantMin n'est pas respecté, il sera rejeté
        assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(100000.0)).isTrue(); // Au-dessus du min
        assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.isMontantValide(1000.0)).isFalse(); // En dessous du min
        
        // Restaurer montantMax original
        montantMaxField.set(TypeAide.AIDE_FINANCIERE_URGENTE, montantMaxOriginal);
      } catch (Exception e) {
        org.junit.jupiter.api.Assumptions.assumeTrue(false, "Réflexion non disponible");
      }
    }
  }

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

    @ParameterizedTest
    @CsvSource({
        "AIDE_FINANCIERE_URGENTE, 1",
        "PRET_SANS_INTERET, 2",
        "AIDE_COTISATION, 3",
        "DON_MATERIEL, 3"
    })
    @DisplayName("getNiveauPriorite - tous les niveaux")
    void testGetNiveauPriorite(TypeAide type, int expected) {
      assertThat(type.getNiveauPriorite()).isEqualTo(expected);
    }

    @Test
    @DisplayName("getNiveauPriorite - priorité inconnue (default)")
    void testGetNiveauPrioriteDefault() {
      // Pour tester la branche default, on devrait utiliser la réflexion pour modifier la priorité
      // Mais comme tous les types ont une priorité valide, on teste juste que le default retourne 3
      // En fait, tous les types ont une priorité valide, donc le default ne sera jamais atteint
      // Mais on peut tester avec une valeur qui n'est pas "urgent", "important", ou "normal"
      // En utilisant la réflexion pour simuler une priorité inconnue
      try {
        java.lang.reflect.Field prioriteField = TypeAide.class.getDeclaredField("priorite");
        prioriteField.setAccessible(true);
        
        // Sauvegarder la priorité originale
        String prioriteOriginale = (String) prioriteField.get(TypeAide.AIDE_COTISATION);
        
        // Modifier temporairement la priorité pour tester le default
        prioriteField.set(TypeAide.AIDE_COTISATION, "inconnue");
        
        // Le default devrait retourner 3
        assertThat(TypeAide.AIDE_COTISATION.getNiveauPriorite()).isEqualTo(3);
        
        // Restaurer la priorité originale
        prioriteField.set(TypeAide.AIDE_COTISATION, prioriteOriginale);
      } catch (Exception e) {
        // Si la réflexion échoue, on skip ce test
        org.junit.jupiter.api.Assumptions.assumeTrue(false, "Réflexion non disponible");
      }
    }
  }

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

    @Test
    @DisplayName("getDateLimiteReponse - retourne une date future")
    void testGetDateLimiteReponse() {
      var dateLimite = TypeAide.AIDE_FINANCIERE_URGENTE.getDateLimiteReponse();
      assertThat(dateLimite).isAfter(java.time.LocalDateTime.now());
    }
  }

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

    @ParameterizedTest
    @CsvSource({
        "AIDE_FINANCIERE_URGENTE, Aide financière",
        "DON_MATERIEL, Aide matérielle",
        "AIDE_RECHERCHE_EMPLOI, Aide professionnelle",
        "GARDE_ENFANTS, Aide sociale",
        "HEBERGEMENT_URGENCE, Aide d'urgence",
        "SOUTIEN_PSYCHOLOGIQUE, Aide spécialisée",
        "AUTRE, Autre"
    })
    @DisplayName("getLibelleCategorie - tous les libellés")
    void testGetLibelleCategorie(TypeAide type, String expected) {
      assertThat(type.getLibelleCategorie()).isEqualTo(expected);
    }

    @Test
    @DisplayName("getLibelleCategorie - catégorie inconnue (default)")
    void testGetLibelleCategorieDefault() {
      // Pour tester la branche default, on utilise la réflexion pour modifier la catégorie
      try {
        java.lang.reflect.Field categorieField = TypeAide.class.getDeclaredField("categorie");
        categorieField.setAccessible(true);
        
        // Sauvegarder la catégorie originale
        String categorieOriginale = (String) categorieField.get(TypeAide.AIDE_COTISATION);
        
        // Modifier temporairement la catégorie pour tester le default
        categorieField.set(TypeAide.AIDE_COTISATION, "categorie_inconnue");
        
        // Le default devrait retourner la catégorie telle quelle
        assertThat(TypeAide.AIDE_COTISATION.getLibelleCategorie()).isEqualTo("categorie_inconnue");
        
        // Restaurer la catégorie originale
        categorieField.set(TypeAide.AIDE_COTISATION, categorieOriginale);
      } catch (Exception e) {
        // Si la réflexion échoue, on skip ce test
        org.junit.jupiter.api.Assumptions.assumeTrue(false, "Réflexion non disponible");
      }
    }
  }

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

    @ParameterizedTest
    @CsvSource({
        "AIDE_FINANCIERE_URGENTE, FCFA",
        "PRET_SANS_INTERET, FCFA",
        "DON_MATERIEL,"
    })
    @DisplayName("getUniteMontant - unités correctes")
    void testGetUniteMontant(TypeAide type, String expected) {
      if (expected == null || expected.isEmpty()) {
        assertThat(type.getUniteMontant()).isNull();
      } else {
        assertThat(type.getUniteMontant()).isEqualTo(expected);
      }
    }
  }

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

    @Test
    @DisplayName("getMessageValidationMontant - type sans montant")
    void testGetMessageValidationMontantSansMontant() {
      assertThat(TypeAide.DON_MATERIEL.getMessageValidationMontant(1000.0)).isNull();
    }

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

    @Test
    @DisplayName("getMessageValidationMontant - montant valide")
    void testGetMessageValidationMontantValide() {
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(10000.0)).isNull();
    }

    @Test
    @DisplayName("getMessageValidationMontant - montant en dessous du minimum")
    void testGetMessageValidationMontantEnDessous() {
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(1000.0))
          .contains("minimum");
    }

    @Test
    @DisplayName("getMessageValidationMontant - montant au-dessus du maximum")
    void testGetMessageValidationMontantAuDessus() {
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(60000.0))
          .contains("maximum");
      assertThat(TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(60000.0))
          .contains("50000");
    }

    @Test
    @DisplayName("getMessageValidationMontant - montantMin null (pas de minimum)")
    void testGetMessageValidationMontantMontantMinNull() {
      // Tous les types avec necessiteMontant ont un montantMin
      // On teste avec un montant valide pour couvrir la branche où montantMin est null
      // En utilisant la réflexion pour simuler montantMin null
      try {
        java.lang.reflect.Field montantMinField = TypeAide.class.getDeclaredField("montantMin");
        montantMinField.setAccessible(true);
        Double montantMinOriginal = (Double) montantMinField.get(TypeAide.AIDE_FINANCIERE_URGENTE);
        
        // Modifier temporairement montantMin à null
        montantMinField.set(TypeAide.AIDE_FINANCIERE_URGENTE, null);
        
        // Maintenant avec montantMin null, un montant valide ne devrait pas retourner d'erreur de minimum
        String message = TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(1000.0);
        // Si montantMax est dépassé, on aura un message de maximum, sinon null
        assertThat(message == null || message.contains("maximum")).isTrue();
        
        // Restaurer montantMin original
        montantMinField.set(TypeAide.AIDE_FINANCIERE_URGENTE, montantMinOriginal);
      } catch (Exception e) {
        org.junit.jupiter.api.Assumptions.assumeTrue(false, "Réflexion non disponible");
      }
    }

    @Test
    @DisplayName("getMessageValidationMontant - montantMax null (pas de maximum)")
    void testGetMessageValidationMontantMontantMaxNull() {
      // Tous les types avec necessiteMontant ont un montantMax
      // On teste avec la réflexion pour simuler montantMax null
      try {
        java.lang.reflect.Field montantMaxField = TypeAide.class.getDeclaredField("montantMax");
        montantMaxField.setAccessible(true);
        Double montantMaxOriginal = (Double) montantMaxField.get(TypeAide.AIDE_FINANCIERE_URGENTE);
        
        // Modifier temporairement montantMax à null
        montantMaxField.set(TypeAide.AIDE_FINANCIERE_URGENTE, null);
        
        // Maintenant avec montantMax null, un montant élevé ne devrait pas retourner d'erreur de maximum
        String message = TypeAide.AIDE_FINANCIERE_URGENTE.getMessageValidationMontant(100000.0);
        // Si montantMin n'est pas respecté, on aura un message de minimum, sinon null
        assertThat(message == null || message.contains("minimum")).isTrue();
        
        // Restaurer montantMax original
        montantMaxField.set(TypeAide.AIDE_FINANCIERE_URGENTE, montantMaxOriginal);
      } catch (Exception e) {
        org.junit.jupiter.api.Assumptions.assumeTrue(false, "Réflexion non disponible");
      }
    }
  }

  @Nested
  @DisplayName("Tests méthodes statiques")
  class MethodesStatiquesTests {

    @Test
    @DisplayName("getParCategorie - retourne les types par catégorie")
    void testGetParCategorie() {
      List<TypeAide> types = TypeAide.getParCategorie("financiere");
      assertThat(types).isNotEmpty();
      assertThat(types).allMatch(TypeAide::isFinancier);
    }

    @Test
    @DisplayName("getParCategorie - catégorie inexistante retourne liste vide")
    void testGetParCategorieInexistante() {
      List<TypeAide> types = TypeAide.getParCategorie("categorie_inexistante");
      assertThat(types).isEmpty();
    }

    @Test
    @DisplayName("getParCategorie - catégorie null")
    void testGetParCategorieNull() {
      // getParCategorie utilise equals() qui peut lancer NullPointerException si categorie est null
      // Mais on teste quand même pour couvrir cette branche
      List<TypeAide> types = TypeAide.getParCategorie(null);
      assertThat(types).isEmpty();
    }

    @Test
    @DisplayName("getUrgents - retourne les types urgents")
    void testGetUrgents() {
      List<TypeAide> types = TypeAide.getUrgents();
      assertThat(types).isNotEmpty();
      assertThat(types).allMatch(TypeAide::isUrgent);
    }

    @Test
    @DisplayName("getFinanciers - retourne les types financiers")
    void testGetFinanciers() {
      List<TypeAide> types = TypeAide.getFinanciers();
      assertThat(types).isNotEmpty();
      assertThat(types).allMatch(TypeAide::isFinancier);
    }

    @Test
    @DisplayName("getCategories - retourne toutes les catégories")
    void testGetCategories() {
      Set<String> categories = TypeAide.getCategories();
      assertThat(categories).isNotEmpty();
      assertThat(categories).contains("financiere", "materielle", "professionnelle");
    }
  }
}
