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

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

import java.util.List;
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 StatutEvenement")
class StatutEvenementTest {

  @Test
  @DisplayName("Test de base - enum peut être utilisé")
  void testEnumUtilisable() {
    assertThat(StatutEvenement.PLANIFIE).isNotNull();
  }

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

    @Test
    @DisplayName("Test toutes les valeurs enum")
    void testToutesValeurs() {
      StatutEvenement[] values = StatutEvenement.values();
      assertThat(values).hasSize(6);
      assertThat(values).containsExactly(
          StatutEvenement.PLANIFIE,
          StatutEvenement.CONFIRME,
          StatutEvenement.EN_COURS,
          StatutEvenement.TERMINE,
          StatutEvenement.ANNULE,
          StatutEvenement.REPORTE);
    }

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

  @Nested
  @DisplayName("Tests permetModification")
  class PermetModificationTests {

    @ParameterizedTest
    @CsvSource({
        "PLANIFIE, true",
        "CONFIRME, true",
        "REPORTE, true",
        "EN_COURS, false",
        "TERMINE, false",
        "ANNULE, false"
    })
    @DisplayName("permetModification - statuts modifiables")
    void testPermetModification(StatutEvenement statut, Boolean expected) {
      assertThat(statut.permetModification()).isEqualTo(expected);
    }
  }

  @Nested
  @DisplayName("Tests permetAnnulation")
  class PermetAnnulationTests {

    @ParameterizedTest
    @CsvSource({
        "PLANIFIE, true",
        "CONFIRME, true",
        "EN_COURS, true",
        "REPORTE, true",
        "TERMINE, false",
        "ANNULE, false"
    })
    @DisplayName("permetAnnulation - statuts annulables")
    void testPermetAnnulation(StatutEvenement statut, Boolean expected) {
      assertThat(statut.permetAnnulation()).isEqualTo(expected);
    }
  }

  @Nested
  @DisplayName("Tests isEnCours")
  class IsEnCoursTests {

    @ParameterizedTest
    @CsvSource({
        "EN_COURS, true",
        "PLANIFIE, false",
        "TERMINE, false"
    })
    @DisplayName("isEnCours - statut en cours")
    void testIsEnCours(StatutEvenement statut, Boolean expected) {
      assertThat(statut.isEnCours()).isEqualTo(expected);
    }
  }

  @Nested
  @DisplayName("Tests isSucces")
  class IsSuccesTests {

    @ParameterizedTest
    @CsvSource({
        "TERMINE, true",
        "PLANIFIE, false",
        "ANNULE, false"
    })
    @DisplayName("isSucces - statut de succès")
    void testIsSucces(StatutEvenement statut, Boolean expected) {
      assertThat(statut.isSucces()).isEqualTo(expected);
    }
  }

  @Nested
  @DisplayName("Tests peutTransitionnerVers")
  class PeutTransitionnerVersTests {

    @Test
    @DisplayName("peutTransitionnerVers - même statut retourne false")
    void testPeutTransitionnerVersMemeStatut() {
      assertThat(StatutEvenement.PLANIFIE.peutTransitionnerVers(StatutEvenement.PLANIFIE))
          .isFalse();
    }

    @Test
    @DisplayName("peutTransitionnerVers - statut final vers autre statut (sauf REPORTE)")
    void testPeutTransitionnerVersStatutFinal() {
      assertThat(StatutEvenement.TERMINE.peutTransitionnerVers(StatutEvenement.PLANIFIE))
          .isFalse();
      assertThat(StatutEvenement.ANNULE.peutTransitionnerVers(StatutEvenement.PLANIFIE))
          .isFalse();
    }

    @Test
    @DisplayName("peutTransitionnerVers - statut final vers REPORTE")
    void testPeutTransitionnerVersStatutFinalVersReporte() {
      // estFinal && nouveauStatut != REPORTE retourne false
      // Mais TERMINE.estFinal = true, donc la condition estFinal && nouveauStatut != REPORTE
      // est true && true = true, donc retourne false
      assertThat(StatutEvenement.TERMINE.peutTransitionnerVers(StatutEvenement.REPORTE))
          .isFalse();
      assertThat(StatutEvenement.ANNULE.peutTransitionnerVers(StatutEvenement.REPORTE))
          .isFalse();
      
      // Test avec nouveauStatut == REPORTE : estFinal && nouveauStatut != REPORTE
      // = true && false = false, donc on continue vers le switch
      // Mais TERMINE et ANNULE ne sont pas dans le switch, donc default -> false
      // Donc le résultat est toujours false
      // Mais testons aussi avec un statut non-final vers REPORTE pour couvrir la branche
      assertThat(StatutEvenement.PLANIFIE.peutTransitionnerVers(StatutEvenement.REPORTE))
          .isTrue();
    }

    @Test
    @DisplayName("peutTransitionnerVers - REPORTE vers CONFIRME (non géré dans switch)")
    void testPeutTransitionnerVersReporteVersConfirme() {
      // REPORTE -> CONFIRME n'est pas dans le switch, donc retourne false (default)
      assertThat(StatutEvenement.REPORTE.peutTransitionnerVers(StatutEvenement.CONFIRME))
          .isFalse();
      assertThat(StatutEvenement.REPORTE.peutTransitionnerVers(StatutEvenement.EN_COURS))
          .isFalse();
      assertThat(StatutEvenement.REPORTE.peutTransitionnerVers(StatutEvenement.TERMINE))
          .isFalse();
    }

    @ParameterizedTest
    @CsvSource({
        "PLANIFIE, CONFIRME, true",
        "PLANIFIE, ANNULE, true",
        "PLANIFIE, REPORTE, true",
        "PLANIFIE, EN_COURS, false",
        "PLANIFIE, TERMINE, false",
        "CONFIRME, EN_COURS, true",
        "CONFIRME, ANNULE, true",
        "CONFIRME, REPORTE, true",
        "CONFIRME, PLANIFIE, false",
        "CONFIRME, TERMINE, false",
        "EN_COURS, TERMINE, true",
        "EN_COURS, ANNULE, true",
        "EN_COURS, PLANIFIE, false",
        "EN_COURS, CONFIRME, false",
        "REPORTE, PLANIFIE, true",
        "REPORTE, ANNULE, true",
        "REPORTE, CONFIRME, false",
        "REPORTE, EN_COURS, false",
        "TERMINE, ANNULE, false",
        "ANNULE, TERMINE, false"
    })
    @DisplayName("peutTransitionnerVers - toutes les transitions")
    void testPeutTransitionnerVersToutesTransitions(
        StatutEvenement source, StatutEvenement cible, boolean expected) {
      assertThat(source.peutTransitionnerVers(cible)).isEqualTo(expected);
    }
  }

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

    @ParameterizedTest
    @CsvSource({
        "EN_COURS, 1",
        "CONFIRME, 2",
        "PLANIFIE, 3",
        "REPORTE, 4",
        "TERMINE, 5",
        "ANNULE, 6"
    })
    @DisplayName("getNiveauPriorite - tous les niveaux")
    void testGetNiveauPriorite(StatutEvenement statut, int expected) {
      assertThat(statut.getNiveauPriorite()).isEqualTo(expected);
    }
  }

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

    @Test
    @DisplayName("getStatutsFinaux - retourne les statuts finaux")
    void testGetStatutsFinaux() {
      List<StatutEvenement> statuts = StatutEvenement.getStatutsFinaux();
      assertThat(statuts).isNotEmpty();
      assertThat(statuts).contains(StatutEvenement.TERMINE, StatutEvenement.ANNULE);
    }

    @Test
    @DisplayName("getStatutsEchec - retourne les statuts d'échec")
    void testGetStatutsEchec() {
      List<StatutEvenement> statuts = StatutEvenement.getStatutsEchec();
      assertThat(statuts).isNotEmpty();
      assertThat(statuts).contains(StatutEvenement.ANNULE);
    }

    @Test
    @DisplayName("getStatutsActifs - retourne les statuts actifs")
    void testGetStatutsActifs() {
      StatutEvenement[] statuts = StatutEvenement.getStatutsActifs();
      assertThat(statuts).hasSize(4);
      assertThat(statuts).contains(
          StatutEvenement.PLANIFIE,
          StatutEvenement.CONFIRME,
          StatutEvenement.EN_COURS,
          StatutEvenement.REPORTE);
    }

    @Test
    @DisplayName("fromCode - trouve le statut par code")
    void testFromCode() {
      assertThat(StatutEvenement.fromCode("planned")).isEqualTo(StatutEvenement.PLANIFIE);
      assertThat(StatutEvenement.fromCode("completed")).isEqualTo(StatutEvenement.TERMINE);
    }

    @Test
    @DisplayName("fromCode - code inexistant retourne null")
    void testFromCodeInexistant() {
      assertThat(StatutEvenement.fromCode("inexistant")).isNull();
    }

    @Test
    @DisplayName("fromCode - code null retourne null")
    void testFromCodeNull() {
      assertThat(StatutEvenement.fromCode(null)).isNull();
    }

    @Test
    @DisplayName("fromCode - code vide retourne null")
    void testFromCodeVide() {
      assertThat(StatutEvenement.fromCode("")).isNull();
    }

    @Test
    @DisplayName("fromCode - code avec whitespace retourne null")
    void testFromCodeWhitespace() {
      assertThat(StatutEvenement.fromCode("   ")).isNull();
    }

    @Test
    @DisplayName("fromCode - tous les codes")
    void testFromCodeTousLesCodes() {
      assertThat(StatutEvenement.fromCode("planned")).isEqualTo(StatutEvenement.PLANIFIE);
      assertThat(StatutEvenement.fromCode("confirmed")).isEqualTo(StatutEvenement.CONFIRME);
      assertThat(StatutEvenement.fromCode("ongoing")).isEqualTo(StatutEvenement.EN_COURS);
      assertThat(StatutEvenement.fromCode("completed")).isEqualTo(StatutEvenement.TERMINE);
      assertThat(StatutEvenement.fromCode("cancelled")).isEqualTo(StatutEvenement.ANNULE);
      assertThat(StatutEvenement.fromCode("postponed")).isEqualTo(StatutEvenement.REPORTE);
    }

    @Test
    @DisplayName("fromLibelle - trouve le statut par libellé")
    void testFromLibelle() {
      assertThat(StatutEvenement.fromLibelle("Planifié")).isEqualTo(StatutEvenement.PLANIFIE);
      assertThat(StatutEvenement.fromLibelle("Terminé")).isEqualTo(StatutEvenement.TERMINE);
    }

    @Test
    @DisplayName("fromLibelle - libellé inexistant retourne null")
    void testFromLibelleInexistant() {
      assertThat(StatutEvenement.fromLibelle("Inexistant")).isNull();
    }

    @Test
    @DisplayName("fromLibelle - libellé null retourne null")
    void testFromLibelleNull() {
      assertThat(StatutEvenement.fromLibelle(null)).isNull();
    }

    @Test
    @DisplayName("fromLibelle - libellé vide retourne null")
    void testFromLibelleVide() {
      assertThat(StatutEvenement.fromLibelle("")).isNull();
    }

    @Test
    @DisplayName("fromLibelle - libellé avec whitespace retourne null")
    void testFromLibelleWhitespace() {
      assertThat(StatutEvenement.fromLibelle("   ")).isNull();
    }

    @Test
    @DisplayName("fromLibelle - tous les libellés")
    void testFromLibelleTousLesLibelles() {
      assertThat(StatutEvenement.fromLibelle("Planifié")).isEqualTo(StatutEvenement.PLANIFIE);
      assertThat(StatutEvenement.fromLibelle("Confirmé")).isEqualTo(StatutEvenement.CONFIRME);
      assertThat(StatutEvenement.fromLibelle("En cours")).isEqualTo(StatutEvenement.EN_COURS);
      assertThat(StatutEvenement.fromLibelle("Terminé")).isEqualTo(StatutEvenement.TERMINE);
      assertThat(StatutEvenement.fromLibelle("Annulé")).isEqualTo(StatutEvenement.ANNULE);
      assertThat(StatutEvenement.fromLibelle("Reporté")).isEqualTo(StatutEvenement.REPORTE);
    }
  }

  @Nested
  @DisplayName("Tests getTransitionsPossibles")
  class GetTransitionsPossiblesTests {

    @Test
    @DisplayName("getTransitionsPossibles - PLANIFIE")
    void testGetTransitionsPossiblesPlanifie() {
      StatutEvenement[] transitions = StatutEvenement.PLANIFIE.getTransitionsPossibles();
      assertThat(transitions).containsExactly(
          StatutEvenement.CONFIRME,
          StatutEvenement.ANNULE,
          StatutEvenement.REPORTE);
    }

    @Test
    @DisplayName("getTransitionsPossibles - CONFIRME")
    void testGetTransitionsPossiblesConfirme() {
      StatutEvenement[] transitions = StatutEvenement.CONFIRME.getTransitionsPossibles();
      assertThat(transitions).containsExactly(
          StatutEvenement.EN_COURS,
          StatutEvenement.ANNULE,
          StatutEvenement.REPORTE);
    }

    @Test
    @DisplayName("getTransitionsPossibles - EN_COURS")
    void testGetTransitionsPossiblesEnCours() {
      StatutEvenement[] transitions = StatutEvenement.EN_COURS.getTransitionsPossibles();
      assertThat(transitions).containsExactly(
          StatutEvenement.TERMINE,
          StatutEvenement.ANNULE);
    }

    @Test
    @DisplayName("getTransitionsPossibles - REPORTE")
    void testGetTransitionsPossiblesReporte() {
      StatutEvenement[] transitions = StatutEvenement.REPORTE.getTransitionsPossibles();
      // Note: selon le code, REPORTE peut transitionner vers PLANIFIE, CONFIRME ou ANNULE
      // Mais peutTransitionnerVers ne permet que PLANIFIE ou ANNULE
      assertThat(transitions).containsExactly(
          StatutEvenement.PLANIFIE,
          StatutEvenement.CONFIRME,
          StatutEvenement.ANNULE);
    }

    @Test
    @DisplayName("getTransitionsPossibles - TERMINE")
    void testGetTransitionsPossiblesTermine() {
      StatutEvenement[] transitions = StatutEvenement.TERMINE.getTransitionsPossibles();
      assertThat(transitions).isEmpty();
    }

    @Test
    @DisplayName("getTransitionsPossibles - ANNULE")
    void testGetTransitionsPossiblesAnnule() {
      StatutEvenement[] transitions = StatutEvenement.ANNULE.getTransitionsPossibles();
      assertThat(transitions).isEmpty();
    }
  }

  @Nested
  @DisplayName("Tests isEstFinal et isEstEchec")
  class IsEstFinalEtEchecTests {

    @ParameterizedTest
    @CsvSource({
        "PLANIFIE, false, false",
        "CONFIRME, false, false",
        "EN_COURS, false, false",
        "TERMINE, true, false",
        "ANNULE, true, true",
        "REPORTE, false, false"
    })
    @DisplayName("isEstFinal et isEstEchec - tous les statuts")
    void testIsEstFinalEtEchec(StatutEvenement statut, boolean estFinal, boolean estEchec) {
      assertThat(statut.isEstFinal()).isEqualTo(estFinal);
      assertThat(statut.isEstEchec()).isEqualTo(estEchec);
    }
  }
}
