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

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

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

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

  @Test
  @DisplayName("Toutes les priorités doivent avoir un libellé")
  void testToutesLesPrioritesOntLibelle() {
    for (PrioriteAide priorite : PrioriteAide.values()) {
      assertThat(priorite.getLibelle()).isNotNull().isNotBlank();
    }
  }

  @Test
  @DisplayName("Toutes les priorités doivent avoir un code")
  void testToutesLesPrioritesOntCode() {
    for (PrioriteAide priorite : PrioriteAide.values()) {
      assertThat(priorite.getCode()).isNotNull().isNotBlank();
    }
  }

  @Nested
  @DisplayName("Tests isUrgente")
  class IsUrgenteTests {

    @Test
    @DisplayName("isUrgente doit retourner true pour CRITIQUE et URGENTE")
    void testIsUrgente() {
      assertThat(PrioriteAide.CRITIQUE.isUrgente()).isTrue();
      assertThat(PrioriteAide.URGENTE.isUrgente()).isTrue();

      assertThat(PrioriteAide.ELEVEE.isUrgente()).isFalse();
      assertThat(PrioriteAide.NORMALE.isUrgente()).isFalse();
      assertThat(PrioriteAide.FAIBLE.isUrgente()).isFalse();
    }
  }

  @Nested
  @DisplayName("Tests necessiteTraitementImmediat")
  class NecessiteTraitementImmediatTests {

    @Test
    @DisplayName("necessiteTraitementImmediat doit retourner true pour niveau <= 2")
    void testNecessiteTraitementImmediat() {
      assertThat(PrioriteAide.CRITIQUE.necessiteTraitementImmediat()).isTrue();
      assertThat(PrioriteAide.URGENTE.necessiteTraitementImmediat()).isTrue();

      assertThat(PrioriteAide.ELEVEE.necessiteTraitementImmediat()).isFalse();
      assertThat(PrioriteAide.NORMALE.necessiteTraitementImmediat()).isFalse();
      assertThat(PrioriteAide.FAIBLE.necessiteTraitementImmediat()).isFalse();
    }
  }

  @Nested
  @DisplayName("Tests getDateLimiteTraitement")
  class GetDateLimiteTraitementTests {

    @Test
    @DisplayName("getDateLimiteTraitement doit retourner une date future")
    void testGetDateLimiteTraitement() {
      LocalDateTime maintenant = LocalDateTime.now();
      LocalDateTime dateLimite = PrioriteAide.CRITIQUE.getDateLimiteTraitement();

      assertThat(dateLimite).isAfter(maintenant);
      assertThat(dateLimite).isBefore(maintenant.plusHours(25)); // CRITIQUE = 24h
    }
  }

  @Nested
  @DisplayName("Tests getPrioriteEscalade")
  class GetPrioriteEscaladeTests {

    @Test
    @DisplayName("getPrioriteEscalade doit retourner la priorité suivante")
    void testGetPrioriteEscalade() {
      assertThat(PrioriteAide.FAIBLE.getPrioriteEscalade()).isEqualTo(PrioriteAide.NORMALE);
      assertThat(PrioriteAide.NORMALE.getPrioriteEscalade()).isEqualTo(PrioriteAide.ELEVEE);
      assertThat(PrioriteAide.ELEVEE.getPrioriteEscalade()).isEqualTo(PrioriteAide.URGENTE);
      assertThat(PrioriteAide.URGENTE.getPrioriteEscalade()).isEqualTo(PrioriteAide.CRITIQUE);
      assertThat(PrioriteAide.CRITIQUE.getPrioriteEscalade()).isEqualTo(PrioriteAide.CRITIQUE);
    }
  }

  @Nested
  @DisplayName("Tests determinerPriorite")
  class DeterminerPrioriteTests {

    @Test
    @DisplayName("determinerPriorite doit retourner CRITIQUE pour AIDE_FINANCIERE_URGENTE")
    void testDeterminerPrioriteCritique() {
      assertThat(PrioriteAide.determinerPriorite(TypeAide.AIDE_FINANCIERE_URGENTE))
          .isEqualTo(PrioriteAide.CRITIQUE);
      assertThat(PrioriteAide.determinerPriorite(TypeAide.AIDE_FRAIS_MEDICAUX))
          .isEqualTo(PrioriteAide.CRITIQUE);
    }

    @Test
    @DisplayName("determinerPriorite doit retourner URGENTE pour HEBERGEMENT_URGENCE et AIDE_ALIMENTAIRE")
    void testDeterminerPrioriteUrgente() {
      assertThat(PrioriteAide.determinerPriorite(TypeAide.HEBERGEMENT_URGENCE))
          .isEqualTo(PrioriteAide.URGENTE);
      assertThat(PrioriteAide.determinerPriorite(TypeAide.AIDE_ALIMENTAIRE))
          .isEqualTo(PrioriteAide.URGENTE);
    }

    @Test
    @DisplayName("determinerPriorite doit retourner ELEVEE pour types urgents non spécifiques")
    void testDeterminerPrioriteElevee() {
      assertThat(PrioriteAide.determinerPriorite(TypeAide.PRET_SANS_INTERET))
          .isEqualTo(PrioriteAide.ELEVEE);
    }

    @Test
    @DisplayName("determinerPriorite retourne ELEVEE (default switch) pour type urgent non listé (reflection)")
    void testDeterminerPrioriteUrgentDefault() throws Exception {
      java.lang.reflect.Field prioriteField = TypeAide.class.getDeclaredField("priorite");
      prioriteField.setAccessible(true);
      String backupPriorite = (String) prioriteField.get(TypeAide.AIDE_VESTIMENTAIRE);
      try {
        prioriteField.set(TypeAide.AIDE_VESTIMENTAIRE, "urgent");
        assertThat(TypeAide.AIDE_VESTIMENTAIRE.isUrgent()).isTrue();
        assertThat(PrioriteAide.determinerPriorite(TypeAide.AIDE_VESTIMENTAIRE))
            .isEqualTo(PrioriteAide.ELEVEE);
      } finally {
        prioriteField.set(TypeAide.AIDE_VESTIMENTAIRE, backupPriorite);
      }
    }

    @Test
    @DisplayName("determinerPriorite doit retourner ELEVEE pour types avec priorite important")
    void testDeterminerPrioriteImportant() {
      assertThat(PrioriteAide.determinerPriorite(TypeAide.PRET_SANS_INTERET))
          .isEqualTo(PrioriteAide.ELEVEE);
      assertThat(PrioriteAide.determinerPriorite(TypeAide.AIDE_FRAIS_SCOLARITE))
          .isEqualTo(PrioriteAide.ELEVEE);
    }

    @Test
    @DisplayName("determinerPriorite doit retourner NORMALE par défaut")
    void testDeterminerPrioriteNormale() {
      assertThat(PrioriteAide.determinerPriorite(TypeAide.DON_MATERIEL))
          .isEqualTo(PrioriteAide.NORMALE);
      assertThat(PrioriteAide.determinerPriorite(TypeAide.TRANSPORT))
          .isEqualTo(PrioriteAide.NORMALE);
    }
  }

  @Nested
  @DisplayName("Tests getPrioritesUrgentes")
  class GetPrioritesUrgentesTests {

    @Test
    @DisplayName("getPrioritesUrgentes doit retourner CRITIQUE et URGENTE")
    void testGetPrioritesUrgentes() {
      List<PrioriteAide> prioritésUrgentes = PrioriteAide.getPrioritesUrgentes();

      assertThat(prioritésUrgentes).hasSize(2);
      assertThat(prioritésUrgentes).contains(PrioriteAide.CRITIQUE);
      assertThat(prioritésUrgentes).contains(PrioriteAide.URGENTE);
    }
  }

  @Nested
  @DisplayName("Tests getParNiveauCroissant")
  class GetParNiveauCroissantTests {

    @Test
    @DisplayName("getParNiveauCroissant doit retourner les priorités triées par niveau croissant")
    void testGetParNiveauCroissant() {
      List<PrioriteAide> priorités = PrioriteAide.getParNiveauCroissant();

      assertThat(priorités).hasSize(5);
      assertThat(priorités.get(0)).isEqualTo(PrioriteAide.CRITIQUE);
      assertThat(priorités.get(1)).isEqualTo(PrioriteAide.URGENTE);
      assertThat(priorités.get(2)).isEqualTo(PrioriteAide.ELEVEE);
      assertThat(priorités.get(3)).isEqualTo(PrioriteAide.NORMALE);
      assertThat(priorités.get(4)).isEqualTo(PrioriteAide.FAIBLE);
    }
  }

  @Nested
  @DisplayName("Tests getParNiveauDecroissant")
  class GetParNiveauDecroissantTests {

    @Test
    @DisplayName("getParNiveauDecroissant doit retourner les priorités triées par niveau décroissant")
    void testGetParNiveauDecroissant() {
      List<PrioriteAide> priorités = PrioriteAide.getParNiveauDecroissant();

      assertThat(priorités).hasSize(5);
      assertThat(priorités.get(0)).isEqualTo(PrioriteAide.FAIBLE);
      assertThat(priorités.get(1)).isEqualTo(PrioriteAide.NORMALE);
      assertThat(priorités.get(2)).isEqualTo(PrioriteAide.ELEVEE);
      assertThat(priorités.get(3)).isEqualTo(PrioriteAide.URGENTE);
      assertThat(priorités.get(4)).isEqualTo(PrioriteAide.CRITIQUE);
    }
  }

  @Nested
  @DisplayName("Tests parCode")
  class ParCodeTests {

    @Test
    @DisplayName("parCode doit retourner la priorité correspondante")
    void testParCode() {
      assertThat(PrioriteAide.parCode("critical")).isEqualTo(PrioriteAide.CRITIQUE);
      assertThat(PrioriteAide.parCode("urgent")).isEqualTo(PrioriteAide.URGENTE);
      assertThat(PrioriteAide.parCode("high")).isEqualTo(PrioriteAide.ELEVEE);
      assertThat(PrioriteAide.parCode("normal")).isEqualTo(PrioriteAide.NORMALE);
      assertThat(PrioriteAide.parCode("low")).isEqualTo(PrioriteAide.FAIBLE);
    }

    @Test
    @DisplayName("parCode doit retourner NORMALE pour un code inconnu")
    void testParCodeInconnu() {
      assertThat(PrioriteAide.parCode("unknown")).isEqualTo(PrioriteAide.NORMALE);
      assertThat(PrioriteAide.parCode("")).isEqualTo(PrioriteAide.NORMALE);
    }
  }

  @Nested
  @DisplayName("Tests getScorePriorite")
  class GetScorePrioriteTests {

    @Test
    @DisplayName("getScorePriorite doit calculer correctement")
    void testGetScorePriorite() {
      // CRITIQUE: niveau=1, notificationImmediate=true, escaladeAutomatique=true
      // Score = 1 - 0.5 - 0.3 = 0.2
      double scoreCritique = PrioriteAide.CRITIQUE.getScorePriorite();
      assertThat(scoreCritique).isLessThan(1.0);

      // URGENTE: niveau=2, notificationImmediate=true, escaladeAutomatique=false
      // Score = 2 - 0.5 = 1.5
      double scoreUrgente = PrioriteAide.URGENTE.getScorePriorite();
      assertThat(scoreUrgente).isLessThan(2.0);

      // FAIBLE: niveau=5, notificationImmediate=false, escaladeAutomatique=false,
      // delaiTraitementHeures=720 > 168
      // Score = 5 + 0.2 = 5.2
      double scoreFaible = PrioriteAide.FAIBLE.getScorePriorite();
      assertThat(scoreFaible).isGreaterThan(5.0);
    }
  }

  @Nested
  @DisplayName("Tests isDelaiDepasse")
  class IsDelaiDepasseTests {

    @Test
    @DisplayName("isDelaiDepasse doit retourner false si le délai n'est pas dépassé")
    void testIsDelaiDepasseNonDepasse() {
      LocalDateTime dateCreation = LocalDateTime.now().minusHours(10);
      assertThat(PrioriteAide.CRITIQUE.isDelaiDepasse(dateCreation)).isFalse();
    }

    @Test
    @DisplayName("isDelaiDepasse doit retourner true si le délai est dépassé")
    void testIsDelaiDepasseDepasse() {
      LocalDateTime dateCreation = LocalDateTime.now().minusHours(25);
      assertThat(PrioriteAide.CRITIQUE.isDelaiDepasse(dateCreation)).isTrue();
    }

    @Test
    @DisplayName("isDelaiDepasse avec date de référence doit fonctionner correctement")
    void testIsDelaiDepasseAvecDateReference() {
      LocalDateTime dateCreation = LocalDateTime.now().minusHours(10);
      LocalDateTime maintenant = LocalDateTime.now().plusHours(20);
      assertThat(PrioriteAide.CRITIQUE.isDelaiDepasse(dateCreation, maintenant)).isTrue();

      maintenant = LocalDateTime.now().plusHours(10);
      assertThat(PrioriteAide.CRITIQUE.isDelaiDepasse(dateCreation, maintenant)).isFalse();
    }

    @Test
    @DisplayName("isDelaiDepasse doit retourner false si exactement à la limite")
    void testIsDelaiDepasseALaLimite() {
      // Pour être exactement à la limite dans isDelaiDepasse(dateCreation,
      // maintenant),
      // il faut passer maintenant = dateCreation.plusHours(delai)
      LocalDateTime dateCreation = LocalDateTime.now().minusHours(24);
      LocalDateTime maintenant = dateCreation.plusHours(24); // Ex: CRITIQUE

      assertThat(PrioriteAide.CRITIQUE.isDelaiDepasse(dateCreation, maintenant)).isFalse();
    }
  }

  @Nested
  @DisplayName("Tests getPourcentageTempsEcoule")
  class GetPourcentageTempsEcouleTests {

    @Test
    @DisplayName("getPourcentageTempsEcoule doit calculer correctement")
    void testGetPourcentageTempsEcoule() {
      LocalDateTime dateCreation = LocalDateTime.now().minusHours(12);
      // CRITIQUE = 24h, donc 12h = 50%
      double pourcentage = PrioriteAide.CRITIQUE.getPourcentageTempsEcoule(dateCreation);
      assertThat(pourcentage).isBetween(45.0, 55.0);
    }

    @Test
    @DisplayName("getPourcentageTempsEcoule doit retourner 100 si délai dépassé")
    void testGetPourcentageTempsEcouleDepasse() {
      LocalDateTime dateCreation = LocalDateTime.now().minusHours(30);
      double pourcentage = PrioriteAide.CRITIQUE.getPourcentageTempsEcoule(dateCreation);
      assertThat(pourcentage).isEqualTo(100.0);
    }

    @Test
    @DisplayName("getPourcentageTempsEcoule doit gérer dureeTotal <= 0")
    void testGetPourcentageTempsEcouleDureeZero() {
      double pourcentage = PrioriteAide.CRITIQUE.getPourcentageTempsEcoule(LocalDateTime.now());
      assertThat(pourcentage).isGreaterThanOrEqualTo(0.0);
      assertThat(pourcentage).isLessThanOrEqualTo(100.0);
    }

    @Test
    @DisplayName("getPourcentageTempsEcoule retourne 100 quand dureeTotal <= 0 (reflection)")
    void testGetPourcentageTempsEcouleDureeTotalZero() throws Exception {
      java.lang.reflect.Field f = PrioriteAide.class.getDeclaredField("delaiTraitementHeures");
      f.setAccessible(true);
      int backup = (int) f.get(PrioriteAide.CRITIQUE);
      try {
        f.setInt(PrioriteAide.CRITIQUE, 0);
        LocalDateTime dateCreation = LocalDateTime.now();
        assertThat(PrioriteAide.CRITIQUE.getPourcentageTempsEcoule(dateCreation)).isEqualTo(100.0);
      } finally {
        f.setInt(PrioriteAide.CRITIQUE, backup);
      }
    }
  }

  @Nested
  @DisplayName("Tests getMessageAlerte")
  class GetMessageAlerteTests {

    @Test
    @DisplayName("getMessageAlerte doit retourner le bon message selon le pourcentage")
    void testGetMessageAlerte() {
      // Pourcentage >= 100
      LocalDateTime dateCreation = LocalDateTime.now().minusHours(30);
      assertThat(PrioriteAide.CRITIQUE.getMessageAlerte(dateCreation))
          .isEqualTo("Délai de traitement dépassé !");

      // Pourcentage >= 80
      dateCreation = LocalDateTime.now().minusHours(20);
      assertThat(PrioriteAide.CRITIQUE.getMessageAlerte(dateCreation))
          .isEqualTo("Délai de traitement bientôt dépassé");

      // Pourcentage >= 60
      dateCreation = LocalDateTime.now().minusHours(15);
      assertThat(PrioriteAide.CRITIQUE.getMessageAlerte(dateCreation))
          .isEqualTo("Plus de la moitié du délai écoulé");

      // Pourcentage < 60
      dateCreation = LocalDateTime.now().minusHours(5);
      assertThat(PrioriteAide.CRITIQUE.getMessageAlerte(dateCreation)).isNull();
    }
  }
}
