package dev.lions.repositories;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import jakarta.transaction.Transactional;
import jakarta.validation.constraints.NotNull;

import lombok.extern.slf4j.Slf4j;

import dev.lions.events.AnalyticsEvent;
import dev.lions.exceptions.RepositoryException;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * Repository gérant la persistance des événements analytiques.
 * Cette classe assure le stockage, la récupération et l'analyse
 * des événements analytiques de l'application.
 *
 * @author Lions Dev Team
 * @version 1.1
 */
@Slf4j
@ApplicationScoped
public class AnalyticsRepository extends BaseRepository<AnalyticsEvent, Long> {

  @PersistenceContext
  private EntityManager entityManager;

  /**
   * Recherche tous les événements pour une période donnée.
   *
   * @param startDate Date de début de la période
   * @param endDate Date de fin de la période
   * @return Liste des événements trouvés
   * @throws RepositoryException En cas d'erreur de requête
   */
  public List<AnalyticsEvent> findEventsByDateRange(@NotNull LocalDateTime startDate,
                                                    @NotNull LocalDateTime endDate) {
    log.debug("Recherche des événements entre {} et {}", startDate, endDate);

    try {
      TypedQuery<AnalyticsEvent> query = entityManager.createQuery(
          "SELECT e FROM AnalyticsEvent e " +
          "WHERE e.timestamp BETWEEN :startDate AND :endDate " +
          "ORDER BY e.timestamp DESC",
          AnalyticsEvent.class);

      query.setParameter("startDate", startDate);
      query.setParameter("endDate", endDate);

      List<AnalyticsEvent> events = query.getResultList();
      log.info("Trouvé {} événements pour la période demandée", events.size());

      return events;

    } catch (Exception e) {
      log.error("Erreur lors de la recherche des événements par période", e);
      throw new RepositoryException(
          "Erreur lors de la recherche des événements par période", e);
    }
  }

  /**
   * Recherche les événements analytiques par type pour une période donnée.
   *
   * @param eventType Type d'événement recherché
   * @param startDate Date de début
   * @param endDate Date de fin
   * @return Liste des événements trouvés
   */
  public List<AnalyticsEvent> findEventsByType(@NotNull String eventType,
                                               @NotNull LocalDateTime startDate,
                                               @NotNull LocalDateTime endDate) {
    log.debug("Recherche des événements de type {} entre {} et {}",
              eventType, startDate, endDate);

    try {
      TypedQuery<AnalyticsEvent> query = entityManager.createQuery(
          "SELECT e FROM AnalyticsEvent e " +
          "WHERE e.eventType = :eventType " +
          "AND e.timestamp BETWEEN :startDate AND :endDate " +
          "ORDER BY e.timestamp DESC",
          AnalyticsEvent.class);

      query.setParameter("eventType", eventType);
      query.setParameter("startDate", startDate);
      query.setParameter("endDate", endDate);

      return query.getResultList();

    } catch (Exception e) {
      throw new RepositoryException(
          "Erreur lors de la recherche des événements par type", e);
    }
  }

  /**
   * Enregistre un nouvel événement analytique.
   *
   * @param event Événement à sauvegarder
   * @return Événement sauvegardé
   */
  @Transactional
  public AnalyticsEvent save(AnalyticsEvent event) {
    log.debug("Sauvegarde d'un nouvel événement de type: {}", event.getEventType());

    try {
      if (event.getId() == null) {
        entityManager.persist(event);
        log.info("Nouvel événement créé avec l'ID: {}", event.getId());
      } else {
        event = entityManager.merge(event);
        log.info("Événement mis à jour avec l'ID: {}", event.getId());
      }
      return event;
    } catch (Exception e) {
      log.error("Erreur lors de la sauvegarde de l'événement", e);
      throw new RepositoryException("Erreur lors de la sauvegarde de l'événement", e);
    }
  }

  /**
   * Calcule le nombre d'événements par type pour une période donnée.
   *
   * @param startDate Date de début
   * @param endDate Date de fin
   * @return Map des compteurs par type d'événement
   */
  public Map<String, Long> getEventCountByType(@NotNull LocalDateTime startDate,
                                               @NotNull LocalDateTime endDate) {
    log.debug("Calcul du nombre d'événements entre {} et {}", startDate, endDate);

    try {
      List<Object[]> results = entityManager.createQuery(
                                                "SELECT e.eventType, COUNT(e) FROM AnalyticsEvent e " +
                                                "WHERE e.timestamp BETWEEN :startDate AND :endDate " +
                                                "GROUP BY e.eventType ORDER BY COUNT(e) DESC",
                                                Object[].class)
                                            .setParameter("startDate", startDate)
                                            .setParameter("endDate", endDate)
                                            .getResultList();

      return results.stream()
                    .collect(Collectors.toMap(
                        row -> (String) row[0],
                        row -> (Long) row[1]
                    ));

    } catch (Exception e) {
      throw new RepositoryException(
          "Erreur lors du calcul du nombre d'événements", e);
    }
  }

  /**
   * Recherche les événements associés à un contact spécifique.
   *
   * @param contactId Identifiant du contact
   * @return Liste des événements associés
   */
  public List<AnalyticsEvent> findEventsByContactId(@NotNull String contactId) {
    log.debug("Recherche des événements pour le contact {}", contactId);

    try {
      TypedQuery<AnalyticsEvent> query = entityManager.createQuery(
          "SELECT e FROM AnalyticsEvent e " +
          "WHERE e.contactId = :contactId " +
          "ORDER BY e.timestamp DESC",
          AnalyticsEvent.class);

      query.setParameter("contactId", contactId);
      return query.getResultList();

    } catch (Exception e) {
      throw new RepositoryException(
          "Erreur lors de la recherche des événements par contact", e);
    }
  }

  /**
   * Supprime les événements antérieurs à une date donnée.
   *
   * @param retentionDate Date limite de conservation
   * @return Nombre d'événements supprimés
   */
  @Transactional
  public int deleteEventsOlderThan(@NotNull LocalDateTime retentionDate) {
    log.info("Suppression des événements antérieurs à {}", retentionDate);

    try {
      int deletedCount = entityManager.createQuery(
                                          "DELETE FROM AnalyticsEvent e WHERE e.timestamp < :retentionDate")
                                      .setParameter("retentionDate", retentionDate)
                                      .executeUpdate();

      log.info("{} événements supprimés", deletedCount);
      return deletedCount;

    } catch (Exception e) {
      throw new RepositoryException(
          "Erreur lors de la suppression des anciens événements", e);
    }
  }

  /**
   * Recherche un événement par son identifiant.
   *
   * @param id Identifiant de l'événement
   * @return Optional contenant l'événement s'il existe
   */
  public Optional<AnalyticsEvent> findById(Long id) {
    log.debug("Recherche de l'événement avec l'ID: {}", id);
    try {
      AnalyticsEvent event = entityManager.find(AnalyticsEvent.class, id);
      return Optional.ofNullable(event);
    } catch (Exception e) {
      throw new RepositoryException(
          "Erreur lors de la recherche de l'événement par ID", e);
    }
  }

  /**
   * Calcule les statistiques d'événements par environnement.
   *
   * @param startDate Date de début
   * @param endDate Date de fin
   * @return Map des statistiques par environnement
   */
  public Map<String, Long> getEventStatistics(LocalDateTime startDate,
                                              LocalDateTime endDate) {
    log.debug("Calcul des statistiques entre {} et {}", startDate, endDate);

    try {
      List<Object[]> results = entityManager.createQuery(
                                                "SELECT e.environment, COUNT(e) FROM AnalyticsEvent e " +
                                                "WHERE e.timestamp BETWEEN :startDate AND :endDate " +
                                                "GROUP BY e.environment",
                                                Object[].class)
                                            .setParameter("startDate", startDate)
                                            .setParameter("endDate", endDate)
                                            .getResultList();

      return results.stream()
                    .collect(Collectors.toMap(
                        row -> (String) row[0],
                        row -> (Long) row[1]
                    ));

    } catch (Exception e) {
      throw new RepositoryException(
          "Erreur lors du calcul des statistiques", e);
    }
  }
}