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

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

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
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 PeriodeAnalyse")
class PeriodeAnalyseTest {

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

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

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

    @ParameterizedTest
    @EnumSource(PeriodeAnalyse.class)
    @DisplayName("Test getters de base pour toutes les valeurs")
    void testGettersBase(PeriodeAnalyse periode) {
      assertThat(periode.getLibelle()).isNotNull().isNotEmpty();
      assertThat(periode.getCode()).isNotNull().isNotEmpty();
      assertThat(periode.getDuree()).isGreaterThanOrEqualTo(0);
      assertThat(periode.getUnite()).isNotNull();
    }
  }

  @Nested
  @DisplayName("Tests getDateDebut")
  class GetDateDebutTests {

    @Test
    @DisplayName("getDateDebut - AUJOURD_HUI")
    void testGetDateDebutAujourdHui() {
      LocalDateTime debut = PeriodeAnalyse.AUJOURD_HUI.getDateDebut();
      assertThat(debut).isNotNull();
      assertThat(debut.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate());
      assertThat(debut.getHour()).isEqualTo(0);
      assertThat(debut.getMinute()).isEqualTo(0);
    }

    @Test
    @DisplayName("getDateDebut - HIER")
    void testGetDateDebutHier() {
      LocalDateTime debut = PeriodeAnalyse.HIER.getDateDebut();
      assertThat(debut).isNotNull();
      assertThat(debut.toLocalDate()).isEqualTo(LocalDateTime.now().minusDays(1).toLocalDate());
    }

    @Test
    @DisplayName("getDateDebut - CETTE_SEMAINE")
    void testGetDateDebutCetteSemaine() {
      LocalDateTime debut = PeriodeAnalyse.CETTE_SEMAINE.getDateDebut();
      assertThat(debut).isNotNull();
      // Doit être le lundi de cette semaine
      assertThat(debut.getDayOfWeek().getValue()).isEqualTo(1); // Lundi
    }

    @Test
    @DisplayName("getDateDebut - SEMAINE_DERNIERE")
    void testGetDateDebutSemaineDerniere() {
      LocalDateTime debut = PeriodeAnalyse.SEMAINE_DERNIERE.getDateDebut();
      assertThat(debut).isNotNull();
      // Doit être le lundi de la semaine dernière
      assertThat(debut.getDayOfWeek().getValue()).isEqualTo(1); // Lundi
    }

    @Test
    @DisplayName("getDateDebut - CE_MOIS")
    void testGetDateDebutCeMois() {
      LocalDateTime debut = PeriodeAnalyse.CE_MOIS.getDateDebut();
      assertThat(debut).isNotNull();
      assertThat(debut.getDayOfMonth()).isEqualTo(1);
      assertThat(debut.getMonth()).isEqualTo(LocalDateTime.now().getMonth());
    }

    @Test
    @DisplayName("getDateDebut - MOIS_DERNIER")
    void testGetDateDebutMoisDernier() {
      LocalDateTime debut = PeriodeAnalyse.MOIS_DERNIER.getDateDebut();
      assertThat(debut).isNotNull();
      assertThat(debut.getDayOfMonth()).isEqualTo(1);
      assertThat(debut.getMonth()).isEqualTo(LocalDateTime.now().minusMonths(1).getMonth());
    }

    @Test
    @DisplayName("getDateDebut - CETTE_ANNEE")
    void testGetDateDebutCetteAnnee() {
      LocalDateTime debut = PeriodeAnalyse.CETTE_ANNEE.getDateDebut();
      assertThat(debut).isNotNull();
      assertThat(debut.getDayOfYear()).isEqualTo(1);
      assertThat(debut.getYear()).isEqualTo(LocalDateTime.now().getYear());
    }

    @Test
    @DisplayName("getDateDebut - ANNEE_DERNIERE")
    void testGetDateDebutAnneeDerniere() {
      LocalDateTime debut = PeriodeAnalyse.ANNEE_DERNIERE.getDateDebut();
      assertThat(debut).isNotNull();
      assertThat(debut.getDayOfYear()).isEqualTo(1);
      assertThat(debut.getYear()).isEqualTo(LocalDateTime.now().getYear() - 1);
    }

    @Test
    @DisplayName("getDateDebut - DEPUIS_CREATION")
    void testGetDateDebutDepuisCreation() {
      LocalDateTime debut = PeriodeAnalyse.DEPUIS_CREATION.getDateDebut();
      assertThat(debut).isEqualTo(LocalDateTime.of(2020, 1, 1, 0, 0));
    }

    @Test
    @DisplayName("getDateDebut - PERIODE_PERSONNALISEE")
    void testGetDateDebutPeriodePersonnalisee() {
      LocalDateTime avant = LocalDateTime.now();
      LocalDateTime debut = PeriodeAnalyse.PERIODE_PERSONNALISEE.getDateDebut();
      LocalDateTime apres = LocalDateTime.now();
      assertThat(debut).isBetween(avant.minusSeconds(1), apres.plusSeconds(1));
    }

    @ParameterizedTest
    @CsvSource({
        "TROIS_DERNIERS_MOIS",
        "SIX_DERNIERS_MOIS",
        "SEPT_DERNIERS_JOURS",
        "TRENTE_DERNIERS_JOURS",
        "QUATRE_VINGT_DIX_DERNIERS_JOURS",
        "DEUX_DERNIERES_ANNEES"
    })
    @DisplayName("getDateDebut - périodes avec default")
    void testGetDateDebutDefault(PeriodeAnalyse periode) {
      LocalDateTime debut = periode.getDateDebut();
      assertThat(debut).isNotNull();
      // Vérifie que la date est dans le passé
      assertThat(debut).isBefore(LocalDateTime.now());
    }
  }

  @Nested
  @DisplayName("Tests getDateFin")
  class GetDateFinTests {

    @Test
    @DisplayName("getDateFin - AUJOURD_HUI")
    void testGetDateFinAujourdHui() {
      LocalDateTime fin = PeriodeAnalyse.AUJOURD_HUI.getDateFin();
      assertThat(fin).isNotNull();
      assertThat(fin.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate());
      assertThat(fin.getHour()).isEqualTo(23);
      assertThat(fin.getMinute()).isEqualTo(59);
    }

    @Test
    @DisplayName("getDateFin - HIER")
    void testGetDateFinHier() {
      LocalDateTime fin = PeriodeAnalyse.HIER.getDateFin();
      assertThat(fin).isNotNull();
      assertThat(fin.toLocalDate()).isEqualTo(LocalDateTime.now().minusDays(1).toLocalDate());
      assertThat(fin.getHour()).isEqualTo(23);
    }

    @Test
    @DisplayName("getDateFin - CETTE_SEMAINE")
    void testGetDateFinCetteSemaine() {
      LocalDateTime fin = PeriodeAnalyse.CETTE_SEMAINE.getDateFin();
      assertThat(fin).isNotNull();
      assertThat(fin.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate());
    }

    @Test
    @DisplayName("getDateFin - SEMAINE_DERNIERE")
    void testGetDateFinSemaineDerniere() {
      LocalDateTime fin = PeriodeAnalyse.SEMAINE_DERNIERE.getDateFin();
      assertThat(fin).isNotNull();
      // Doit être le dimanche de la semaine dernière
      assertThat(fin.getDayOfWeek().getValue()).isEqualTo(7); // Dimanche
    }

    @Test
    @DisplayName("getDateFin - CE_MOIS")
    void testGetDateFinCeMois() {
      LocalDateTime fin = PeriodeAnalyse.CE_MOIS.getDateFin();
      assertThat(fin).isNotNull();
      assertThat(fin.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate());
    }

    @Test
    @DisplayName("getDateFin - MOIS_DERNIER")
    void testGetDateFinMoisDernier() {
      LocalDateTime fin = PeriodeAnalyse.MOIS_DERNIER.getDateFin();
      assertThat(fin).isNotNull();
      // Doit être le dernier jour du mois dernier
      assertThat(fin.getMonth()).isEqualTo(LocalDateTime.now().minusMonths(1).getMonth());
    }

    @Test
    @DisplayName("getDateFin - CETTE_ANNEE")
    void testGetDateFinCetteAnnee() {
      LocalDateTime fin = PeriodeAnalyse.CETTE_ANNEE.getDateFin();
      assertThat(fin).isNotNull();
      assertThat(fin.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate());
    }

    @Test
    @DisplayName("getDateFin - ANNEE_DERNIERE")
    void testGetDateFinAnneeDerniere() {
      LocalDateTime fin = PeriodeAnalyse.ANNEE_DERNIERE.getDateFin();
      assertThat(fin).isNotNull();
      // Doit être le dernier jour de l'année dernière
      assertThat(fin.getYear()).isEqualTo(LocalDateTime.now().getYear() - 1);
      assertThat(fin.getMonthValue()).isEqualTo(12);
      assertThat(fin.getDayOfMonth()).isEqualTo(31);
    }

    @Test
    @DisplayName("getDateFin - DEPUIS_CREATION")
    void testGetDateFinDepuisCreation() {
      LocalDateTime avant = LocalDateTime.now();
      LocalDateTime fin = PeriodeAnalyse.DEPUIS_CREATION.getDateFin();
      LocalDateTime apres = LocalDateTime.now();
      assertThat(fin).isBetween(avant.minusSeconds(1), apres.plusSeconds(1));
    }

    @Test
    @DisplayName("getDateFin - PERIODE_PERSONNALISEE")
    void testGetDateFinPeriodePersonnalisee() {
      LocalDateTime avant = LocalDateTime.now();
      LocalDateTime fin = PeriodeAnalyse.PERIODE_PERSONNALISEE.getDateFin();
      LocalDateTime apres = LocalDateTime.now();
      assertThat(fin).isBetween(avant.minusSeconds(1), apres.plusSeconds(1));
    }

    @ParameterizedTest
    @CsvSource({
        "TROIS_DERNIERS_MOIS",
        "SIX_DERNIERS_MOIS",
        "SEPT_DERNIERS_JOURS",
        "TRENTE_DERNIERS_JOURS",
        "QUATRE_VINGT_DIX_DERNIERS_JOURS",
        "DEUX_DERNIERES_ANNEES"
    })
    @DisplayName("getDateFin - périodes avec default")
    void testGetDateFinDefault(PeriodeAnalyse periode) {
      LocalDateTime fin = periode.getDateFin();
      assertThat(fin).isNotNull();
      assertThat(fin.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate());
      assertThat(fin.getHour()).isEqualTo(23);
      assertThat(fin.getMinute()).isEqualTo(59);
    }
  }

  @Nested
  @DisplayName("Tests isPeriodeCourte")
  class IsPeriodeCourteTests {

    @ParameterizedTest
    @CsvSource({
        "AUJOURD_HUI, true",
        "HIER, true",
        "CETTE_SEMAINE, true",
        "SEMAINE_DERNIERE, true",
        "SEPT_DERNIERS_JOURS, true",
        "CE_MOIS, false",
        "MOIS_DERNIER, false",
        "CETTE_ANNEE, false",
        "DEPUIS_CREATION, false"
    })
    @DisplayName("isPeriodeCourte - toutes les périodes")
    void testIsPeriodeCourte(PeriodeAnalyse periode, boolean expected) {
      assertThat(periode.isPeriodeCourte()).isEqualTo(expected);
    }
  }

  @Nested
  @DisplayName("Tests isPeriodeLongue")
  class IsPeriodeLongueTests {

    @ParameterizedTest
    @CsvSource({
        "CETTE_ANNEE, true",
        "ANNEE_DERNIERE, true",
        "DEUX_DERNIERES_ANNEES, true",
        "DEPUIS_CREATION, true",
        "AUJOURD_HUI, false",
        "CE_MOIS, false",
        "CETTE_SEMAINE, false"
    })
    @DisplayName("isPeriodeLongue - toutes les périodes")
    void testIsPeriodeLongue(PeriodeAnalyse periode, boolean expected) {
      assertThat(periode.isPeriodeLongue()).isEqualTo(expected);
    }
  }

  @Nested
  @DisplayName("Tests isPersonnalisable")
  class IsPersonnalisableTests {

    @Test
    @DisplayName("isPersonnalisable - PERIODE_PERSONNALISEE")
    void testIsPersonnalisableTrue() {
      assertThat(PeriodeAnalyse.PERIODE_PERSONNALISEE.isPersonnalisable()).isTrue();
    }

    @ParameterizedTest
    @CsvSource({
        "AUJOURD_HUI",
        "CE_MOIS",
        "CETTE_ANNEE",
        "DEPUIS_CREATION"
    })
    @DisplayName("isPersonnalisable - autres périodes")
    void testIsPersonnalisableFalse(PeriodeAnalyse periode) {
      assertThat(periode.isPersonnalisable()).isFalse();
    }
  }

  @Nested
  @DisplayName("Tests getIntervalleRegroupement")
  class GetIntervalleRegroupementTests {

    @ParameterizedTest
    @CsvSource({
        "AUJOURD_HUI, heure",
        "HIER, heure",
        "CETTE_SEMAINE, jour",
        "SEMAINE_DERNIERE, jour",
        "SEPT_DERNIERS_JOURS, jour",
        "CE_MOIS, jour",
        "MOIS_DERNIER, jour",
        "TRENTE_DERNIERS_JOURS, jour",
        "TROIS_DERNIERS_MOIS, semaine",
        "SIX_DERNIERS_MOIS, semaine",
        "QUATRE_VINGT_DIX_DERNIERS_JOURS, semaine",
        "CETTE_ANNEE, mois",
        "ANNEE_DERNIERE, mois",
        "DEUX_DERNIERES_ANNEES, mois",
        "DEPUIS_CREATION, annee",
        "PERIODE_PERSONNALISEE, jour"
    })
    @DisplayName("getIntervalleRegroupement - toutes les périodes")
    void testGetIntervalleRegroupement(PeriodeAnalyse periode, String expected) {
      assertThat(periode.getIntervalleRegroupement()).isEqualTo(expected);
    }
  }

  @Nested
  @DisplayName("Tests getFormatDate")
  class GetFormatDateTests {

    @ParameterizedTest
    @CsvSource({
        "AUJOURD_HUI, HH:mm",
        "HIER, HH:mm",
        "CETTE_SEMAINE, dd/MM",
        "SEMAINE_DERNIERE, dd/MM",
        "SEPT_DERNIERS_JOURS, dd/MM",
        "CE_MOIS, dd/MM",
        "MOIS_DERNIER, dd/MM",
        "TRENTE_DERNIERS_JOURS, dd/MM",
        "TROIS_DERNIERS_MOIS, dd/MM",
        "SIX_DERNIERS_MOIS, dd/MM",
        "QUATRE_VINGT_DIX_DERNIERS_JOURS, dd/MM",
        "CETTE_ANNEE, MM/yyyy",
        "ANNEE_DERNIERE, MM/yyyy",
        "DEUX_DERNIERES_ANNEES, yyyy",
        "DEPUIS_CREATION, yyyy",
        "PERIODE_PERSONNALISEE, dd/MM/yyyy"
    })
    @DisplayName("getFormatDate - toutes les périodes")
    void testGetFormatDate(PeriodeAnalyse periode, String expected) {
      assertThat(periode.getFormatDate()).isEqualTo(expected);
    }
  }

  @Nested
  @DisplayName("Tests valeurs spécifiques")
  class ValeursSpecifiquesTests {

    @Test
    @DisplayName("Test duree et unite pour toutes les périodes")
    void testDureeEtUnite() {
      assertThat(PeriodeAnalyse.AUJOURD_HUI.getDuree()).isEqualTo(1);
      assertThat(PeriodeAnalyse.AUJOURD_HUI.getUnite()).isEqualTo(ChronoUnit.DAYS);

      assertThat(PeriodeAnalyse.CE_MOIS.getDuree()).isEqualTo(1);
      assertThat(PeriodeAnalyse.CE_MOIS.getUnite()).isEqualTo(ChronoUnit.MONTHS);

      assertThat(PeriodeAnalyse.CETTE_ANNEE.getDuree()).isEqualTo(1);
      assertThat(PeriodeAnalyse.CETTE_ANNEE.getUnite()).isEqualTo(ChronoUnit.YEARS);

      assertThat(PeriodeAnalyse.DEPUIS_CREATION.getDuree()).isEqualTo(0);
      assertThat(PeriodeAnalyse.DEPUIS_CREATION.getUnite()).isEqualTo(ChronoUnit.FOREVER);

      assertThat(PeriodeAnalyse.PERIODE_PERSONNALISEE.getDuree()).isEqualTo(0);
      assertThat(PeriodeAnalyse.PERIODE_PERSONNALISEE.getUnite()).isEqualTo(ChronoUnit.DAYS);
    }

    @Test
    @DisplayName("Test code pour toutes les périodes")
    void testCode() {
      assertThat(PeriodeAnalyse.AUJOURD_HUI.getCode()).isEqualTo("today");
      assertThat(PeriodeAnalyse.HIER.getCode()).isEqualTo("yesterday");
      assertThat(PeriodeAnalyse.CE_MOIS.getCode()).isEqualTo("this_month");
      assertThat(PeriodeAnalyse.DEPUIS_CREATION.getCode()).isEqualTo("since_creation");
      assertThat(PeriodeAnalyse.PERIODE_PERSONNALISEE.getCode()).isEqualTo("custom");
    }

    @Test
    @DisplayName("Test toutes les périodes - couverture complète")
    void testToutesLesPeriodes() {
      // Test toutes les périodes pour s'assurer qu'elles sont toutes couvertes
      for (PeriodeAnalyse periode : PeriodeAnalyse.values()) {
        assertThat(periode.getDateDebut()).isNotNull();
        assertThat(periode.getDateFin()).isNotNull();
        assertThat(periode.getIntervalleRegroupement()).isNotNull().isNotEmpty();
        assertThat(periode.getFormatDate()).isNotNull().isNotEmpty();
      }
    }

    @Test
    @DisplayName("Test getDateDebut - cas default avec différentes unités")
    void testGetDateDebutDefault() {
      // Test que le default fonctionne pour toutes les périodes qui l'utilisent
      LocalDateTime debutTroisMois = PeriodeAnalyse.TROIS_DERNIERS_MOIS.getDateDebut();
      LocalDateTime debutSixMois = PeriodeAnalyse.SIX_DERNIERS_MOIS.getDateDebut();
      LocalDateTime debutSeptJours = PeriodeAnalyse.SEPT_DERNIERS_JOURS.getDateDebut();
      LocalDateTime debutTrenteJours = PeriodeAnalyse.TRENTE_DERNIERS_JOURS.getDateDebut();
      LocalDateTime debutQuatreVingtDixJours = PeriodeAnalyse.QUATRE_VINGT_DIX_DERNIERS_JOURS.getDateDebut();
      LocalDateTime debutDeuxAnnees = PeriodeAnalyse.DEUX_DERNIERES_ANNEES.getDateDebut();

      assertThat(debutTroisMois).isBefore(LocalDateTime.now());
      assertThat(debutSixMois).isBefore(LocalDateTime.now());
      assertThat(debutSeptJours).isBefore(LocalDateTime.now());
      assertThat(debutTrenteJours).isBefore(LocalDateTime.now());
      assertThat(debutQuatreVingtDixJours).isBefore(LocalDateTime.now());
      assertThat(debutDeuxAnnees).isBefore(LocalDateTime.now());

      // Vérifie que les dates sont cohérentes avec les durées
      // Plus la durée est grande, plus la date de début est dans le passé
      assertThat(debutSixMois).isBefore(debutTroisMois);
      assertThat(debutTrenteJours).isBefore(debutSeptJours);
    }

    @Test
    @DisplayName("Test getDateFin - cas default")
    void testGetDateFinDefault() {
      // Test que le default fonctionne pour toutes les périodes qui l'utilisent
      LocalDateTime finTroisMois = PeriodeAnalyse.TROIS_DERNIERS_MOIS.getDateFin();
      LocalDateTime finSixMois = PeriodeAnalyse.SIX_DERNIERS_MOIS.getDateFin();
      LocalDateTime finSeptJours = PeriodeAnalyse.SEPT_DERNIERS_JOURS.getDateFin();
      LocalDateTime finTrenteJours = PeriodeAnalyse.TRENTE_DERNIERS_JOURS.getDateFin();
      LocalDateTime finQuatreVingtDixJours = PeriodeAnalyse.QUATRE_VINGT_DIX_DERNIERS_JOURS.getDateFin();
      LocalDateTime finDeuxAnnees = PeriodeAnalyse.DEUX_DERNIERES_ANNEES.getDateFin();

      assertThat(finTroisMois.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate());
      assertThat(finTroisMois.getHour()).isEqualTo(23);
      assertThat(finTroisMois.getMinute()).isEqualTo(59);
      assertThat(finSixMois.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate());
      assertThat(finSeptJours.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate());
      assertThat(finTrenteJours.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate());
      assertThat(finQuatreVingtDixJours.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate());
      assertThat(finDeuxAnnees.toLocalDate()).isEqualTo(LocalDateTime.now().toLocalDate());
    }
  }
}
