package dev.lions.services;

import dev.lions.events.AnalyticsEvent;
import dev.lions.events.AnalyticsEventPublisher;
import dev.lions.exceptions.AnalyticsException;
import dev.lions.repositories.AnalyticsRepository;
import dev.lions.utils.MetricsCollector;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;

import lombok.extern.slf4j.Slf4j;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * Service responsable du traitement et de l'enregistrement des événements analytiques.
 * Gère l'enrichissement des données, leur persistance et leur publication.
 *
 * @author Lions Dev Team
 * @version 1.1
 */
@Slf4j
@ApplicationScoped
public class AnalyticsService {

  private static final int MAX_RETRY_ATTEMPTS = 3;
  private static final long RETRY_DELAY_MS = 1000;
  private static final int BATCH_SIZE = 100;

  @Inject
  AnalyticsRepository analyticsRepository;

  @Inject
  AnalyticsEventPublisher eventPublisher;

  @Inject
  private MetricsCollector metricsCollector;

  /**
   * Traite et enregistre un événement analytique.
   * L'événement est enrichi avec des métadonnées contextuelles avant son traitement.
   *
   * @param event L'événement analytique à traiter
   * @return L'événement traité et enrichi
   * @throws AnalyticsException Si une erreur survient pendant le traitement
   */
  @Transactional
  public AnalyticsEvent processEvent(@NotNull @Valid AnalyticsEvent event) {
    log.debug("Début du traitement de l'événement analytique de type: {}", event.getEventType());

    try {
      // Enrichissement et validation
      AnalyticsEvent enrichedEvent = enrichEventData(event);
      validateEvent(enrichedEvent);

      // Persistance avec gestion des reprises
      AnalyticsEvent savedEvent = persistEventWithRetry(enrichedEvent);

      // Publication asynchrone
      publishEventAsync(savedEvent);

      // Collecte des métriques
      metricsCollector.incrementEventCounter(event.getEventType());

      log.info("Événement analytique traité avec succès - ID: {}, Type: {}",
               savedEvent.getId(), savedEvent.getEventType());

      return savedEvent;

    } catch (Exception e) {
      log.error("Erreur lors du traitement de l'événement analytique", e);
      throw new AnalyticsException("Impossible de traiter l'événement analytique", e);
    }
  }

  /**
   * Traite un lot d'événements analytiques de manière optimisée.
   *
   * @param events Liste des événements à traiter
   * @return Liste des événements traités
   */
  @Transactional
  public List<AnalyticsEvent> processBatchEvents(List<AnalyticsEvent> events) {
    log.debug("Traitement par lot de {} événements analytiques", events.size());

    return events.stream()
                 .map(this::processEvent)
                 .collect(java.util.stream.Collectors.toList());
  }

  /**
   * Récupère les événements analytiques pour une période donnée.
   *
   * @param startDate Date de début
   * @param endDate Date de fin
   * @return Liste des événements pour la période
   */
  public List<dev.lions.events.AnalyticsEvent> getEventsByDateRange(LocalDateTime startDate, LocalDateTime endDate) {
    log.debug("Recherche des événements entre {} et {}", startDate, endDate);
    return analyticsRepository.findEventsByDateRange(startDate, endDate);
  }

  /**
   * Enrichit l'événement avec des données contextuelles supplémentaires.
   *
   * @param event L'événement à enrichir
   * @return L'événement enrichi
   */
  private AnalyticsEvent enrichEventData(AnalyticsEvent event) {
    log.trace("Enrichissement des données de l'événement: {}", event.getId());

    Map<String, Object> contextData = Map.of(
        "processTimestamp", LocalDateTime.now(),
        "processingNode", System.getProperty("jboss.node.name"),
        "applicationVersion", System.getProperty("app.version")
    );

    return event.withAdditionalProperties(contextData)
                .enrichWithMetadata();
  }

  /**
   * Valide l'intégrité et la cohérence d'un événement.
   *
   * @param event L'événement à valider
   * @throws AnalyticsException Si l'événement est invalide
   */
  private void validateEvent(AnalyticsEvent event) {
    log.trace("Validation de l'événement analytique");

    if (!event.isValid()) {
      log.warn("Validation échouée pour l'événement: {}", event);
      throw new AnalyticsException("L'événement analytique est invalide");
    }
  }

  /**
   * Persiste un événement avec mécanisme de reprise en cas d'échec.
   *
   * @param event L'événement à persister
   * @return L'événement persisté
   * @throws AnalyticsException Si la persistance échoue après les reprises
   */
  private AnalyticsEvent persistEventWithRetry(AnalyticsEvent event) {
    Exception lastException = null;

    for (int attempt = 1; attempt <= MAX_RETRY_ATTEMPTS; attempt++) {
      try {
        return analyticsRepository.save(event);
      } catch (Exception e) {
        lastException = e;
        log.warn("Échec de la persistance (tentative {}/{}): {}",
                 attempt, MAX_RETRY_ATTEMPTS, e.getMessage());

        if (attempt < MAX_RETRY_ATTEMPTS) {
          try {
            TimeUnit.MILLISECONDS.sleep(RETRY_DELAY_MS * attempt);
          } catch (InterruptedException ie) {
            Thread.currentThread().interrupt();
            throw new AnalyticsException("Interruption pendant la reprise", ie);
          }
        }
      }
    }

    throw new AnalyticsException("Échec de la persistance après " + MAX_RETRY_ATTEMPTS +
                                 " tentatives", lastException);
  }

  /**
   * Publie un événement de manière asynchrone.
   *
   * @param event L'événement à publier
   */
  private void publishEventAsync(AnalyticsEvent event) {
    CompletableFuture.runAsync(() -> {
      try {
        eventPublisher.publish(event);
        log.debug("Publication asynchrone réussie pour l'événement: {}", event.getId());
      } catch (Exception e) {
        log.error("Erreur lors de la publication asynchrone de l'événement: {}",
                  event.getId(), e);
      }
    });
  }

  /**
   * Nettoie les anciens événements selon la politique de rétention.
   *
   * @param retentionDate Date limite de conservation
   * @return Nombre d'événements supprimés
   */
  @Transactional
  public int cleanupOldEvents(LocalDateTime retentionDate) {
    log.info("Nettoyage des événements antérieurs à {}", retentionDate);

    try {
      int deletedCount = analyticsRepository.deleteEventsOlderThan(retentionDate);
      log.info("{} événements anciens ont été supprimés", deletedCount);
      return deletedCount;
    } catch (Exception e) {
      log.error("Erreur lors du nettoyage des anciens événements", e);
      throw new AnalyticsException("Échec du nettoyage des événements", e);
    }
  }
}