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

import com.fasterxml.jackson.annotation.JsonFormat;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse;
import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
 * DTO pour les tendances et évolutions des KPI UnionFlow
 *
 * <p>Représente l'évolution d'un KPI dans le temps avec les points de données historiques pour
 * générer des graphiques de tendance.
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-16
 */
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class KPITrendResponse extends BaseDTO {

  private static final long serialVersionUID = 1L;

  /** Type de métrique pour cette tendance */
  @NotNull(message = "Le type de métrique est obligatoire")
  private TypeMetrique typeMetrique;

  /** Période d'analyse globale */
  @NotNull(message = "La période d'analyse est obligatoire")
  private PeriodeAnalyse periodeAnalyse;

  /** Identifiant de l'organisation (optionnel) */
  private UUID organisationId;

  /** Nom de l'organisation */
  @Size(max = 200, message = "Le nom de l'organisation ne peut pas dépasser 200 caractères")
  private String nomOrganisation;

  /** Date de début de la période analysée */
  @NotNull(message = "La date de début est obligatoire")
  @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  private LocalDateTime dateDebut;

  /** Date de fin de la période analysée */
  @NotNull(message = "La date de fin est obligatoire")
  @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  private LocalDateTime dateFin;

  /** Points de données pour la tendance */
  @NotNull(message = "Les points de données sont obligatoires")
  private List<PointDonneeDTO> pointsDonnees;

  /** Valeur actuelle du KPI */
  @NotNull(message = "La valeur actuelle est obligatoire")
  @DecimalMin(value = "0.0", message = "La valeur actuelle doit être positive ou nulle")
  @Digits(integer = 15, fraction = 4, message = "Format de valeur actuelle invalide")
  private BigDecimal valeurActuelle;

  /** Valeur minimale sur la période */
  @DecimalMin(value = "0.0", message = "La valeur minimale doit être positive ou nulle")
  @Digits(integer = 15, fraction = 4, message = "Format de valeur minimale invalide")
  private BigDecimal valeurMinimale;

  /** Valeur maximale sur la période */
  @DecimalMin(value = "0.0", message = "La valeur maximale doit être positive ou nulle")
  @Digits(integer = 15, fraction = 4, message = "Format de valeur maximale invalide")
  private BigDecimal valeurMaximale;

  /** Valeur moyenne sur la période */
  @DecimalMin(value = "0.0", message = "La valeur moyenne doit être positive ou nulle")
  @Digits(integer = 15, fraction = 4, message = "Format de valeur moyenne invalide")
  private BigDecimal valeurMoyenne;

  /** Écart-type des valeurs */
  @DecimalMin(value = "0.0", message = "L'écart-type doit être positif ou nul")
  @Digits(integer = 15, fraction = 4, message = "Format d'écart-type invalide")
  private BigDecimal ecartType;

  /** Coefficient de variation (écart-type / moyenne) */
  @DecimalMin(value = "0.0", message = "Le coefficient de variation doit être positif ou nul")
  @Digits(integer = 6, fraction = 4, message = "Format de coefficient de variation invalide")
  private BigDecimal coefficientVariation;

  /** Tendance générale (pente de la régression linéaire) */
  @Digits(integer = 10, fraction = 6, message = "Format de tendance invalide")
  private BigDecimal tendanceGenerale;

  /** Coefficient de corrélation R² */
  @DecimalMin(value = "0.0", message = "Le coefficient de corrélation doit être positif ou nul")
  @DecimalMax(value = "1.0", message = "Le coefficient de corrélation ne peut pas dépasser 1")
  @Digits(integer = 1, fraction = 6, message = "Format de coefficient de corrélation invalide")
  private BigDecimal coefficientCorrelation;

  /** Pourcentage d'évolution depuis le début de la période */
  @Digits(integer = 6, fraction = 2, message = "Format de pourcentage d'évolution invalide")
  private BigDecimal pourcentageEvolutionGlobale;

  /** Prédiction pour la prochaine période */
  @DecimalMin(value = "0.0", message = "La prédiction doit être positive ou nulle")
  @Digits(integer = 15, fraction = 4, message = "Format de prédiction invalide")
  private BigDecimal predictionProchainePeriode;

  /** Marge d'erreur de la prédiction (en pourcentage) */
  @DecimalMin(value = "0.0", message = "La marge d'erreur doit être positive ou nulle")
  @DecimalMax(value = "100.0", message = "La marge d'erreur ne peut pas dépasser 100%")
  @Digits(integer = 3, fraction = 2, message = "Format de marge d'erreur invalide")
  private BigDecimal margeErreurPrediction;

  /** Seuil d'alerte bas */
  @DecimalMin(value = "0.0", message = "Le seuil d'alerte bas doit être positif ou nul")
  @Digits(integer = 15, fraction = 4, message = "Format de seuil d'alerte bas invalide")
  private BigDecimal seuilAlerteBas;

  /** Seuil d'alerte haut */
  @DecimalMin(value = "0.0", message = "Le seuil d'alerte haut doit être positif ou nul")
  @Digits(integer = 15, fraction = 4, message = "Format de seuil d'alerte haut invalide")
  private BigDecimal seuilAlerteHaut;

  /** Indicateur si une alerte est active */
  @Builder.Default private Boolean alerteActive = false;

  /** Type d'alerte (bas, haut, anomalie) */
  @Size(max = 50, message = "Le type d'alerte ne peut pas dépasser 50 caractères")
  private String typeAlerte;

  /** Message d'alerte */
  @Size(max = 500, message = "Le message d'alerte ne peut pas dépasser 500 caractères")
  private String messageAlerte;

  /** Configuration du graphique (couleurs, style, etc.) */
  @Size(max = 2000, message = "La configuration graphique ne peut pas dépasser 2000 caractères")
  private String configurationGraphique;

  /** Intervalle de regroupement des données */
  @Size(max = 20, message = "L'intervalle de regroupement ne peut pas dépasser 20 caractères")
  private String intervalleRegroupement;

  /** Format d'affichage des dates */
  @Size(max = 20, message = "Le format de date ne peut pas dépasser 20 caractères")
  private String formatDate;

  /** Date de dernière mise à jour */
  @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  private LocalDateTime dateDerniereMiseAJour;

  /** Fréquence de mise à jour en minutes */
  @DecimalMin(value = "1", message = "La fréquence de mise à jour minimum est 1 minute")
  private Integer frequenceMiseAJourMinutes;

  // === CLASSES INTERNES ===

  /** Classe interne représentant un point de données dans la tendance */
  @Getter
  @Setter
  @Builder
  @NoArgsConstructor
  @AllArgsConstructor
  public static class PointDonneeDTO {

    /** Date du point de données */
    @NotNull(message = "La date du point de données est obligatoire")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime date;

    /** Valeur du point de données */
    @NotNull(message = "La valeur du point de données est obligatoire")
    @DecimalMin(value = "0.0", message = "La valeur du point doit être positive ou nulle")
    @Digits(integer = 15, fraction = 4, message = "Format de valeur du point invalide")
    private BigDecimal valeur;

    /** Libellé du point (optionnel) */
    @Size(max = 100, message = "Le libellé du point ne peut pas dépasser 100 caractères")
    private String libelle;

    /** Indicateur si le point est une anomalie */
    @Builder.Default private Boolean anomalie = false;

    /** Indicateur si le point est une prédiction */
    @Builder.Default private Boolean prediction = false;

    /** Métadonnées additionnelles du point */
    private String metadonnees;
  }

  // === MÉTHODES UTILITAIRES ===

  /**
   * Retourne le libellé de la métrique
   *
   * @return Le libellé de la métrique
   */
  public String getLibelleMetrique() {
    return typeMetrique.getLibelle();
  }

  /**
   * Retourne l'unité de mesure
   *
   * @return L'unité de mesure
   */
  public String getUnite() {
    return typeMetrique.getUnite();
  }

  /**
   * Retourne l'icône de la métrique
   *
   * @return L'icône Material Design
   */
  public String getIcone() {
    return typeMetrique.getIcone();
  }

  /**
   * Retourne la couleur de la métrique
   *
   * @return Le code couleur hexadécimal
   */
  public String getCouleur() {
    return typeMetrique.getCouleur();
  }

  /**
   * Vérifie si la tendance est positive
   *
   * @return true si la tendance générale est positive
   */
  public boolean isTendancePositive() {
    return tendanceGenerale != null && tendanceGenerale.compareTo(BigDecimal.ZERO) > 0;
  }

  /**
   * Vérifie si la tendance est négative
   *
   * @return true si la tendance générale est négative
   */
  public boolean isTendanceNegative() {
    return tendanceGenerale != null && tendanceGenerale.compareTo(BigDecimal.ZERO) < 0;
  }

  /**
   * Vérifie si la tendance est stable
   *
   * @return true si la tendance générale est stable
   */
  public boolean isTendanceStable() {
    return tendanceGenerale != null && tendanceGenerale.compareTo(BigDecimal.ZERO) == 0;
  }

  /**
   * Retourne la volatilité du KPI (basée sur le coefficient de variation)
   *
   * @return "faible", "moyenne" ou "élevée"
   */
  public String getVolatilite() {
    if (coefficientVariation == null) return "inconnue";

    BigDecimal cv = coefficientVariation;
    if (cv.compareTo(new BigDecimal("0.1")) <= 0) return "faible";
    if (cv.compareTo(new BigDecimal("0.3")) <= 0) return "moyenne";
    return "élevée";
  }

  /**
   * Vérifie si la prédiction est fiable (R² > 0.7)
   *
   * @return true si la prédiction est considérée comme fiable
   */
  public boolean isPredictionFiable() {
    return coefficientCorrelation != null
        && coefficientCorrelation.compareTo(new BigDecimal("0.7")) >= 0;
  }

  /**
   * Retourne le nombre de points de données
   *
   * @return Le nombre de points de données
   */
  public int getNombrePointsDonnees() {
    return pointsDonnees != null ? pointsDonnees.size() : 0;
  }

  /**
   * Vérifie si des anomalies ont été détectées
   *
   * @return true si au moins un point est marqué comme anomalie
   */
  public boolean hasAnomalies() {
    return pointsDonnees != null
        && pointsDonnees.stream().anyMatch(point -> Boolean.TRUE.equals(point.getAnomalie()));
  }
}
