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.List;
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 KPITrendResponse.
 * Couvre getLibelleMetrique, getUnite, getIcone, getCouleur, isTendancePositive/Negative/Stable,
 * getVolatilite, isPredictionFiable, getNombrePointsDonnees, hasAnomalies.
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2026-02-21
 */
@DisplayName("Tests KPITrendResponse")
class KPITrendResponseTest {

  private static KPITrendResponse minimalKpi() {
    return KPITrendResponse.builder()
        .typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
        .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE)
        .dateDebut(LocalDateTime.now())
        .dateFin(LocalDateTime.now())
        .pointsDonnees(List.of())
        .valeurActuelle(BigDecimal.ONE).build();
  }

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

    @Test
    @DisplayName("délèguent à typeMetrique")
    void testDelegation() {
      KPITrendResponse k = minimalKpi();
      assertThat(k.getLibelleMetrique()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getLibelle());
      assertThat(k.getUnite()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getUnite());
      assertThat(k.getIcone()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getIcone());
      assertThat(k.getCouleur()).isEqualTo(TypeMetrique.NOMBRE_MEMBRES_ACTIFS.getCouleur());
    }
  }

  @Nested
  @DisplayName("isTendancePositive, isTendanceNegative, isTendanceStable")
  class Tendance {

    @Test
    @DisplayName("isTendancePositive true quand tendanceGenerale > 0")
    void testPositive() {
      KPITrendResponse k = minimalKpi();
      k.setTendanceGenerale(BigDecimal.ONE);
      assertThat(k.isTendancePositive()).isTrue();
      assertThat(k.isTendanceNegative()).isFalse();
      assertThat(k.isTendanceStable()).isFalse();
    }

    @Test
    @DisplayName("isTendanceNegative true quand tendanceGenerale < 0")
    void testNegative() {
      KPITrendResponse k = minimalKpi();
      k.setTendanceGenerale(BigDecimal.ONE.negate());
      assertThat(k.isTendanceNegative()).isTrue();
      assertThat(k.isTendancePositive()).isFalse();
      assertThat(k.isTendanceStable()).isFalse();
    }

    @Test
    @DisplayName("isTendanceStable true quand tendanceGenerale == 0")
    void testStable() {
      KPITrendResponse k = minimalKpi();
      k.setTendanceGenerale(BigDecimal.ZERO);
      assertThat(k.isTendanceStable()).isTrue();
      assertThat(k.isTendancePositive()).isFalse();
      assertThat(k.isTendanceNegative()).isFalse();
    }

    @Test
    @DisplayName("toutes false quand tendanceGenerale null")
    void testNull() {
      KPITrendResponse k = minimalKpi();
      assertThat(k.isTendancePositive()).isFalse();
      assertThat(k.isTendanceNegative()).isFalse();
      assertThat(k.isTendanceStable()).isFalse();
    }
  }

  @Nested
  @DisplayName("getVolatilite")
  class GetVolatilite {

    @Test
    @DisplayName("retourne inconnue quand coefficientVariation null")
    void testNull() {
      KPITrendResponse k = minimalKpi();
      assertThat(k.getVolatilite()).isEqualTo("inconnue");
    }

    @Test
    @DisplayName("retourne faible quand cv <= 0.1")
    void testFaible() {
      KPITrendResponse k = minimalKpi();
      k.setCoefficientVariation(new BigDecimal("0.05"));
      assertThat(k.getVolatilite()).isEqualTo("faible");
      k.setCoefficientVariation(new BigDecimal("0.1"));
      assertThat(k.getVolatilite()).isEqualTo("faible");
    }

    @Test
    @DisplayName("retourne moyenne quand cv <= 0.3 et > 0.1")
    void testMoyenne() {
      KPITrendResponse k = minimalKpi();
      k.setCoefficientVariation(new BigDecimal("0.2"));
      assertThat(k.getVolatilite()).isEqualTo("moyenne");
      k.setCoefficientVariation(new BigDecimal("0.3"));
      assertThat(k.getVolatilite()).isEqualTo("moyenne");
    }

    @Test
    @DisplayName("retourne élevée quand cv > 0.3")
    void testElevee() {
      KPITrendResponse k = minimalKpi();
      k.setCoefficientVariation(new BigDecimal("0.5"));
      assertThat(k.getVolatilite()).isEqualTo("élevée");
    }
  }

  @Nested
  @DisplayName("isPredictionFiable")
  class IsPredictionFiable {

    @Test
    @DisplayName("retourne false quand coefficientCorrelation null ou < 0.7")
    void testFalse() {
      KPITrendResponse k = minimalKpi();
      assertThat(k.isPredictionFiable()).isFalse();
      k.setCoefficientCorrelation(new BigDecimal("0.5"));
      assertThat(k.isPredictionFiable()).isFalse();
    }

    @Test
    @DisplayName("retourne true quand coefficientCorrelation >= 0.7")
    void testTrue() {
      KPITrendResponse k = minimalKpi();
      k.setCoefficientCorrelation(new BigDecimal("0.7"));
      assertThat(k.isPredictionFiable()).isTrue();
      k.setCoefficientCorrelation(new BigDecimal("0.95"));
      assertThat(k.isPredictionFiable()).isTrue();
    }
  }

  @Nested
  @DisplayName("getNombrePointsDonnees")
  class GetNombrePointsDonnees {

    @Test
    @DisplayName("retourne 0 quand pointsDonnees null")
    void testNull() {
      KPITrendResponse k = KPITrendResponse.builder()
          .typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
          .periodeAnalyse(PeriodeAnalyse.CETTE_SEMAINE)
          .dateDebut(LocalDateTime.now())
          .dateFin(LocalDateTime.now())
          .valeurActuelle(BigDecimal.ONE)
          .pointsDonnees(null).build();
      assertThat(k.getNombrePointsDonnees()).isEqualTo(0);
    }

    @Test
    @DisplayName("retourne la taille de pointsDonnees")
    void testSize() {
      KPITrendResponse k = minimalKpi();
      k.setPointsDonnees(List.of(
          KPITrendResponse.PointDonneeDTO.builder().date(LocalDateTime.now()).valeur(BigDecimal.ONE).build(),
          KPITrendResponse.PointDonneeDTO.builder().date(LocalDateTime.now()).valeur(BigDecimal.TEN).build()));
      assertThat(k.getNombrePointsDonnees()).isEqualTo(2);
    }
  }

  @Nested
  @DisplayName("hasAnomalies")
  class HasAnomalies {

    @Test
    @DisplayName("retourne false quand pointsDonnees null ou aucun point anomalie")
    void testFalse() {
      KPITrendResponse k = minimalKpi();
      assertThat(k.hasAnomalies()).isFalse();
      k.setPointsDonnees(List.of(
          KPITrendResponse.PointDonneeDTO.builder().date(LocalDateTime.now()).valeur(BigDecimal.ONE).anomalie(false).build()));
      assertThat(k.hasAnomalies()).isFalse();
    }

    @Test
    @DisplayName("retourne true quand au moins un point a anomalie true")
    void testTrue() {
      KPITrendResponse k = minimalKpi();
      k.setPointsDonnees(List.of(
          KPITrendResponse.PointDonneeDTO.builder().date(LocalDateTime.now()).valeur(BigDecimal.ONE).anomalie(false).build(),
          KPITrendResponse.PointDonneeDTO.builder().date(LocalDateTime.now()).valeur(BigDecimal.TEN).anomalie(true).build()));
      assertThat(k.hasAnomalies()).isTrue();
    }
  }
}
