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

import static dev.lions.unionflow.server.api.TestDataFactory.createAnalyticsDataDTO;
import static dev.lions.unionflow.server.api.TestDataFactory.now;
import static org.assertj.core.api.Assertions.assertThat;

import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse;
import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
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;

@DisplayName("Tests pour AnalyticsDataDTO")
class AnalyticsDataDTOTest {

  @Test
  @DisplayName("Test de base - classe peut être instanciée")
  void testClasseInstanciable() {
    AnalyticsDataDTO dto = new AnalyticsDataDTO();
    assertThat(dto).isNotNull();
  }

  @Nested
  @DisplayName("Tests de construction")
  class ConstructionTests {

    @Test
    @DisplayName("Constructeur par défaut")
    void testConstructeurParDefaut() {
      AnalyticsDataDTO dto = new AnalyticsDataDTO();

      assertThat(dto).isNotNull();
    }

    @Test
    @DisplayName("Builder pattern")
    void testBuilder() {
      LocalDateTime dateCalcul = now();
      UUID orgId = UUID.randomUUID();

      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
          .periodeAnalyse(PeriodeAnalyse.CE_MOIS)
          .valeur(new BigDecimal("100.50"))
          .valeurPrecedente(new BigDecimal("90.25"))
          .pourcentageEvolution(new BigDecimal("11.36"))
          .dateDebut(now().minusDays(30))
          .dateFin(now())
          .dateCalcul(dateCalcul)
          .organisationId(orgId)
          .nomOrganisation("Test Org")
          .build();

      assertThat(dto.getTypeMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
      assertThat(dto.getPeriodeAnalyse()).isEqualTo(PeriodeAnalyse.CE_MOIS);
      assertThat(dto.getValeur()).isEqualTo(new BigDecimal("100.50"));
      assertThat(dto.getDateCalcul()).isEqualTo(dateCalcul);
      assertThat(dto.getOrganisationId()).isEqualTo(orgId);
    }

    @Test
    @DisplayName("Constructeur avec paramètres essentiels")
    void testConstructeurAvecParametres() {
      AnalyticsDataDTO dto = new AnalyticsDataDTO(
          TypeMetrique.NOMBRE_MEMBRES_ACTIFS,
          PeriodeAnalyse.CE_MOIS,
          new BigDecimal("100.0"));

      assertThat(dto.getTypeMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
      assertThat(dto.getPeriodeAnalyse()).isEqualTo(PeriodeAnalyse.CE_MOIS);
      assertThat(dto.getValeur()).isEqualTo(new BigDecimal("100.0"));
      assertThat(dto.getDateCalcul()).isNotNull();
      assertThat(dto.getTempsReel()).isFalse();
      assertThat(dto.getNecessiteMiseAJour()).isFalse();
      assertThat(dto.getNiveauPriorite()).isEqualTo(3);
      assertThat(dto.getIndicateurFiabilite()).isEqualTo(new BigDecimal("95.0"));
    }
  }

  @Nested
  @DisplayName("Tests getLibelleAffichage")
  class GetLibelleAffichageTests {

    @Test
    @DisplayName("getLibelleAffichage - avec libellé personnalisé")
    void testGetLibelleAffichagePersonnalise() {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
          .libellePersonnalise("Membres Actifs")
          .build();

      assertThat(dto.getLibelleAffichage()).isEqualTo("Membres Actifs");
    }

    @Test
    @DisplayName("getLibelleAffichage - sans libellé personnalisé")
    void testGetLibelleAffichageParDefaut() {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
          .build();

      assertThat(dto.getLibelleAffichage()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getLibelle());
    }

    @Test
    @DisplayName("getLibelleAffichage - libellé personnalisé vide")
    void testGetLibelleAffichageVide() {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
          .libellePersonnalise("   ")
          .build();

      assertThat(dto.getLibelleAffichage()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getLibelle());
    }

    @Test
    @DisplayName("getLibelleAffichage - typeMetrique null")
    void testGetLibelleAffichageTypeMetriqueNull() {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .typeMetrique(null)
          .libellePersonnalise("Libellé personnalisé")
          .build();

      // Si typeMetrique est null, getLibelle() lancerait NPE, mais testons le comportement
      // En fait, si libellePersonnalise n'est pas null/vide, il devrait être retourné
      assertThat(dto.getLibelleAffichage()).isEqualTo("Libellé personnalisé");
    }

    @Test
    @DisplayName("getLibelleAffichage - typeMetrique null et libellé personnalisé null")
    void testGetLibelleAffichageTypeMetriqueNullEtLibelleNull() {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .typeMetrique(null)
          .libellePersonnalise(null)
          .build();

      // Si typeMetrique est null et libellePersonnalise est null, cela devrait lancer NPE
      org.assertj.core.api.Assertions.assertThatThrownBy(() -> dto.getLibelleAffichage())
          .isInstanceOf(NullPointerException.class);
    }

    @Test
    @DisplayName("getLibelleAffichage - typeMetrique null et libellePersonnalise vide")
    void testGetLibelleAffichageTypeMetriqueNullEtLibellePersonnaliseVide() {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .typeMetrique(null)
          .libellePersonnalise("   ")
          .build();
      // Si libellePersonnalise est vide (trim), on essaie d'appeler typeMetrique.getLibelle()
      // ce qui devrait lancer NPE
      org.assertj.core.api.Assertions.assertThatThrownBy(() -> dto.getLibelleAffichage())
          .isInstanceOf(NullPointerException.class);
    }

    @Test
    @DisplayName("getLibelleAffichage - libellePersonnalise null et typeMetrique non null")
    void testGetLibelleAffichageLibelleNullEtTypeMetriqueNonNull() {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
          .libellePersonnalise(null)
          .build();

      // Si libellePersonnalise est null, on retourne typeMetrique.getLibelle()
      assertThat(dto.getLibelleAffichage()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getLibelle());
    }

    @Test
    @DisplayName("getLibelleAffichage - libellePersonnalise vide et typeMetrique non null")
    void testGetLibelleAffichageLibelleVideEtTypeMetriqueNonNull() {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
          .libellePersonnalise("")
          .build();

      // Si libellePersonnalise est vide, on retourne typeMetrique.getLibelle()
      assertThat(dto.getLibelleAffichage()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getLibelle());
    }
  }

  @Nested
  @DisplayName("Tests méthodes utilitaires TypeMetrique")
  class MethodesTypeMetriqueTests {

    @Test
    @DisplayName("getUnite - retourne l'unité du type métrique")
    void testGetUnite() {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
          .build();

      assertThat(dto.getUnite()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getUnite());
    }

    @Test
    @DisplayName("getUnite - typeMetrique null")
    void testGetUniteTypeMetriqueNull() {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .typeMetrique(null)
          .build();

      org.assertj.core.api.Assertions.assertThatThrownBy(() -> dto.getUnite())
          .isInstanceOf(NullPointerException.class);
    }

    @Test
    @DisplayName("getIcone - retourne l'icône du type métrique")
    void testGetIcone() {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
          .build();

      assertThat(dto.getIcone()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getIcone());
    }

    @Test
    @DisplayName("getIcone - typeMetrique null")
    void testGetIconeTypeMetriqueNull() {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .typeMetrique(null)
          .build();

      org.assertj.core.api.Assertions.assertThatThrownBy(() -> dto.getIcone())
          .isInstanceOf(NullPointerException.class);
    }

    @Test
    @DisplayName("getCouleur - retourne la couleur du type métrique")
    void testGetCouleur() {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
          .build();

      assertThat(dto.getCouleur()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getCouleur());
    }

    @Test
    @DisplayName("getCouleur - typeMetrique null")
    void testGetCouleurTypeMetriqueNull() {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .typeMetrique(null)
          .build();

      org.assertj.core.api.Assertions.assertThatThrownBy(() -> dto.getCouleur())
          .isInstanceOf(NullPointerException.class);
    }
  }

  @Nested
  @DisplayName("Tests hasEvolutionPositive")
  class HasEvolutionPositiveTests {

    @Test
    @DisplayName("hasEvolutionPositive - évolution positive")
    void testHasEvolutionPositive() {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .pourcentageEvolution(new BigDecimal("10.5"))
          .build();

      assertThat(dto.hasEvolutionPositive()).isTrue();
    }

    @Test
    @DisplayName("hasEvolutionPositive - évolution négative")
    void testHasEvolutionPositiveNegative() {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .pourcentageEvolution(new BigDecimal("-5.0"))
          .build();

      assertThat(dto.hasEvolutionPositive()).isFalse();
    }

    @Test
    @DisplayName("hasEvolutionPositive - null")
    void testHasEvolutionPositiveNull() {
      AnalyticsDataDTO dto = new AnalyticsDataDTO();

      assertThat(dto.hasEvolutionPositive()).isFalse();
    }
  }

  @Nested
  @DisplayName("Tests hasEvolutionNegative")
  class HasEvolutionNegativeTests {

    @Test
    @DisplayName("hasEvolutionNegative - évolution négative")
    void testHasEvolutionNegative() {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .pourcentageEvolution(new BigDecimal("-10.5"))
          .build();

      assertThat(dto.hasEvolutionNegative()).isTrue();
    }

    @Test
    @DisplayName("hasEvolutionNegative - évolution positive")
    void testHasEvolutionNegativePositive() {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .pourcentageEvolution(new BigDecimal("5.0"))
          .build();

      assertThat(dto.hasEvolutionNegative()).isFalse();
    }
  }

  @Nested
  @DisplayName("Tests isStable")
  class IsStableTests {

    @Test
    @DisplayName("isStable - évolution nulle")
    void testIsStable() {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .pourcentageEvolution(BigDecimal.ZERO)
          .build();

      assertThat(dto.isStable()).isTrue();
    }

    @Test
    @DisplayName("isStable - évolution non nulle")
    void testIsStableNonNulle() {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .pourcentageEvolution(new BigDecimal("5.0"))
          .build();

      assertThat(dto.isStable()).isFalse();
    }
  }

  @Nested
  @DisplayName("Tests getTendance")
  class GetTendanceTests {

    @ParameterizedTest
    @CsvSource({
        "10.5, hausse",
        "-5.0, baisse",
        "0.0, stable"
    })
    @DisplayName("getTendance - toutes les tendances")
    void testGetTendance(String evolution, String expected) {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .pourcentageEvolution(new BigDecimal(evolution))
          .build();

      assertThat(dto.getTendance()).isEqualTo(expected);
    }
  }

  @Nested
  @DisplayName("Tests isDonneesFiables")
  class IsDonneesFiablesTests {

    @ParameterizedTest
    @CsvSource({
        "80.0, true",
        "95.0, true",
        "79.9, false",
        "50.0, false"
    })
    @DisplayName("isDonneesFiables - seuil 80%")
    void testIsDonneesFiables(String fiabilite, Boolean expected) {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .indicateurFiabilite(new BigDecimal(fiabilite))
          .build();

      assertThat(dto.isDonneesFiables()).isEqualTo(expected);
    }

    @Test
    @DisplayName("isDonneesFiables - null")
    void testIsDonneesFiablesNull() {
      AnalyticsDataDTO dto = new AnalyticsDataDTO();

      assertThat(dto.isDonneesFiables()).isFalse();
    }
  }

  @Nested
  @DisplayName("Tests isCritique")
  class IsCritiqueTests {

    @ParameterizedTest
    @CsvSource({
        "4, true",
        "5, true",
        "3, false",
        "1, false"
    })
    @DisplayName("isCritique - priorité >= 4")
    void testIsCritique(Integer priorite, Boolean expected) {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .niveauPriorite(priorite)
          .build();

      assertThat(dto.isCritique()).isEqualTo(expected);
    }

    @Test
    @DisplayName("isCritique - null")
    void testIsCritiqueNull() {
      AnalyticsDataDTO dto = new AnalyticsDataDTO();

      assertThat(dto.isCritique()).isFalse();
    }
  }

  @Nested
  @DisplayName("Tests Builder complet - tous les champs")
  class BuilderCompletTests {

    @Test
    @DisplayName("Builder - tous les champs")
    void testBuilderTousChamps() {
      UUID orgId = UUID.randomUUID();
      UUID userId = UUID.randomUUID();
      LocalDateTime now = LocalDateTime.now();
      Map<String, Object> metadonnees = new HashMap<>();
      metadonnees.put("key", "value");
      List<String> tags = Arrays.asList("tag1", "tag2");

      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
          .periodeAnalyse(PeriodeAnalyse.CE_MOIS)
          .valeur(new BigDecimal("100.50"))
          .valeurPrecedente(new BigDecimal("90.25"))
          .pourcentageEvolution(new BigDecimal("11.36"))
          .dateDebut(now.minusDays(30))
          .dateFin(now)
          .dateCalcul(now)
          .organisationId(orgId)
          .nomOrganisation("Test Org")
          .utilisateurId(userId)
          .nomUtilisateur("Test User")
          .libellePersonnalise("Libellé personnalisé")
          .description("Description test")
          .donneesDetaillees("{\"data\": \"test\"}")
          .configurationGraphique("{\"config\": \"test\"}")
          .metadonnees(metadonnees)
          .indicateurFiabilite(new BigDecimal("95.5"))
          .nombreElementsAnalyses(1000)
          .tempsCalculMs(150L)
          .tempsReel(true)
          .necessiteMiseAJour(true)
          .niveauPriorite(4)
          .tags(tags)
          .build();

      assertThat(dto.getTypeMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
      assertThat(dto.getPeriodeAnalyse()).isEqualTo(PeriodeAnalyse.CE_MOIS);
      assertThat(dto.getValeur()).isEqualTo(new BigDecimal("100.50"));
      assertThat(dto.getValeurPrecedente()).isEqualTo(new BigDecimal("90.25"));
      assertThat(dto.getPourcentageEvolution()).isEqualTo(new BigDecimal("11.36"));
      assertThat(dto.getDateDebut()).isEqualTo(now.minusDays(30));
      assertThat(dto.getDateFin()).isEqualTo(now);
      assertThat(dto.getDateCalcul()).isEqualTo(now);
      assertThat(dto.getOrganisationId()).isEqualTo(orgId);
      assertThat(dto.getNomOrganisation()).isEqualTo("Test Org");
      assertThat(dto.getUtilisateurId()).isEqualTo(userId);
      assertThat(dto.getNomUtilisateur()).isEqualTo("Test User");
      assertThat(dto.getLibellePersonnalise()).isEqualTo("Libellé personnalisé");
      assertThat(dto.getDescription()).isEqualTo("Description test");
      assertThat(dto.getDonneesDetaillees()).isEqualTo("{\"data\": \"test\"}");
      assertThat(dto.getConfigurationGraphique()).isEqualTo("{\"config\": \"test\"}");
      assertThat(dto.getMetadonnees()).isEqualTo(metadonnees);
      assertThat(dto.getIndicateurFiabilite()).isEqualTo(new BigDecimal("95.5"));
      assertThat(dto.getNombreElementsAnalyses()).isEqualTo(1000);
      assertThat(dto.getTempsCalculMs()).isEqualTo(150L);
      assertThat(dto.getTempsReel()).isTrue();
      assertThat(dto.getNecessiteMiseAJour()).isTrue();
      assertThat(dto.getNiveauPriorite()).isEqualTo(4);
      assertThat(dto.getTags()).isEqualTo(tags);
    }

    @Test
    @DisplayName("Builder - valeurs par défaut")
    void testBuilderValeursParDefaut() {
      AnalyticsDataDTO dto = AnalyticsDataDTO.builder()
          .typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
          .periodeAnalyse(PeriodeAnalyse.CE_MOIS)
          .valeur(new BigDecimal("100.0"))
          .dateDebut(LocalDateTime.now().minusDays(30))
          .dateFin(LocalDateTime.now())
          .dateCalcul(LocalDateTime.now())
          .build();

      assertThat(dto.getTempsReel()).isFalse();
      assertThat(dto.getNecessiteMiseAJour()).isFalse();
    }

  }

  @Nested
  @DisplayName("Tests getters/setters complets")
  class GettersSettersCompletsTests {

    @Test
    @DisplayName("Test tous les getters/setters")
    void testTousLesGettersSetters() {
      AnalyticsDataDTO dto = new AnalyticsDataDTO();
      UUID orgId = UUID.randomUUID();
      UUID userId = UUID.randomUUID();
      LocalDateTime now = LocalDateTime.now();
      Map<String, Object> metadonnees = new HashMap<>();
      metadonnees.put("key", "value");
      List<String> tags = Arrays.asList("tag1", "tag2");

      dto.setTypeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
      dto.setPeriodeAnalyse(PeriodeAnalyse.CE_MOIS);
      dto.setValeur(new BigDecimal("100.50"));
      dto.setValeurPrecedente(new BigDecimal("90.25"));
      dto.setPourcentageEvolution(new BigDecimal("11.36"));
      dto.setDateDebut(now.minusDays(30));
      dto.setDateFin(now);
      dto.setDateCalcul(now);
      dto.setOrganisationId(orgId);
      dto.setNomOrganisation("Test Org");
      dto.setUtilisateurId(userId);
      dto.setNomUtilisateur("Test User");
      dto.setLibellePersonnalise("Libellé personnalisé");
      dto.setDescription("Description test");
      dto.setDonneesDetaillees("{\"data\": \"test\"}");
      dto.setConfigurationGraphique("{\"config\": \"test\"}");
      dto.setMetadonnees(metadonnees);
      dto.setIndicateurFiabilite(new BigDecimal("95.5"));
      dto.setNombreElementsAnalyses(1000);
      dto.setTempsCalculMs(150L);
      dto.setTempsReel(true);
      dto.setNecessiteMiseAJour(true);
      dto.setNiveauPriorite(4);
      dto.setTags(tags);

      assertThat(dto.getTypeMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
      assertThat(dto.getPeriodeAnalyse()).isEqualTo(PeriodeAnalyse.CE_MOIS);
      assertThat(dto.getValeur()).isEqualTo(new BigDecimal("100.50"));
      assertThat(dto.getValeurPrecedente()).isEqualTo(new BigDecimal("90.25"));
      assertThat(dto.getPourcentageEvolution()).isEqualTo(new BigDecimal("11.36"));
      assertThat(dto.getDateDebut()).isEqualTo(now.minusDays(30));
      assertThat(dto.getDateFin()).isEqualTo(now);
      assertThat(dto.getDateCalcul()).isEqualTo(now);
      assertThat(dto.getOrganisationId()).isEqualTo(orgId);
      assertThat(dto.getNomOrganisation()).isEqualTo("Test Org");
      assertThat(dto.getUtilisateurId()).isEqualTo(userId);
      assertThat(dto.getNomUtilisateur()).isEqualTo("Test User");
      assertThat(dto.getLibellePersonnalise()).isEqualTo("Libellé personnalisé");
      assertThat(dto.getDescription()).isEqualTo("Description test");
      assertThat(dto.getDonneesDetaillees()).isEqualTo("{\"data\": \"test\"}");
      assertThat(dto.getConfigurationGraphique()).isEqualTo("{\"config\": \"test\"}");
      assertThat(dto.getMetadonnees()).isEqualTo(metadonnees);
      assertThat(dto.getIndicateurFiabilite()).isEqualTo(new BigDecimal("95.5"));
      assertThat(dto.getNombreElementsAnalyses()).isEqualTo(1000);
      assertThat(dto.getTempsCalculMs()).isEqualTo(150L);
      assertThat(dto.getTempsReel()).isTrue();
      assertThat(dto.getNecessiteMiseAJour()).isTrue();
      assertThat(dto.getNiveauPriorite()).isEqualTo(4);
      assertThat(dto.getTags()).isEqualTo(tags);
    }
  }
}

