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

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

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse;
import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique;

/**
 * Tests unitaires pour AnalyticsDataResponse.
 * Couvre getLibelleAffichage, hasEvolutionPositive/Negative, isStable, getTendance,
 * isDonneesFiables, isCritique, constructeur 3 args.
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2026-02-21
 */
@DisplayName("Tests AnalyticsDataResponse")
class AnalyticsDataResponseTest {

  private static final TypeMetrique TYPE = TypeMetrique.NOMBRE_MEMBRES_ACTIFS;
  private static final PeriodeAnalyse PERIODE = PeriodeAnalyse.CETTE_SEMAINE;

  @Nested
  @DisplayName("getLibelleAffichage")
  class GetLibelleAffichage {

    @Test
    @DisplayName("retourne typeMetrique.getLibelle() quand libellePersonnalise null")
    void testLibelleNull() {
      AnalyticsDataResponse r = AnalyticsDataResponse.builder()
          .typeMetrique(TYPE)
          .periodeAnalyse(PERIODE)
          .valeur(BigDecimal.ONE)
          .dateDebut(LocalDateTime.now())
          .dateFin(LocalDateTime.now())
          .dateCalcul(LocalDateTime.now()).build();
      assertThat(r.getLibelleAffichage()).isEqualTo(TYPE.getLibelle());
    }

    @Test
    @DisplayName("retourne typeMetrique.getLibelle() quand libellePersonnalise vide ou blanc")
    void testLibelleVide() {
      AnalyticsDataResponse r = AnalyticsDataResponse.builder()
          .typeMetrique(TYPE)
          .periodeAnalyse(PERIODE)
          .valeur(BigDecimal.ONE)
          .dateDebut(LocalDateTime.now())
          .dateFin(LocalDateTime.now())
          .dateCalcul(LocalDateTime.now())
          .libellePersonnalise("").build();
      assertThat(r.getLibelleAffichage()).isEqualTo(TYPE.getLibelle());
      r.setLibellePersonnalise("   ");
      assertThat(r.getLibelleAffichage()).isEqualTo(TYPE.getLibelle());
    }

    @Test
    @DisplayName("retourne libellePersonnalise quand non vide")
    void testLibellePersonnalise() {
      AnalyticsDataResponse r = AnalyticsDataResponse.builder()
          .typeMetrique(TYPE)
          .periodeAnalyse(PERIODE)
          .valeur(BigDecimal.ONE)
          .dateDebut(LocalDateTime.now())
          .dateFin(LocalDateTime.now())
          .dateCalcul(LocalDateTime.now())
          .libellePersonnalise("Mon libellé").build();
      assertThat(r.getLibelleAffichage()).isEqualTo("Mon libellé");
    }
  }

  @Nested
  @DisplayName("getUnite, getIcone, getCouleur")
  class DelegationTypeMetrique {

    @Test
    @DisplayName("délèguent à typeMetrique")
    void testDelegation() {
      AnalyticsDataResponse r = AnalyticsDataResponse.builder()
          .typeMetrique(TYPE)
          .periodeAnalyse(PERIODE)
          .valeur(BigDecimal.ONE)
          .dateDebut(LocalDateTime.now())
          .dateFin(LocalDateTime.now())
          .dateCalcul(LocalDateTime.now()).build();
      assertThat(r.getUnite()).isEqualTo(TYPE.getUnite());
      assertThat(r.getIcone()).isEqualTo(TYPE.getIcone());
      assertThat(r.getCouleur()).isEqualTo(TYPE.getCouleur());
    }
  }

  @Nested
  @DisplayName("hasEvolutionPositive, hasEvolutionNegative, isStable")
  class Evolution {

    @Test
    @DisplayName("hasEvolutionPositive true quand pourcentageEvolution > 0")
    void testEvolutionPositive() {
      AnalyticsDataResponse r = AnalyticsDataResponse.builder()
          .typeMetrique(TYPE)
          .periodeAnalyse(PERIODE)
          .valeur(BigDecimal.ONE)
          .dateDebut(LocalDateTime.now())
          .dateFin(LocalDateTime.now())
          .dateCalcul(LocalDateTime.now())
          .pourcentageEvolution(BigDecimal.valueOf(5.5)).build();
      assertThat(r.hasEvolutionPositive()).isTrue();
      assertThat(r.hasEvolutionNegative()).isFalse();
      assertThat(r.isStable()).isFalse();
    }

    @Test
    @DisplayName("hasEvolutionNegative true quand pourcentageEvolution < 0")
    void testEvolutionNegative() {
      AnalyticsDataResponse r = AnalyticsDataResponse.builder()
          .typeMetrique(TYPE)
          .periodeAnalyse(PERIODE)
          .valeur(BigDecimal.ONE)
          .dateDebut(LocalDateTime.now())
          .dateFin(LocalDateTime.now())
          .dateCalcul(LocalDateTime.now())
          .pourcentageEvolution(BigDecimal.valueOf(-3.2)).build();
      assertThat(r.hasEvolutionNegative()).isTrue();
      assertThat(r.hasEvolutionPositive()).isFalse();
      assertThat(r.isStable()).isFalse();
    }

    @Test
    @DisplayName("isStable true quand pourcentageEvolution == 0")
    void testStable() {
      AnalyticsDataResponse r = AnalyticsDataResponse.builder()
          .typeMetrique(TYPE)
          .periodeAnalyse(PERIODE)
          .valeur(BigDecimal.ONE)
          .dateDebut(LocalDateTime.now())
          .dateFin(LocalDateTime.now())
          .dateCalcul(LocalDateTime.now())
          .pourcentageEvolution(BigDecimal.ZERO).build();
      assertThat(r.isStable()).isTrue();
      assertThat(r.hasEvolutionPositive()).isFalse();
      assertThat(r.hasEvolutionNegative()).isFalse();
    }

    @Test
    @DisplayName("retournent false / false / false quand pourcentageEvolution null")
    void testEvolutionNull() {
      AnalyticsDataResponse r = AnalyticsDataResponse.builder()
          .typeMetrique(TYPE)
          .periodeAnalyse(PERIODE)
          .valeur(BigDecimal.ONE)
          .dateDebut(LocalDateTime.now())
          .dateFin(LocalDateTime.now())
          .dateCalcul(LocalDateTime.now()).build();
      assertThat(r.hasEvolutionPositive()).isFalse();
      assertThat(r.hasEvolutionNegative()).isFalse();
      assertThat(r.isStable()).isFalse();
    }
  }

  @Nested
  @DisplayName("getTendance")
  class GetTendance {

    @Test
    @DisplayName("retourne hausse quand évolution positive")
    void testHausse() {
      AnalyticsDataResponse r = AnalyticsDataResponse.builder()
          .typeMetrique(TYPE)
          .periodeAnalyse(PERIODE)
          .valeur(BigDecimal.ONE)
          .dateDebut(LocalDateTime.now())
          .dateFin(LocalDateTime.now())
          .dateCalcul(LocalDateTime.now())
          .pourcentageEvolution(BigDecimal.ONE).build();
      assertThat(r.getTendance()).isEqualTo("hausse");
    }

    @Test
    @DisplayName("retourne baisse quand évolution négative")
    void testBaisse() {
      AnalyticsDataResponse r = AnalyticsDataResponse.builder()
          .typeMetrique(TYPE)
          .periodeAnalyse(PERIODE)
          .valeur(BigDecimal.ONE)
          .dateDebut(LocalDateTime.now())
          .dateFin(LocalDateTime.now())
          .dateCalcul(LocalDateTime.now())
          .pourcentageEvolution(BigDecimal.ONE.negate()).build();
      assertThat(r.getTendance()).isEqualTo("baisse");
    }

    @Test
    @DisplayName("retourne stable sinon")
    void testStable() {
      AnalyticsDataResponse r = AnalyticsDataResponse.builder()
          .typeMetrique(TYPE)
          .periodeAnalyse(PERIODE)
          .valeur(BigDecimal.ONE)
          .dateDebut(LocalDateTime.now())
          .dateFin(LocalDateTime.now())
          .dateCalcul(LocalDateTime.now())
          .pourcentageEvolution(BigDecimal.ZERO).build();
      assertThat(r.getTendance()).isEqualTo("stable");
      r.setPourcentageEvolution(null);
      assertThat(r.getTendance()).isEqualTo("stable");
    }
  }

  @Nested
  @DisplayName("isDonneesFiables")
  class IsDonneesFiables {

    @Test
    @DisplayName("retourne false quand indicateurFiabilite null")
    void testNull() {
      AnalyticsDataResponse r = AnalyticsDataResponse.builder()
          .typeMetrique(TYPE)
          .periodeAnalyse(PERIODE)
          .valeur(BigDecimal.ONE)
          .dateDebut(LocalDateTime.now())
          .dateFin(LocalDateTime.now())
          .dateCalcul(LocalDateTime.now()).build();
      assertThat(r.isDonneesFiables()).isFalse();
    }

    @Test
    @DisplayName("retourne true quand indicateurFiabilite >= 80")
    void testFiable() {
      AnalyticsDataResponse r = AnalyticsDataResponse.builder()
          .typeMetrique(TYPE)
          .periodeAnalyse(PERIODE)
          .valeur(BigDecimal.ONE)
          .dateDebut(LocalDateTime.now())
          .dateFin(LocalDateTime.now())
          .dateCalcul(LocalDateTime.now())
          .indicateurFiabilite(new BigDecimal("80.0")).build();
      assertThat(r.isDonneesFiables()).isTrue();
      r.setIndicateurFiabilite(new BigDecimal("95.5"));
      assertThat(r.isDonneesFiables()).isTrue();
    }

    @Test
    @DisplayName("retourne false quand indicateurFiabilite < 80")
    void testNonFiable() {
      AnalyticsDataResponse r = AnalyticsDataResponse.builder()
          .typeMetrique(TYPE)
          .periodeAnalyse(PERIODE)
          .valeur(BigDecimal.ONE)
          .dateDebut(LocalDateTime.now())
          .dateFin(LocalDateTime.now())
          .dateCalcul(LocalDateTime.now())
          .indicateurFiabilite(new BigDecimal("79.9")).build();
      assertThat(r.isDonneesFiables()).isFalse();
    }
  }

  @Nested
  @DisplayName("isCritique")
  class IsCritique {

    @Test
    @DisplayName("retourne false quand niveauPriorite null ou < 4")
    void testNonCritique() {
      AnalyticsDataResponse r = AnalyticsDataResponse.builder()
          .typeMetrique(TYPE)
          .periodeAnalyse(PERIODE)
          .valeur(BigDecimal.ONE)
          .dateDebut(LocalDateTime.now())
          .dateFin(LocalDateTime.now())
          .dateCalcul(LocalDateTime.now()).build();
      assertThat(r.isCritique()).isFalse();
      r.setNiveauPriorite(3);
      assertThat(r.isCritique()).isFalse();
    }

    @Test
    @DisplayName("retourne true quand niveauPriorite >= 4")
    void testCritique() {
      AnalyticsDataResponse r = AnalyticsDataResponse.builder()
          .typeMetrique(TYPE)
          .periodeAnalyse(PERIODE)
          .valeur(BigDecimal.ONE)
          .dateDebut(LocalDateTime.now())
          .dateFin(LocalDateTime.now())
          .dateCalcul(LocalDateTime.now())
          .niveauPriorite(4).build();
      assertThat(r.isCritique()).isTrue();
      r.setNiveauPriorite(5);
      assertThat(r.isCritique()).isTrue();
    }
  }

  @Nested
  @DisplayName("Constructeur 3 arguments")
  class Constructeur3Args {

    @Test
    @DisplayName("initialise typeMetrique, periodeAnalyse, valeur, dateCalcul, dateDebut, dateFin, défauts")
    void testConstructeur() {
      AnalyticsDataResponse r = new AnalyticsDataResponse(TYPE, PERIODE, BigDecimal.valueOf(42));
      assertThat(r.getTypeMetrique()).isEqualTo(TYPE);
      assertThat(r.getPeriodeAnalyse()).isEqualTo(PERIODE);
      assertThat(r.getValeur()).isEqualByComparingTo(BigDecimal.valueOf(42));
      assertThat(r.getDateCalcul()).isNotNull();
      assertThat(r.getDateDebut()).isEqualTo(PERIODE.getDateDebut());
      assertThat(r.getDateFin()).isEqualTo(PERIODE.getDateFin());
      assertThat(r.getTempsReel()).isFalse();
      assertThat(r.getNecessiteMiseAJour()).isFalse();
      assertThat(r.getNiveauPriorite()).isEqualTo(3);
      assertThat(r.getIndicateurFiabilite()).isEqualByComparingTo(new BigDecimal("95.0"));
    }
  }

  @Nested
  @DisplayName("Builder et getters")
  class BuilderGetters {

    @Test
    @DisplayName("champs optionnels accessibles")
    void testBuilder() {
      UUID orgId = UUID.randomUUID();
      AnalyticsDataResponse r = AnalyticsDataResponse.builder()
          .typeMetrique(TYPE)
          .periodeAnalyse(PERIODE)
          .valeur(BigDecimal.ONE)
          .dateDebut(LocalDateTime.now())
          .dateFin(LocalDateTime.now())
          .dateCalcul(LocalDateTime.now())
          .organisationId(orgId)
          .nomOrganisation("Org")
          .tempsReel(true)
          .necessiteMiseAJour(true).build();
      assertThat(r.getOrganisationId()).isEqualTo(orgId);
      assertThat(r.getNomOrganisation()).isEqualTo("Org");
      assertThat(r.getTempsReel()).isTrue();
      assertThat(r.getNecessiteMiseAJour()).isTrue();
    }
  }
}
