package dev.lions.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

import dev.lions.exceptions.JsonConversionException;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import jakarta.validation.constraints.NotNull;

import lombok.extern.slf4j.Slf4j;

/**
 * Convertisseur JSON pour la persistance des données structurées.
 * Cette classe permet de convertir automatiquement des objets complexes
 * en JSON pour le stockage en base de données et vice-versa.
 *
 * @param <T> Type de l'objet à convertir.
 */
@Slf4j
@Converter
public class JsonConverter<T> implements AttributeConverter<T, String> {

  private static final ObjectMapper objectMapper = createObjectMapper();

  // Classe cible pour la conversion JSON -> Objet
  private final Class<T> targetClass;

  /**
   * Constructeur par défaut requis pour l'intégration CDI.
   */
  @SuppressWarnings("unchecked")
  public JsonConverter() {
    this.targetClass = (Class<T>) Object.class;
    // Constructeur par défaut requis pour Quarkus
    log.info("Initialisation du JsonConverter sans classe cible.");
  }

  /**
   * Constructeur initialisant la classe cible pour la conversion.
   *
   * @param targetClass Classe de l'objet à convertir.
   */
  public JsonConverter(Class<T> targetClass) {
    this.targetClass = targetClass;
    log.debug("JsonConverter initialisé pour la classe cible : {}", targetClass.getSimpleName());
  }

  /**
   * Configure l'ObjectMapper avec les paramètres appropriés.
   * - Indentation des JSON pour la lisibilité.
   * - Désactive les timestamps pour les dates.
   * - Ajoute le support pour les types de date/heure Java 8.
   *
   * @return Un ObjectMapper configuré.
   */
  private static ObjectMapper createObjectMapper() {
    ObjectMapper mapper = new ObjectMapper();
    mapper.enable(SerializationFeature.INDENT_OUTPUT); // JSON avec indentation
    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // Dates en ISO8601
    mapper.registerModule(new JavaTimeModule()); // Support des dates Java 8
    return mapper;
  }

  /**
   * Convertit un objet en chaîne JSON pour le stockage en base de données.
   *
   * @param attribute Objet à convertir.
   * @return Représentation JSON de l'objet.
   */
  @Override
  public String convertToDatabaseColumn(T attribute) {
    if (attribute == null) {
      return null;
    }

    try {
      String json = objectMapper.writeValueAsString(attribute);
      log.debug("Objet converti en JSON : {}", json);
      return json;
    } catch (JsonProcessingException e) {
      String errorMessage = String.format(
          "Erreur de conversion en JSON pour la classe %s : %s",
          attribute.getClass().getSimpleName(),
          e.getMessage()
      );
      log.error(errorMessage, e);
      throw new JsonConversionException(errorMessage, e);
    }
  }

  /**
   * Convertit une chaîne JSON en objet Java.
   *
   * @param dbData Chaîne JSON issue de la base de données.
   * @return L'objet reconstruit.
   */
  @Override
  public T convertToEntityAttribute(String dbData) {
    if (dbData == null || dbData.trim().isEmpty()) {
      return null;
    }

    try {
      T result = objectMapper.readValue(dbData, targetClass);
      log.debug("JSON converti en objet : {}", result);
      return result;
    } catch (JsonProcessingException e) {
      String errorMessage = String.format(
          "Erreur de conversion depuis JSON pour la classe %s : %s",
          targetClass.getSimpleName(),
          e.getMessage()
      );
      log.error(errorMessage, e);
      throw new JsonConversionException(errorMessage, e);
    }
  }

  /**
   * Convertit un objet en JSON de manière sûre.
   *
   * @param object Objet à convertir.
   * @return JSON ou null en cas d'erreur.
   */
  public static String toJsonSafe(@NotNull Object object) {
    try {
      return objectMapper.writeValueAsString(object);
    } catch (Exception e) {
      log.warn("Erreur lors de la conversion en JSON : {}", e.getMessage());
      return null;
    }
  }

  /**
   * Convertit une chaîne JSON en objet Java de manière sûre.
   *
   * @param json  Chaîne JSON.
   * @param clazz Classe cible.
   * @param <T>   Type de l'objet.
   * @return Objet ou null en cas d'erreur.
   */
  public static <T> T fromJsonSafe(String json, Class<T> clazz) {
    if (json == null || json.trim().isEmpty()) {
      return null;
    }

    try {
      return objectMapper.readValue(json, clazz);
    } catch (Exception e) {
      log.warn("Erreur lors de la conversion depuis JSON : {}", e.getMessage());
      return null;
    }
  }

  /**
   * Valide qu'une chaîne est un JSON valide.
   *
   * @param json Chaîne JSON à valider.
   * @return true si le JSON est valide, false sinon.
   */
  public static boolean isValidJson(String json) {
    if (json == null || json.trim().isEmpty()) {
      return false;
    }

    try {
      objectMapper.readTree(json);
      return true;
    } catch (Exception e) {
      log.warn("La chaîne n'est pas un JSON valide : {}", json);
      return false;
    }
  }
}
