package dev.lions.repositories;

import dev.lions.models.Notification;
import dev.lions.models.NotificationStatus;
import dev.lions.models.NotificationType;
import dev.lions.exceptions.RepositoryException;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.EntityManager;
import jakarta.persistence.NoResultException;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.TypedQuery;
import jakarta.transaction.Transactional;
import jakarta.validation.constraints.NotNull;

import java.util.Map;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

/**
 * Repository gérant la persistance des notifications système.
 * Cette classe assure le stockage et la récupération des notifications
 * avec support pour le filtrage par statut, type et période.
 */
@Slf4j
@ApplicationScoped
public class NotificationRepository extends BaseRepository<Notification, Long> {

  @PersistenceContext
  private EntityManager entityManager;

  /**
   * Récupère les notifications non lues.
   * Cette méthode retourne les notifications qui nécessitent
   * l'attention des utilisateurs.
   *
   * @return Liste des notifications non lues
   */
  public List<Notification> findUnreadNotifications() {
    log.debug("Recherche des notifications non lues");

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

      query.setParameter("status", NotificationStatus.UNREAD);
      return query.getResultList();

    } catch (Exception e) {
      throw new RepositoryException(
          "Erreur lors de la recherche des notifications non lues", e);
    }
  }

  /**
   * Recherche les notifications pour une période donnée.
   *
   * @param start Date de début
   * @param end Date de fin
   * @return Liste des notifications pour la période
   */
  public List<Notification> findNotificationsByDateRange(
      @NotNull LocalDateTime start,
      @NotNull LocalDateTime end) {

    log.debug("Recherche des notifications entre {} et {}", start, end);

    try {
      TypedQuery<Notification> query = entityManager.createQuery(
          "SELECT n FROM Notification n " +
          "WHERE n.timestamp BETWEEN :start AND :end " +
          "ORDER BY n.timestamp DESC",
          Notification.class
      );

      query.setParameter("start", start);
      query.setParameter("end", end);
      return query.getResultList();

    } catch (Exception e) {
      throw new RepositoryException(
          "Erreur lors de la recherche des notifications par période", e);
    }
  }

  /**
   * Récupère les notifications critiques non lues.
   * Ces notifications représentent des alertes importantes nécessitant
   * une attention immédiate.
   *
   * @return Liste des notifications critiques
   */
  public List<Notification> findCriticalNotifications() {
    log.debug("Recherche des notifications critiques");

    try {
      TypedQuery<Notification> query = entityManager.createQuery(
          "SELECT n FROM Notification n " +
          "WHERE n.type.isCritical = true " +
          "AND n.status = :status " +
          "ORDER BY n.timestamp DESC",
          Notification.class
      );

      query.setParameter("status", NotificationStatus.UNREAD);
      return query.getResultList();

    } catch (Exception e) {
      throw new RepositoryException(
          "Erreur lors de la recherche des notifications critiques", e);
    }
  }

  /**
   * Marque une notification comme lue.
   *
   * @param notificationId Identifiant de la notification
   */
  @Transactional
  public void markAsRead(@NotNull Long notificationId) {
    log.debug("Marquage de la notification {} comme lue", notificationId);

    try {
      Notification notification = findById(notificationId)
          .orElseThrow(() -> new NoResultException("Notification non trouvée"));

      notification.setStatus(NotificationStatus.READ);
      notification.setReadTimestamp(LocalDateTime.now());
      update(notification);

      log.info("Notification {} marquée comme lue", notificationId);

    } catch (NoResultException e) {
      log.warn("Tentative de marquage d'une notification inexistante : {}",
               notificationId);
      throw new RepositoryException("Notification non trouvée", e);

    } catch (Exception e) {
      throw new RepositoryException(
          "Erreur lors du marquage de la notification comme lue", e);
    }
  }

  /**
   * Marque toutes les notifications non lues comme lues.
   */
  @Transactional
  public void markAllAsRead() {
    log.debug("Marquage de toutes les notifications comme lues");

    try {
      int updatedCount = entityManager.createQuery(
                                          "UPDATE Notification n " +
                                          "SET n.status = :newStatus, n.readTimestamp = :timestamp " +
                                          "WHERE n.status = :oldStatus"
                                      )
                                      .setParameter("newStatus", NotificationStatus.READ)
                                      .setParameter("timestamp", LocalDateTime.now())
                                      .setParameter("oldStatus", NotificationStatus.UNREAD)
                                      .executeUpdate();

      log.info("{} notifications marquées comme lues", updatedCount);

    } catch (Exception e) {
      throw new RepositoryException(
          "Erreur lors du marquage de toutes les notifications comme lues", e);
    }
  }

  /**
   * Compte les notifications par type pour une période donnée.
   *
   * @param start Date de début
   * @param end Date de fin
   * @return Nombre de notifications par type
   */
  public Map<NotificationType, Long> countByType(
      @NotNull LocalDateTime start,
      @NotNull LocalDateTime end) {

    log.debug("Comptage des notifications par type entre {} et {}", start, end);

    try {
      List<Object[]> results = entityManager.createQuery(
                                                "SELECT n.type, COUNT(n) FROM Notification n " +
                                                "WHERE n.timestamp BETWEEN :start AND :end " +
                                                "GROUP BY n.type",
                                                Object[].class
                                            )
                                            .setParameter("start", start)
                                            .setParameter("end", end)
                                            .getResultList();

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

    } catch (Exception e) {
      throw new RepositoryException(
          "Erreur lors du comptage des notifications par type", e);
    }
  }

  /**
   * Supprime les notifications antérieures à une date donnée.
   *
   * @param retentionDate Date de conservation des notifications
   * @return Nombre de notifications supprimées
   */
  @Transactional
  public int deleteEventsOlderThan(@NotNull LocalDateTime retentionDate) {
    log.info("Suppression des notifications antérieures à {}", retentionDate);

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

      log.info("{} notifications supprimées", deletedCount);
      return deletedCount;

    } catch (Exception e) {
      throw new RepositoryException(
          "Erreur lors de la suppression des anciennes notifications", e);
    }
  }
}