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

import com.fasterxml.jackson.annotation.JsonFormat;
import dev.lions.unionflow.server.api.dto.base.BaseDTO;
import dev.lions.unionflow.server.api.enums.evenement.PrioriteEvenement;
import dev.lions.unionflow.server.api.enums.evenement.StatutEvenement;
import dev.lions.unionflow.server.api.enums.evenement.TypeEvenementMetier;
import dev.lions.unionflow.server.api.validation.ValidationConstants;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Future;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;

/**
 * DTO pour la gestion des événements dans l'API UnionFlow Représente un événement organisé par une
 * association
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-10
 */
@Getter
@Setter
public class EvenementDTO extends BaseDTO {

  private static final long serialVersionUID = 1L;

  /** Titre de l'événement */
  @NotBlank(message = "Le titre" + ValidationConstants.OBLIGATOIRE_MESSAGE)
  @Size(
      min = ValidationConstants.TITRE_MIN_LENGTH,
      max = ValidationConstants.TITRE_MAX_LENGTH,
      message = ValidationConstants.TITRE_SIZE_MESSAGE)
  private String titre;

  /** Description détaillée de l'événement */
  @Size(
      max = ValidationConstants.DESCRIPTION_COURTE_MAX_LENGTH,
      message = ValidationConstants.DESCRIPTION_COURTE_SIZE_MESSAGE)
  private String description;

  /** Type d'événement */
  @NotNull(message = "Le type d'événement est obligatoire")
  private TypeEvenementMetier typeEvenement;

  /** Statut de l'événement */
  @NotNull(message = "Le statut est obligatoire")
  private StatutEvenement statut;

  /** Priorité de l'événement */
  private PrioriteEvenement priorite;

  /** Date de début de l'événement */
  @JsonFormat(pattern = "yyyy-MM-dd")
  @NotNull(message = "La date de début est obligatoire")
  @Future(message = "La date de début doit être dans le futur")
  private LocalDate dateDebut;

  /** Date de fin de l'événement */
  @JsonFormat(pattern = "yyyy-MM-dd")
  private LocalDate dateFin;

  /** Heure de début */
  @JsonFormat(pattern = "HH:mm")
  private LocalTime heureDebut;

  /** Heure de fin */
  @JsonFormat(pattern = "HH:mm")
  private LocalTime heureFin;

  /** Lieu de l'événement */
  @NotBlank(message = "Le lieu est obligatoire")
  @Size(max = 100, message = "Le lieu ne peut pas dépasser 100 caractères")
  private String lieu;

  /** Adresse complète du lieu */
  @Size(max = 200, message = "L'adresse ne peut pas dépasser 200 caractères")
  private String adresse;

  /** Ville */
  @Size(max = 50, message = "La ville ne peut pas dépasser 50 caractères")
  private String ville;

  /** Région */
  @Size(max = 50, message = "La région ne peut pas dépasser 50 caractères")
  private String region;

  /** Coordonnées GPS (latitude) */
  @DecimalMin(value = "-90.0", message = "La latitude doit être entre -90 et 90")
  @DecimalMax(value = "90.0", message = "La latitude doit être entre -90 et 90")
  private BigDecimal latitude;

  /** Coordonnées GPS (longitude) */
  @DecimalMin(value = "-180.0", message = "La longitude doit être entre -180 et 180")
  @DecimalMax(value = "180.0", message = "La longitude doit être entre -180 et 180")
  private BigDecimal longitude;

  /** Identifiant de l'association organisatrice */
  @NotNull(message = "L'association organisatrice est obligatoire")
  private UUID associationId;

  /** Nom de l'association organisatrice (lecture seule) */
  private String nomAssociation;

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

  /** Email de l'organisateur */
  @Email(message = "Format d'email invalide")
  @Size(max = 100, message = "L'email ne peut pas dépasser 100 caractères")
  private String emailOrganisateur;

  /** Téléphone de l'organisateur */
  @Pattern(regexp = "^\\+?[0-9\\s\\-\\(\\)]{8,20}$", message = "Format de téléphone invalide")
  private String telephoneOrganisateur;

  /** Capacité maximale de participants */
  @Min(value = 1, message = "La capacité doit être d'au moins 1 personne")
  @Max(value = 10000, message = "La capacité ne peut pas dépasser 10000 personnes")
  private Integer capaciteMax;

  /** Nombre de participants inscrits */
  @Min(value = 0, message = "Le nombre de participants ne peut pas être négatif")
  private Integer participantsInscrits;

  /** Nombre de participants présents */
  @Min(value = 0, message = "Le nombre de participants présents ne peut pas être négatif")
  private Integer participantsPresents;

  /** Budget prévu pour l'événement */
  @DecimalMin(
      value = ValidationConstants.MONTANT_MIN_VALUE,
      message = ValidationConstants.MONTANT_POSITIF_MESSAGE)
  @Digits(
      integer = ValidationConstants.MONTANT_INTEGER_DIGITS,
      fraction = ValidationConstants.MONTANT_FRACTION_DIGITS,
      message = ValidationConstants.MONTANT_DIGITS_MESSAGE)
  private BigDecimal budget;

  /** Coût réel de l'événement */
  @DecimalMin(
      value = ValidationConstants.MONTANT_MIN_VALUE,
      message = ValidationConstants.MONTANT_POSITIF_MESSAGE)
  @Digits(
      integer = ValidationConstants.MONTANT_INTEGER_DIGITS,
      fraction = ValidationConstants.MONTANT_FRACTION_DIGITS,
      message = ValidationConstants.MONTANT_DIGITS_MESSAGE)
  private BigDecimal coutReel;

  /** Code de la devise */
  @Pattern(
      regexp = ValidationConstants.DEVISE_PATTERN,
      message = ValidationConstants.DEVISE_MESSAGE)
  private String codeDevise;

  /** Indique si l'inscription est obligatoire */
  private Boolean inscriptionObligatoire = false;

  /** Date limite d'inscription */
  @JsonFormat(pattern = "yyyy-MM-dd")
  private LocalDate dateLimiteInscription;

  /** Indique si l'événement est public */
  private Boolean evenementPublic = true;

  /** Indique si l'événement est récurrent */
  private Boolean recurrent = false;

  /** Fréquence de récurrence (si récurrent) */
  @Pattern(
      regexp = "^(HEBDOMADAIRE|MENSUELLE|TRIMESTRIELLE|ANNUELLE)$",
      message = "Fréquence de récurrence invalide")
  private String frequenceRecurrence;

  /** Instructions spéciales pour les participants */
  @Size(max = 500, message = "Les instructions ne peuvent pas dépasser 500 caractères")
  private String instructions;

  /** Matériel nécessaire */
  @Size(max = 500, message = "La liste du matériel ne peut pas dépasser 500 caractères")
  private String materielNecessaire;

  /** Conditions météorologiques requises */
  @Size(max = 100, message = "Les conditions météo ne peuvent pas dépasser 100 caractères")
  private String conditionsMeteo;

  /** URL de l'image de l'événement */
  @Size(max = 255, message = "L'URL de l'image ne peut pas dépasser 255 caractères")
  private String imageUrl;

  /** Couleur thème de l'événement */
  @Pattern(regexp = "^#[0-9A-Fa-f]{6}$", message = "Format de couleur invalide (format: #RRGGBB)")
  private String couleurTheme;

  /** Date d'annulation (si annulé) */
  @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  private LocalDateTime dateAnnulation;

  /** Raison de l'annulation */
  @Size(max = 500, message = "La raison d'annulation ne peut pas dépasser 500 caractères")
  private String raisonAnnulation;

  /** Identifiant de l'utilisateur qui a annulé */
  private Long annulePar;

  /** Nom de l'utilisateur qui a annulé */
  private String nomAnnulateur;

  // Constructeurs
  public EvenementDTO() {
    super();
    this.statut = StatutEvenement.PLANIFIE;
    this.priorite = PrioriteEvenement.NORMALE;
    this.participantsInscrits = 0;
    this.participantsPresents = 0;
    this.inscriptionObligatoire = false;
    this.evenementPublic = true;
    this.recurrent = false;
    this.codeDevise = "XOF"; // Franc CFA par défaut
  }

  public EvenementDTO(
      String titre, TypeEvenementMetier typeEvenement, LocalDate dateDebut, String lieu) {
    this();
    this.titre = titre;
    this.typeEvenement = typeEvenement;
    this.dateDebut = dateDebut;
    this.lieu = lieu;
  }

  // Getters et Setters
  public String getTitre() {
    return titre;
  }

  public void setTitre(String titre) {
    this.titre = titre;
  }

  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  public TypeEvenementMetier getTypeEvenement() {
    return typeEvenement;
  }

  public void setTypeEvenement(TypeEvenementMetier typeEvenement) {
    this.typeEvenement = typeEvenement;
  }

  public StatutEvenement getStatut() {
    return statut;
  }

  public void setStatut(StatutEvenement statut) {
    this.statut = statut;
  }

  public PrioriteEvenement getPriorite() {
    return priorite;
  }

  public void setPriorite(PrioriteEvenement priorite) {
    this.priorite = priorite;
  }

  public LocalDate getDateDebut() {
    return dateDebut;
  }

  public void setDateDebut(LocalDate dateDebut) {
    this.dateDebut = dateDebut;
  }

  public LocalDate getDateFin() {
    return dateFin;
  }

  public void setDateFin(LocalDate dateFin) {
    this.dateFin = dateFin;
  }

  public LocalTime getHeureDebut() {
    return heureDebut;
  }

  public void setHeureDebut(LocalTime heureDebut) {
    this.heureDebut = heureDebut;
  }

  public LocalTime getHeureFin() {
    return heureFin;
  }

  public void setHeureFin(LocalTime heureFin) {
    this.heureFin = heureFin;
  }

  public String getLieu() {
    return lieu;
  }

  public void setLieu(String lieu) {
    this.lieu = lieu;
  }

  public String getAdresse() {
    return adresse;
  }

  public void setAdresse(String adresse) {
    this.adresse = adresse;
  }

  public String getVille() {
    return ville;
  }

  public void setVille(String ville) {
    this.ville = ville;
  }

  public String getRegion() {
    return region;
  }

  public void setRegion(String region) {
    this.region = region;
  }

  public BigDecimal getLatitude() {
    return latitude;
  }

  public void setLatitude(BigDecimal latitude) {
    this.latitude = latitude;
  }

  public BigDecimal getLongitude() {
    return longitude;
  }

  public void setLongitude(BigDecimal longitude) {
    this.longitude = longitude;
  }

  public UUID getAssociationId() {
    return associationId;
  }

  public void setAssociationId(UUID associationId) {
    this.associationId = associationId;
  }

  public String getNomAssociation() {
    return nomAssociation;
  }

  public void setNomAssociation(String nomAssociation) {
    this.nomAssociation = nomAssociation;
  }

  public String getOrganisateur() {
    return organisateur;
  }

  public void setOrganisateur(String organisateur) {
    this.organisateur = organisateur;
  }

  public String getEmailOrganisateur() {
    return emailOrganisateur;
  }

  public void setEmailOrganisateur(String emailOrganisateur) {
    this.emailOrganisateur = emailOrganisateur;
  }

  public String getTelephoneOrganisateur() {
    return telephoneOrganisateur;
  }

  public void setTelephoneOrganisateur(String telephoneOrganisateur) {
    this.telephoneOrganisateur = telephoneOrganisateur;
  }

  public Integer getCapaciteMax() {
    return capaciteMax;
  }

  public void setCapaciteMax(Integer capaciteMax) {
    this.capaciteMax = capaciteMax;
  }

  public Integer getParticipantsInscrits() {
    return participantsInscrits;
  }

  public void setParticipantsInscrits(Integer participantsInscrits) {
    this.participantsInscrits = participantsInscrits;
  }

  public Integer getParticipantsPresents() {
    return participantsPresents;
  }

  public void setParticipantsPresents(Integer participantsPresents) {
    this.participantsPresents = participantsPresents;
  }

  public BigDecimal getBudget() {
    return budget;
  }

  public void setBudget(BigDecimal budget) {
    this.budget = budget;
  }

  public BigDecimal getCoutReel() {
    return coutReel;
  }

  public void setCoutReel(BigDecimal coutReel) {
    this.coutReel = coutReel;
  }

  public String getCodeDevise() {
    return codeDevise;
  }

  public void setCodeDevise(String codeDevise) {
    this.codeDevise = codeDevise;
  }

  public Boolean getInscriptionObligatoire() {
    return inscriptionObligatoire;
  }

  public void setInscriptionObligatoire(Boolean inscriptionObligatoire) {
    this.inscriptionObligatoire = inscriptionObligatoire;
  }

  public LocalDate getDateLimiteInscription() {
    return dateLimiteInscription;
  }

  public void setDateLimiteInscription(LocalDate dateLimiteInscription) {
    this.dateLimiteInscription = dateLimiteInscription;
  }

  public Boolean getEvenementPublic() {
    return evenementPublic;
  }

  public void setEvenementPublic(Boolean evenementPublic) {
    this.evenementPublic = evenementPublic;
  }

  public Boolean getRecurrent() {
    return recurrent;
  }

  public void setRecurrent(Boolean recurrent) {
    this.recurrent = recurrent;
  }

  public String getFrequenceRecurrence() {
    return frequenceRecurrence;
  }

  public void setFrequenceRecurrence(String frequenceRecurrence) {
    this.frequenceRecurrence = frequenceRecurrence;
  }

  public String getInstructions() {
    return instructions;
  }

  public void setInstructions(String instructions) {
    this.instructions = instructions;
  }

  public String getMaterielNecessaire() {
    return materielNecessaire;
  }

  public void setMaterielNecessaire(String materielNecessaire) {
    this.materielNecessaire = materielNecessaire;
  }

  public String getConditionsMeteo() {
    return conditionsMeteo;
  }

  public void setConditionsMeteo(String conditionsMeteo) {
    this.conditionsMeteo = conditionsMeteo;
  }

  public String getImageUrl() {
    return imageUrl;
  }

  public void setImageUrl(String imageUrl) {
    this.imageUrl = imageUrl;
  }

  public String getCouleurTheme() {
    return couleurTheme;
  }

  public void setCouleurTheme(String couleurTheme) {
    this.couleurTheme = couleurTheme;
  }

  public LocalDateTime getDateAnnulation() {
    return dateAnnulation;
  }

  public void setDateAnnulation(LocalDateTime dateAnnulation) {
    this.dateAnnulation = dateAnnulation;
  }

  public String getRaisonAnnulation() {
    return raisonAnnulation;
  }

  public void setRaisonAnnulation(String raisonAnnulation) {
    this.raisonAnnulation = raisonAnnulation;
  }

  public Long getAnnulePar() {
    return annulePar;
  }

  public void setAnnulePar(Long annulePar) {
    this.annulePar = annulePar;
  }

  public String getNomAnnulateur() {
    return nomAnnulateur;
  }

  public void setNomAnnulateur(String nomAnnulateur) {
    this.nomAnnulateur = nomAnnulateur;
  }

  // Méthodes utilitaires

  /**
   * Vérifie si l'événement est en cours
   *
   * @return true si l'événement est actuellement en cours
   */
  public boolean estEnCours() {
    return StatutEvenement.EN_COURS.equals(statut);
  }

  /**
   * Vérifie si l'événement est terminé
   *
   * @return true si l'événement est terminé
   */
  public boolean estTermine() {
    return StatutEvenement.TERMINE.equals(statut);
  }

  /**
   * Vérifie si l'événement est annulé
   *
   * @return true si l'événement est annulé
   */
  public boolean estAnnule() {
    return StatutEvenement.ANNULE.equals(statut);
  }

  /**
   * Vérifie si l'événement est complet (capacité atteinte)
   *
   * @return true si le nombre d'inscrits atteint la capacité maximale
   */
  public boolean estComplet() {
    return capaciteMax != null
        && participantsInscrits != null
        && participantsInscrits >= capaciteMax;
  }

  /**
   * Calcule le nombre de places disponibles
   *
   * @return Le nombre de places restantes
   */
  public int getPlacesDisponibles() {
    if (capaciteMax == null || participantsInscrits == null) {
      return 0;
    }
    return Math.max(0, capaciteMax - participantsInscrits);
  }

  /**
   * Calcule le taux de remplissage en pourcentage
   *
   * @return Le pourcentage de remplissage (0-100)
   */
  public int getTauxRemplissage() {
    if (capaciteMax == null || capaciteMax == 0 || participantsInscrits == null) {
      return 0;
    }
    return (participantsInscrits * 100) / capaciteMax;
  }

  /**
   * Calcule le taux de présence en pourcentage
   *
   * @return Le pourcentage de présence (0-100)
   */
  public int getTauxPresence() {
    if (participantsInscrits == null || participantsInscrits == 0 || participantsPresents == null) {
      return 0;
    }
    return (participantsPresents * 100) / participantsInscrits;
  }

  /**
   * Vérifie si les inscriptions sont encore ouvertes
   *
   * @return true si les inscriptions sont ouvertes
   */
  public boolean sontInscriptionsOuvertes() {
    if (estAnnule() || estTermine()) {
      return false;
    }

    if (dateLimiteInscription != null && LocalDate.now().isAfter(dateLimiteInscription)) {
      return false;
    }

    return !estComplet();
  }

  /**
   * Calcule la durée de l'événement en heures
   *
   * @return La durée en heures, ou 0 si non calculable
   */
  public long getDureeEnHeures() {
    if (heureDebut == null || heureFin == null) {
      return 0;
    }

    return heureDebut.until(heureFin, java.time.temporal.ChronoUnit.HOURS);
  }

  /**
   * Vérifie si l'événement dure plusieurs jours
   *
   * @return true si l'événement s'étend sur plusieurs jours
   */
  public boolean estEvenementMultiJours() {
    return dateFin != null && !dateDebut.equals(dateFin);
  }

  /**
   * Retourne le libellé du type d'événement
   *
   * @return Le libellé du type
   */
  public String getTypeEvenementLibelle() {
    return typeEvenement != null ? typeEvenement.getLibelle() : "Non défini";
  }

  /**
   * Retourne le libellé du statut
   *
   * @return Le libellé du statut
   */
  public String getStatutLibelle() {
    return statut != null ? statut.getLibelle() : "Non défini";
  }

  /**
   * Retourne le libellé de la priorité
   *
   * @return Le libellé de la priorité
   */
  public String getPrioriteLibelle() {
    return priorite != null ? priorite.getLibelle() : "Normale";
  }

  /**
   * Retourne l'adresse complète du lieu
   *
   * @return L'adresse complète formatée
   */
  public String getAdresseComplete() {
    StringBuilder adresseComplete = new StringBuilder();

    if (lieu != null && !lieu.trim().isEmpty()) {
      adresseComplete.append(lieu);
    }

    if (adresse != null && !adresse.trim().isEmpty()) {
      if (adresseComplete.length() > 0) adresseComplete.append(", ");
      adresseComplete.append(adresse);
    }

    if (ville != null && !ville.trim().isEmpty()) {
      if (adresseComplete.length() > 0) adresseComplete.append(", ");
      adresseComplete.append(ville);
    }

    if (region != null && !region.trim().isEmpty()) {
      if (adresseComplete.length() > 0) adresseComplete.append(", ");
      adresseComplete.append(region);
    }

    return adresseComplete.toString();
  }

  /**
   * Vérifie si l'événement a des coordonnées GPS
   *
   * @return true si latitude et longitude sont définies
   */
  public boolean hasCoordonnees() {
    return latitude != null && longitude != null;
  }

  /**
   * Calcule l'écart budgétaire
   *
   * @return La différence entre budget et coût réel (positif = économie, négatif = dépassement)
   */
  public BigDecimal getEcartBudgetaire() {
    if (budget == null || coutReel == null) {
      return BigDecimal.ZERO;
    }
    return budget.subtract(coutReel);
  }

  /**
   * Vérifie si le budget a été dépassé
   *
   * @return true si le coût réel dépasse le budget
   */
  public boolean estBudgetDepasse() {
    return getEcartBudgetaire().compareTo(BigDecimal.ZERO) < 0;
  }

  @Override
  public String toString() {
    return "EvenementDTO{"
        + "titre='"
        + titre
        + '\''
        + ", typeEvenement='"
        + typeEvenement
        + '\''
        + ", statut='"
        + statut
        + '\''
        + ", dateDebut="
        + dateDebut
        + ", lieu='"
        + lieu
        + '\''
        + ", participantsInscrits="
        + participantsInscrits
        + ", capaciteMax="
        + capaciteMax
        + "} "
        + super.toString();
  }
}
