package dev.lions.repositories;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;

import lombok.extern.slf4j.Slf4j;

import dev.lions.exceptions.RepositoryException;

import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.Optional;

/**
 * Repository générique fournissant les opérations CRUD de base.
 * Cette classe abstract implémente les fonctionnalités communes à tous les repositories
 * de l'application en assurant une gestion cohérente des entités.
 *
 * @param <T> Type de l'entité
 * @param <ID> Type de l'identifiant de l'entité
 */
@Slf4j
public abstract class BaseRepository<T, ID> {

  @PersistenceContext
  protected EntityManager entityManager;

  private final Class<T> entityClass;

  /**
   * Constructeur initialisant la classe d'entité via réflexion.
   */
  @SuppressWarnings("unchecked")
  public BaseRepository() {
    Class<?> currentClass = getClass();
    while (!(currentClass.getGenericSuperclass() instanceof ParameterizedType)) {
      currentClass = currentClass.getSuperclass();
    }
    this.entityClass = (Class<T>) ((ParameterizedType) currentClass.getGenericSuperclass())
        .getActualTypeArguments()[0];

    log.debug("Repository initialisé pour l'entité : {}", entityClass.getSimpleName());
  }

  /**
   * Persiste une nouvelle entité.
   *
   * @param entity Entité à persister
   * @return Entité persistée
   */
  @Transactional
  public T save(T entity) {
    try {
      log.debug("Sauvegarde d'une nouvelle entité : {}", entityClass.getSimpleName());
      entityManager.persist(entity);
      entityManager.flush();
      log.info("Entité sauvegardée avec succès : {}", entity);
      return entity;
    } catch (Exception e) {
      throw new RepositoryException("Erreur lors de la sauvegarde de l'entité", e);
    }
  }

  /**
   * Met à jour une entité existante.
   *
   * @param entity Entité à mettre à jour
   * @return Entité mise à jour
   */
  @Transactional
  public T update(T entity) {
    try {
      log.debug("Mise à jour d'une entité : {}", entityClass.getSimpleName());
      T updatedEntity = entityManager.merge(entity);
      entityManager.flush();
      log.info("Entité mise à jour avec succès : {}", entity);
      return updatedEntity;
    } catch (Exception e) {
      throw new RepositoryException("Erreur lors de la mise à jour de l'entité", e);
    }
  }

  /**
   * Recherche une entité par son identifiant.
   *
   * @param id Identifiant de l'entité
   * @return Entité trouvée (Optional)
   */
  public Optional<T> findById(ID id) {
    try {
      log.debug("Recherche de l'entité {} avec l'id : {}",
                entityClass.getSimpleName(), id);
      return Optional.ofNullable(entityManager.find(entityClass, id));
    } catch (Exception e) {
      throw new RepositoryException("Erreur lors de la recherche de l'entité", e);
    }
  }

  /**
   * Récupère toutes les entités.
   *
   * @return Liste des entités
   */
  public List<T> findAll() {
    try {
      log.debug("Récupération de toutes les entités : {}",
                entityClass.getSimpleName());
      CriteriaBuilder cb = entityManager.getCriteriaBuilder();
      CriteriaQuery<T> cq = cb.createQuery(entityClass);
      cq.from(entityClass);
      TypedQuery<T> query = entityManager.createQuery(cq);
      return query.getResultList();
    } catch (Exception e) {
      throw new RepositoryException("Erreur lors de la récupération des entités", e);
    }
  }

  /**
   * Supprime une entité.
   *
   * @param entity Entité à supprimer
   */
  @Transactional
  public void delete(T entity) {
    try {
      log.debug("Suppression de l'entité : {}", entity);
      if (!entityManager.contains(entity)) {
        entity = entityManager.merge(entity);
      }
      entityManager.remove(entity);
      entityManager.flush();
      log.info("Entité supprimée avec succès : {}", entity);
    } catch (Exception e) {
      throw new RepositoryException("Erreur lors de la suppression de l'entité", e);
    }
  }

  /**
   * Supprime une entité par son identifiant.
   *
   * @param id Identifiant de l'entité à supprimer
   */
  @Transactional
  public void deleteById(ID id) {
    try {
      log.debug("Suppression de l'entité {} avec l'id : {}",
                entityClass.getSimpleName(), id);
      findById(id).ifPresent(this::delete);
    } catch (Exception e) {
      throw new RepositoryException("Erreur lors de la suppression de l'entité", e);
    }
  }

  /**
   * Vérifie l'existence d'une entité par son identifiant.
   *
   * @param id Identifiant à vérifier
   * @return true si l'entité existe
   */
  public boolean existsById(ID id) {
    try {
      log.debug("Vérification de l'existence de l'entité {} avec l'id : {}",
                entityClass.getSimpleName(), id);
      return findById(id).isPresent();
    } catch (Exception e) {
      throw new RepositoryException(
          "Erreur lors de la vérification de l'existence de l'entité", e);
    }
  }

  /**
   * Compte le nombre total d'entités.
   *
   * @return Nombre total d'entités
   */
  public long count() {
    try {
      log.debug("Comptage des entités : {}", entityClass.getSimpleName());
      CriteriaBuilder cb = entityManager.getCriteriaBuilder();
      CriteriaQuery<Long> cq = cb.createQuery(Long.class);
      cq.select(cb.count(cq.from(entityClass)));
      return entityManager.createQuery(cq).getSingleResult();
    } catch (Exception e) {
      throw new RepositoryException(
          "Erreur lors du comptage des entités", e);
    }
  }
}