package dev.lions.events;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
import jakarta.persistence.Convert;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
import jakarta.validation.constraints.NotNull;

import dev.lions.utils.JsonConverter;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.io.Serializable;

/**
 * Entité représentant un événement analytique dans le système.
 * Cette classe permet de tracer et d'analyser les différentes actions et interactions
 * des utilisateurs avec l'application.
 *
 * @author Lions Dev Team
 * @version 1.1
 */
@Slf4j
@Data
@Entity
@Table(name = "analytics_events")
@Builder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class AnalyticsEvent implements Serializable {

  private static final long serialVersionUID = 1L;

  /**
   * Identifiant unique de l'événement
   */
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  /**
   * Type d'événement analytique (ex: PAGE_VIEW, USER_ACTION, etc.)
   */
  @NotNull(message = "Le type d'événement est obligatoire")
  @Column(name = "event_type", nullable = false)
  private String eventType;

  /**
   * Identifiant de l'utilisateur associé à l'événement
   */
  @Column(name = "user_id")
  private String userId;

  /**
   * Identifiant du contact associé à l'événement
   */
  @Column(name = "contact_id")
  private String contactId;

  /**
   * Source de l'événement (ex: WEB, MOBILE, API)
   */
  @Column(name = "source")
  private String source;

  /**
   * Propriétés additionnelles de l'événement stockées au format JSON
   */
  @Convert(converter = JsonConverter.class)
  @Column(name = "properties", columnDefinition = "jsonb")
  @Builder.Default
  private Map<String, Object> properties = new HashMap<>();

  /**
   * Date et heure de l'événement
   */
  @Temporal(TemporalType.TIMESTAMP)
  @Column(name = "timestamp", nullable = false)
  @Builder.Default
  private LocalDateTime timestamp = LocalDateTime.now();

  /**
   * Environnement dans lequel l'événement s'est produit
   */
  @Column(name = "environment", nullable = false)
  @Builder.Default
  private String environment = System.getProperty("app.environment", "production");

  /**
   * Type d'événements analytiques supportés
   */
  public enum EventType {
    PAGE_VIEW,
    USER_ACTION,
    SYSTEM_EVENT,
    ERROR,
    PERFORMANCE,
    SECURITY
  }

  /**
   * Crée une copie de l'événement avec des propriétés enrichies
   *
   * @param additionalProps Propriétés supplémentaires à ajouter
   * @return Nouvelle instance d'AnalyticsEvent avec les propriétés enrichies
   */
  public AnalyticsEvent withAdditionalProperties(Map<String, Object> additionalProps) {
    if (additionalProps == null || additionalProps.isEmpty()) {
      log.debug("Aucune propriété additionnelle à ajouter");
      return this;
    }

    log.debug("Ajout de {} propriétés additionnelles à l'événement", additionalProps.size());

    Map<String, Object> newProps = new HashMap<>(this.properties);
    newProps.putAll(additionalProps);

    return this.toBuilder()
               .properties(newProps)
               .build();
  }

  /**
   * Ajoute une propriété unique à l'événement
   *
   * @param key Clé de la propriété
   * @param value Valeur de la propriété
   * @return L'instance actuelle pour chaînage
   */
  public AnalyticsEvent addProperty(String key, Object value) {
    if (key == null || key.trim().isEmpty()) {
      log.warn("Tentative d'ajout d'une propriété avec une clé nulle ou vide");
      return this;
    }

    log.debug("Ajout de la propriété '{}' à l'événement", key);
    this.properties.put(key, value);
    return this;
  }

  /**
   * Enrichit l'événement avec des métadonnées standard
   *
   * @return L'instance actuelle pour chaînage
   */
  public AnalyticsEvent enrichWithMetadata() {
    log.debug("Enrichissement de l'événement {} avec les métadonnées standard", this.id);

    this.addProperty("timestamp_ms", System.currentTimeMillis())
        .addProperty("java_version", System.getProperty("java.version"))
        .addProperty("os_name", System.getProperty("os.name"))
        .addProperty("app_version", System.getProperty("app.version"))
        .addProperty("node_id", System.getProperty("node.id"))
        .addProperty("thread_name", Thread.currentThread().getName());

    return this;
  }

  /**
   * Vérifie si l'événement est valide pour le traitement
   */
  public boolean isValid() {
    boolean isValid = this.eventType != null &&
                      !this.eventType.trim().isEmpty() &&
                      this.timestamp != null;

    if (!isValid) {
      log.warn("Événement invalide détecté: type={}, timestamp={}",
               this.eventType, this.timestamp);
    }

    return isValid;
  }

  /**
   * Marque l'événement comme ayant été traité
   *
   * @param processingDetails Détails du traitement
   * @return L'instance actuelle pour chaînage
   */
  public AnalyticsEvent markAsProcessed(Map<String, Object> processingDetails) {
    log.debug("Marquage de l'événement {} comme traité", this.id);

    return this.addProperty("processed_at", LocalDateTime.now().toString())
               .addProperty("processing_details", processingDetails);
  }

  /**
   * Suit la soumission d'un contact
   */
  public void trackContactSubmission() {
    log.info("Suivi de la soumission pour l'événement: {}", this);
    this.addProperty("submission_tracked", true)
        .addProperty("submission_time", LocalDateTime.now().toString());
  }

  @Override
  public String toString() {
    return String.format(
        "AnalyticsEvent[id=%d, type=%s, userId=%s, timestamp=%s, env=%s]",
        id, eventType, userId, timestamp, environment
    );
  }

  /**
   * Crée une copie de l'événement
   *
   * @return Nouvelle instance avec les mêmes données
   */
  public AnalyticsEvent copy() {
    return this.toBuilder().build();
  }
}