package dev.lions.unionflow.server.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import lombok.*;

/**
 * Entité Événement pour la gestion des événements de l'union
 *
 * @author UnionFlow Team
 * @version 2.0
 * @since 2025-01-16
 */
@Entity
@Table(name = "evenements", indexes = {
    @Index(name = "idx_evenement_date_debut", columnList = "date_debut"),
    @Index(name = "idx_evenement_statut", columnList = "statut"),
    @Index(name = "idx_evenement_type", columnList = "type_evenement"),
    @Index(name = "idx_evenement_organisation", columnList = "organisation_id")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode(callSuper = true)
public class Evenement extends BaseEntity {

  @NotBlank
  @Size(min = 3, max = 200)
  @Column(name = "titre", nullable = false, length = 200)
  private String titre;

  @Size(max = 2000)
  @Column(name = "description", length = 2000)
  private String description;

  @NotNull
  @Column(name = "date_debut", nullable = false)
  private LocalDateTime dateDebut;

  @Column(name = "date_fin")
  private LocalDateTime dateFin;

  @Size(max = 500)
  @Column(name = "lieu", length = 500)
  private String lieu;

  @Size(max = 1000)
  @Column(name = "adresse", length = 1000)
  private String adresse;

  @Column(name = "type_evenement", length = 50)
  private String typeEvenement;

  @Builder.Default
  @Column(name = "statut", nullable = false, length = 30)
  private String statut = "PLANIFIE";

  @Min(0)
  @Column(name = "capacite_max")
  private Integer capaciteMax;

  @DecimalMin("0.00")
  @Digits(integer = 8, fraction = 2)
  @Column(name = "prix", precision = 10, scale = 2)
  private BigDecimal prix;

  @Builder.Default
  @Column(name = "inscription_requise", nullable = false)
  private Boolean inscriptionRequise = false;

  @Column(name = "date_limite_inscription")
  private LocalDateTime dateLimiteInscription;

  @Size(max = 1000)
  @Column(name = "instructions_particulieres", length = 1000)
  private String instructionsParticulieres;

  @Size(max = 500)
  @Column(name = "contact_organisateur", length = 500)
  private String contactOrganisateur;

  @Size(max = 2000)
  @Column(name = "materiel_requis", length = 2000)
  private String materielRequis;

  @Builder.Default
  @Column(name = "visible_public", nullable = false)
  private Boolean visiblePublic = true;

  // Relations
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "organisation_id")
  private Organisation organisation;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "organisateur_id")
  private Membre organisateur;

  @JsonIgnore
  @OneToMany(mappedBy = "evenement", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
  @Builder.Default
  private List<InscriptionEvenement> inscriptions = new ArrayList<>();

  @JsonIgnore
  @OneToMany(mappedBy = "evenement", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
  @Builder.Default
  private List<Adresse> adresses = new ArrayList<>();

  /** Types d'événements */
  public enum TypeEvenement {
    ASSEMBLEE_GENERALE("Assemblée Générale"),
    REUNION("Réunion"),
    FORMATION("Formation"),
    CONFERENCE("Conférence"),
    ATELIER("Atelier"),
    SEMINAIRE("Séminaire"),
    EVENEMENT_SOCIAL("Événement Social"),
    MANIFESTATION("Manifestation"),
    CELEBRATION("Célébration"),
    AUTRE("Autre");

    private final String libelle;

    TypeEvenement(String libelle) {
      this.libelle = libelle;
    }

    public String getLibelle() {
      return libelle;
    }
  }

  /** Statuts d'événement */
  public enum StatutEvenement {
    PLANIFIE("Planifié"),
    CONFIRME("Confirmé"),
    EN_COURS("En cours"),
    TERMINE("Terminé"),
    ANNULE("Annulé"),
    REPORTE("Reporté");

    private final String libelle;

    StatutEvenement(String libelle) {
      this.libelle = libelle;
    }

    public String getLibelle() {
      return libelle;
    }
  }

  // Méthodes métier

  /** Vérifie si l'événement est ouvert aux inscriptions */
  @JsonIgnore
  public boolean isOuvertAuxInscriptions() {
    if (!inscriptionRequise || !getActif()) {
      return false;
    }

    LocalDateTime maintenant = LocalDateTime.now();

    // Vérifier si la date limite d'inscription n'est pas dépassée
    if (dateLimiteInscription != null && maintenant.isAfter(dateLimiteInscription)) {
      return false;
    }

    // Vérifier si l'événement n'a pas déjà commencé
    if (maintenant.isAfter(dateDebut)) {
      return false;
    }

    // Vérifier la capacité
    if (capaciteMax != null && getNombreInscrits() >= capaciteMax) {
      return false;
    }

    return "PLANIFIE".equals(statut) || "CONFIRME".equals(statut);
  }

  /** Obtient le nombre d'inscrits à l'événement */
  @JsonIgnore
  public int getNombreInscrits() {
    return inscriptions != null
        ? (int) inscriptions.stream()
            .filter(
                inscription -> InscriptionEvenement.StatutInscription.CONFIRMEE.name().equals(inscription.getStatut()))
            .count()
        : 0;
  }

  /** Vérifie si l'événement est complet */
  @JsonIgnore
  public boolean isComplet() {
    return capaciteMax != null && getNombreInscrits() >= capaciteMax;
  }

  /** Vérifie si l'événement est en cours */
  public boolean isEnCours() {
    LocalDateTime maintenant = LocalDateTime.now();
    return maintenant.isAfter(dateDebut) && (dateFin == null || maintenant.isBefore(dateFin));
  }

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

    LocalDateTime maintenant = LocalDateTime.now();
    return dateFin != null && maintenant.isAfter(dateFin);
  }

  /** Calcule la durée de l'événement en heures */
  public Long getDureeEnHeures() {
    if (dateFin == null) {
      return null;
    }

    return java.time.Duration.between(dateDebut, dateFin).toHours();
  }

  /** Obtient le nombre de places restantes */
  @JsonIgnore
  public Integer getPlacesRestantes() {
    if (capaciteMax == null) {
      return null; // Capacité illimitée
    }

    return Math.max(0, capaciteMax - getNombreInscrits());
  }

  /** Vérifie si un membre est inscrit à l'événement */
  public boolean isMemberInscrit(UUID membreId) {
    return inscriptions != null
        && inscriptions.stream()
            .anyMatch(
                inscription -> inscription.getMembre().getId().equals(membreId)
                    && InscriptionEvenement.StatutInscription.CONFIRMEE.name().equals(inscription.getStatut()));
  }

  /** Obtient le taux de remplissage en pourcentage */
  @JsonIgnore
  public Double getTauxRemplissage() {
    if (capaciteMax == null || capaciteMax == 0) {
      return null;
    }

    return (double) getNombreInscrits() / capaciteMax * 100;
  }
}
