package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.entity.Evenement;
import dev.lions.unionflow.server.entity.FeedbackEvenement;
import dev.lions.unionflow.server.entity.InscriptionEvenement;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.repository.EvenementRepository;
import dev.lions.unionflow.server.repository.FeedbackEvenementRepository;
import dev.lions.unionflow.server.repository.InscriptionEvenementRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import dev.lions.unionflow.server.service.KeycloakService;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Sort;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import java.time.LocalDateTime;
import jakarta.ws.rs.NotFoundException;
import org.hibernate.Hibernate;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.jboss.logging.Logger;

/**
 * Service métier pour la gestion des événements Version simplifiée pour tester
 * les imports et
 * Lombok
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-15
 */
@ApplicationScoped
public class EvenementService {

  private static final Logger LOG = Logger.getLogger(EvenementService.class);

  @Inject
  EvenementRepository evenementRepository;

  @Inject
  MembreRepository membreRepository;

  @Inject
  OrganisationRepository organisationRepository;

  @Inject
  KeycloakService keycloakService;

  @Inject
  InscriptionEvenementRepository inscriptionRepository;

  @Inject
  FeedbackEvenementRepository feedbackRepository;

  /**
   * Crée un nouvel événement
   *
   * @param evenement l'événement à créer
   * @return l'événement créé
   * @throws IllegalArgumentException si les données sont invalides
   */
  @Transactional
  public Evenement creerEvenement(Evenement evenement) {
    LOG.infof("Création événement: %s", evenement.getTitre());

    // Validation des données
    validerEvenement(evenement);

    // Vérifier l'unicité du titre dans l'organisation
    if (evenement.getOrganisation() != null) {
      Optional<Evenement> existant = evenementRepository.findByTitre(evenement.getTitre());
      if (existant.isPresent()
          && existant.get().getOrganisation().getId().equals(evenement.getOrganisation().getId())) {
        throw new IllegalArgumentException(
            "Un événement avec ce titre existe déjà dans cette organisation");
      }
    }

    // Métadonnées de création
    evenement.setCreePar(keycloakService.getCurrentUserEmail());

    // Valeurs par défaut
    if (evenement.getStatut() == null) {
      evenement.setStatut("PLANIFIE");
    }
    if (evenement.getActif() == null) {
      evenement.setActif(true);
    }
    if (evenement.getVisiblePublic() == null) {
      evenement.setVisiblePublic(true);
    }
    if (evenement.getInscriptionRequise() == null) {
      evenement.setInscriptionRequise(true);
    }

    evenementRepository.persist(evenement);

    LOG.infof("Événement créé avec succès: ID=%s, Titre=%s", evenement.getId(), evenement.getTitre());
    return evenement;
  }

  /**
   * Met à jour un événement existant
   *
   * @param id                l'UUID de l'événement
   * @param evenementMisAJour les nouvelles données
   * @return l'événement mis à jour
   * @throws IllegalArgumentException si l'événement n'existe pas
   */
  @Transactional
  public Evenement mettreAJourEvenement(UUID id, Evenement evenementMisAJour) {
    LOG.infof("Mise à jour événement ID: %s", id);

    Evenement evenementExistant = evenementRepository
        .findByIdOptional(id)
        .orElseThrow(
            () -> new NotFoundException("Événement non trouvé avec l'ID: " + id));

    // Vérifier les permissions
    if (!peutModifierEvenement(evenementExistant)) {
      throw new SecurityException("Vous n'avez pas les permissions pour modifier cet événement");
    }

    // Validation des nouvelles données
    validerEvenement(evenementMisAJour);

    // Mise à jour des champs
    evenementExistant.setTitre(evenementMisAJour.getTitre());
    evenementExistant.setDescription(evenementMisAJour.getDescription());
    evenementExistant.setDateDebut(evenementMisAJour.getDateDebut());
    evenementExistant.setDateFin(evenementMisAJour.getDateFin());
    evenementExistant.setLieu(evenementMisAJour.getLieu());
    evenementExistant.setAdresse(evenementMisAJour.getAdresse());
    evenementExistant.setTypeEvenement(evenementMisAJour.getTypeEvenement());
    evenementExistant.setCapaciteMax(evenementMisAJour.getCapaciteMax());
    evenementExistant.setPrix(evenementMisAJour.getPrix());
    evenementExistant.setInscriptionRequise(evenementMisAJour.getInscriptionRequise());
    evenementExistant.setDateLimiteInscription(evenementMisAJour.getDateLimiteInscription());
    evenementExistant.setInstructionsParticulieres(
        evenementMisAJour.getInstructionsParticulieres());
    evenementExistant.setContactOrganisateur(evenementMisAJour.getContactOrganisateur());
    evenementExistant.setMaterielRequis(evenementMisAJour.getMaterielRequis());
    evenementExistant.setVisiblePublic(evenementMisAJour.getVisiblePublic());
    if (evenementMisAJour.getStatut() != null) {
      evenementExistant.setStatut(evenementMisAJour.getStatut());
    }

    // Métadonnées de modification
    evenementExistant.setModifiePar(keycloakService.getCurrentUserEmail());

    evenementRepository.update(evenementExistant);

    LOG.infof("Événement mis à jour avec succès: ID=%s", id);
    // Initialiser les relations lazy pour éviter LazyInitializationException lors
    // de la sérialisation JSON
    Hibernate.initialize(evenementExistant.getOrganisation());
    Hibernate.initialize(evenementExistant.getOrganisateur());
    return evenementExistant;
  }

  /** Trouve un événement par ID */
  public Optional<Evenement> trouverParId(UUID id) {
    return evenementRepository.findByIdOptional(id);
  }

  /** Liste tous les événements actifs avec pagination */
  public List<Evenement> listerEvenementsActifs(Page page, Sort sort) {
    return evenementRepository.findAllActifs(page, sort);
  }

  /** Liste les événements à venir */
  public List<Evenement> listerEvenementsAVenir(Page page, Sort sort) {
    return evenementRepository.findEvenementsAVenir(page, sort);
  }

  /** Liste les événements publics */
  public List<Evenement> listerEvenementsPublics(Page page, Sort sort) {
    return evenementRepository.findEvenementsPublics(page, sort);
  }

  /** Recherche d'événements par terme */
  public List<Evenement> rechercherEvenements(String terme, Page page, Sort sort) {
    return evenementRepository.rechercheAvancee(
        terme, null, null, null, null, null, null, null, null, null, page, sort);
  }

  /** Liste les événements par type */
  public List<Evenement> listerParType(String type, Page page, Sort sort) {
    return evenementRepository.findByType(type, page, sort);
  }

  /**
   * Supprime logiquement un événement
   *
   * @param id l'UUID de l'événement à supprimer
   * @throws IllegalArgumentException si l'événement n'existe pas
   */
  @Transactional
  public void supprimerEvenement(UUID id) {
    LOG.infof("Suppression événement ID: %s", id);

    Evenement evenement = evenementRepository
        .findByIdOptional(id)
        .orElseThrow(
            () -> new NotFoundException("Événement non trouvé avec l'ID: " + id));

    // Vérifier les permissions
    if (!peutModifierEvenement(evenement)) {
      throw new SecurityException("Vous n'avez pas les permissions pour supprimer cet événement");
    }

    // Vérifier s'il y a des inscriptions
    if (evenement.getNombreInscrits() > 0) {
      throw new IllegalStateException("Impossible de supprimer un événement avec des inscriptions");
    }

    // Suppression logique
    evenement.setActif(false);
    evenement.setModifiePar(keycloakService.getCurrentUserEmail());

    evenementRepository.update(evenement);

    LOG.infof("Événement supprimé avec succès: ID=%s", id);
  }

  /**
   * Change le statut d'un événement
   *
   * @param id            l'UUID de l'événement
   * @param nouveauStatut le nouveau statut
   * @return l'événement mis à jour
   */
  @Transactional
  public Evenement changerStatut(UUID id, String nouveauStatut) {
    LOG.infof("Changement statut événement ID: %s vers %s", id, nouveauStatut);

    Evenement evenement = evenementRepository
        .findByIdOptional(id)
        .orElseThrow(
            () -> new NotFoundException("Événement non trouvé avec l'ID: " + id));

    // Vérifier les permissions
    if (!peutModifierEvenement(evenement)) {
      throw new SecurityException("Vous n'avez pas les permissions pour modifier cet événement");
    }

    // Valider le changement de statut
    validerChangementStatut(evenement.getStatut(), nouveauStatut);

    evenement.setStatut(nouveauStatut);
    evenement.setModifiePar(keycloakService.getCurrentUserEmail());

    evenementRepository.update(evenement);

    LOG.infof("Statut événement changé avec succès: ID=%s, Nouveau statut=%s", id, nouveauStatut);
    return evenement;
  }

  /**
   * Compte le nombre total d'événements
   *
   * @return le nombre total d'événements
   */
  public long countEvenements() {
    return evenementRepository.count();
  }

  /**
   * Compte le nombre d'événements actifs
   *
   * @return le nombre d'événements actifs
   */
  public long countEvenementsActifs() {
    return evenementRepository.countActifs();
  }

  /**
   * Obtient les statistiques des événements
   *
   * @return les statistiques sous forme de Map
   */
  public Map<String, Object> obtenirStatistiques() {
    Map<String, Long> statsBase = evenementRepository.getStatistiques();

    long total = statsBase.getOrDefault("total", 0L);
    long actifs = statsBase.getOrDefault("actifs", 0L);
    long aVenir = statsBase.getOrDefault("aVenir", 0L);
    long enCours = statsBase.getOrDefault("enCours", 0L);

    Map<String, Object> result = new java.util.HashMap<>();
    result.put("total", total);
    result.put("actifs", actifs);
    result.put("aVenir", aVenir);
    result.put("enCours", enCours);
    result.put("passes", statsBase.getOrDefault("passes", 0L));
    result.put("publics", statsBase.getOrDefault("publics", 0L));
    result.put("avecInscription", statsBase.getOrDefault("avecInscription", 0L));
    result.put("tauxActivite", total > 0 ? (actifs * 100.0 / total) : 0.0);
    result.put("tauxEvenementsAVenir", total > 0 ? (aVenir * 100.0 / total) : 0.0);
    result.put("tauxEvenementsEnCours", total > 0 ? (enCours * 100.0 / total) : 0.0);
    result.put("timestamp", LocalDateTime.now());
    return result;
  }

  // Méthodes privées de validation et permissions

  /** Valide les données d'un événement */
  private void validerEvenement(Evenement evenement) {
    if (evenement.getTitre() == null || evenement.getTitre().trim().isEmpty()) {
      throw new IllegalArgumentException("Le titre de l'événement est obligatoire");
    }

    if (evenement.getDateDebut() == null) {
      throw new IllegalArgumentException("La date de début est obligatoire");
    }

    if (evenement.getDateDebut().isBefore(LocalDateTime.now().minusHours(1))) {
      throw new IllegalArgumentException("La date de début ne peut pas être dans le passé");
    }

    if (evenement.getDateFin() != null
        && evenement.getDateFin().isBefore(evenement.getDateDebut())) {
      throw new IllegalArgumentException(
          "La date de fin ne peut pas être antérieure à la date de début");
    }

    if (evenement.getCapaciteMax() != null && evenement.getCapaciteMax() <= 0) {
      throw new IllegalArgumentException("La capacité maximale doit être positive");
    }

    if (evenement.getPrix() != null
        && evenement.getPrix().compareTo(java.math.BigDecimal.ZERO) < 0) {
      throw new IllegalArgumentException("Le prix ne peut pas être négatif");
    }
  }

  /** Valide un changement de statut */
  private void validerChangementStatut(
      String statutActuel, String nouveauStatut) {
    // Règles de transition simplifiées pour la version mobile
    if ("TERMINE".equals(statutActuel) || "ANNULE".equals(statutActuel)) {
      throw new IllegalArgumentException(
          "Impossible de changer le statut d'un événement terminé ou annulé");
    }
  }

  /** Vérifie les permissions de modification pour l'application mobile */
  private boolean peutModifierEvenement(Evenement evenement) {
    if (keycloakService.hasRole("ADMIN") || keycloakService.hasRole("ORGANISATEUR_EVENEMENT")) {
      return true;
    }

    String utilisateurActuel = keycloakService.getCurrentUserEmail();
    return utilisateurActuel != null && utilisateurActuel.equals(evenement.getCreePar());
  }

  /**
   * Indique si l'utilisateur connecté est inscrit à l'événement.
   * Utilisé par l'app mobile pour afficher le statut d'inscription sur la page détail.
   */
  public boolean isUserInscrit(UUID evenementId) {
    Evenement evenement = evenementRepository.findByIdOptional(evenementId).orElse(null);
    if (evenement == null) {
      return false;
    }
    String email = keycloakService.getCurrentUserEmail();
    if (email == null || email.isBlank()) {
      return false;
    }
    return membreRepository.findByEmail(email)
        .map(m -> evenement.isMemberInscrit(m.getId()))
        .orElse(false);
  }

  // === GESTION DES INSCRIPTIONS ===

  /**
   * Inscrit l'utilisateur connecté à un événement
   *
   * @param evenementId UUID de l'événement
   * @return L'inscription créée
   */
  @Transactional
  public InscriptionEvenement inscrireEvenement(UUID evenementId) {
    String email = keycloakService.getCurrentUserEmail();
    if (email == null || email.isBlank()) {
      throw new IllegalStateException("Utilisateur non authentifié");
    }

    Membre membre =
        membreRepository
            .findByEmail(email)
            .orElseThrow(() -> new NotFoundException("Membre non trouvé"));

    Evenement evenement =
        evenementRepository
            .findByIdOptional(evenementId)
            .orElseThrow(() -> new NotFoundException("Événement non trouvé"));

    // Vérifier si déjà inscrit
    Optional<InscriptionEvenement> existante =
        inscriptionRepository.findByMembreAndEvenement(membre.getId(), evenementId);
    if (existante.isPresent()) {
      throw new IllegalStateException("Vous êtes déjà inscrit à cet événement");
    }

    // Vérifier capacité
    if (evenement.getCapaciteMax() != null) {
      long nbInscrits = inscriptionRepository.countConfirmeesByEvenement(evenementId);
      if (nbInscrits >= evenement.getCapaciteMax()) {
        throw new IllegalStateException("L'événement est complet");
      }
    }

    InscriptionEvenement inscription =
        InscriptionEvenement.builder()
            .membre(membre)
            .evenement(evenement)
            .statut(InscriptionEvenement.StatutInscription.CONFIRMEE.name())
            .dateInscription(LocalDateTime.now())
            .build();

    inscriptionRepository.persist(inscription);
    LOG.infof(
        "Inscription créée: membre=%s, événement=%s", membre.getEmail(), evenement.getTitre());
    return inscription;
  }

  /**
   * Désinscrit l'utilisateur connecté d'un événement
   *
   * @param evenementId UUID de l'événement
   */
  @Transactional
  public void desinscrireEvenement(UUID evenementId) {
    String email = keycloakService.getCurrentUserEmail();
    if (email == null || email.isBlank()) {
      throw new IllegalStateException("Utilisateur non authentifié");
    }

    Membre membre =
        membreRepository
            .findByEmail(email)
            .orElseThrow(() -> new NotFoundException("Membre non trouvé"));

    InscriptionEvenement inscription =
        inscriptionRepository
            .findByMembreAndEvenement(membre.getId(), evenementId)
            .orElseThrow(() -> new NotFoundException("Inscription non trouvée"));

    inscriptionRepository.softDelete(inscription);
    LOG.infof("Désinscription: membre=%s, événement=%s", membre.getEmail(), evenementId);
  }

  /**
   * Liste les participants d'un événement
   *
   * @param evenementId UUID de l'événement
   * @return Liste des inscriptions confirmées
   */
  public List<InscriptionEvenement> getParticipants(UUID evenementId) {
    return inscriptionRepository.findConfirmeesByEvenement(evenementId);
  }

  /**
   * Liste les inscriptions de l'utilisateur connecté
   *
   * @return Liste des inscriptions du membre
   */
  public List<InscriptionEvenement> getMesInscriptions() {
    String email = keycloakService.getCurrentUserEmail();
    if (email == null || email.isBlank()) {
      throw new IllegalStateException("Utilisateur non authentifié");
    }

    Membre membre =
        membreRepository
            .findByEmail(email)
            .orElseThrow(() -> new NotFoundException("Membre non trouvé"));

    return inscriptionRepository.findByMembre(membre.getId());
  }

  // === GESTION DES FEEDBACKS ===

  /**
   * Soumet un feedback pour un événement
   *
   * @param evenementId UUID de l'événement
   * @param note Note de 1 à 5
   * @param commentaire Commentaire optionnel
   * @return Le feedback créé
   */
  @Transactional
  public FeedbackEvenement soumetteFeedback(
      UUID evenementId, Integer note, String commentaire) {
    String email = keycloakService.getCurrentUserEmail();
    if (email == null || email.isBlank()) {
      throw new IllegalStateException("Utilisateur non authentifié");
    }

    Membre membre =
        membreRepository
            .findByEmail(email)
            .orElseThrow(() -> new NotFoundException("Membre non trouvé"));

    Evenement evenement =
        evenementRepository
            .findByIdOptional(evenementId)
            .orElseThrow(() -> new NotFoundException("Événement non trouvé"));

    // Vérifier si déjà soumis
    Optional<FeedbackEvenement> existant =
        feedbackRepository.findByMembreAndEvenement(membre.getId(), evenementId);
    if (existant.isPresent()) {
      throw new IllegalStateException("Vous avez déjà soumis un feedback pour cet événement");
    }

    // Vérifier que le membre était inscrit
    boolean etaitInscrit =
        inscriptionRepository.isMembreInscrit(membre.getId(), evenementId);
    if (!etaitInscrit) {
      throw new IllegalStateException(
          "Seuls les participants peuvent donner un feedback");
    }

    // Vérifier que l'événement est terminé
    if (evenement.getDateFin() == null || evenement.getDateFin().isAfter(LocalDateTime.now())) {
      throw new IllegalStateException(
          "Vous ne pouvez donner un feedback qu'après la fin de l'événement");
    }

    FeedbackEvenement feedback =
        FeedbackEvenement.builder()
            .membre(membre)
            .evenement(evenement)
            .note(note)
            .commentaire(commentaire)
            .dateFeedback(LocalDateTime.now())
            .moderationStatut(FeedbackEvenement.ModerationStatut.PUBLIE.name())
            .build();

    feedbackRepository.persist(feedback);
    LOG.infof(
        "Feedback créé: membre=%s, événement=%s, note=%d",
        membre.getEmail(), evenement.getTitre(), note);
    return feedback;
  }

  /**
   * Liste les feedbacks d'un événement
   *
   * @param evenementId UUID de l'événement
   * @return Liste des feedbacks publiés
   */
  public List<FeedbackEvenement> getFeedbacks(UUID evenementId) {
    return feedbackRepository.findPubliesByEvenement(evenementId);
  }

  /**
   * Calcule les statistiques de feedback pour un événement
   *
   * @param evenementId UUID de l'événement
   * @return Map contenant noteMovenne et nombreFeedbacks
   */
  public Map<String, Object> getStatistiquesFeedback(UUID evenementId) {
    Double noteMoyenne = feedbackRepository.calculateAverageNote(evenementId);
    long nombreFeedbacks = feedbackRepository.countPubliesByEvenement(evenementId);

    return Map.of(
        "noteMoyenne", noteMoyenne,
        "nombreFeedbacks", nombreFeedbacks);
  }
}
