package dev.lions.repositories;

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

import dev.lions.models.Project;
import dev.lions.exceptions.RepositoryException;

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

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Repository gérant la persistance des projets dans l'application.
 * Cette classe assure le stockage, la recherche et la gestion des projets
 * avec support pour le filtrage par tags, dates et statuts.
 */
@Slf4j
@ApplicationScoped
public class ProjectRepository extends BaseRepository<Project, String> {

  @PersistenceContext
  private EntityManager entityManager;

  /**
   * Recherche les projets par ensemble de tags.
   * Cette méthode permet de filtrer les projets qui contiennent au moins
   * un des tags spécifiés.
   *
   * @param tags Liste des tags à rechercher
   * @return Liste des projets correspondants
   */
  public List<Project> findByTags(@NotEmpty Set<String> tags) {
    log.debug("Recherche de projets par tags : {}", tags);

    try {
      TypedQuery<Project> query = entityManager.createQuery(
          "SELECT DISTINCT p FROM Project p " +
          "JOIN p.tags t " +
          "WHERE t IN :tags",
          Project.class
      );

      query.setParameter("tags", tags);
      return query.getResultList();

    } catch (Exception e) {
      throw new RepositoryException(
          "Erreur lors de la recherche des projets par tags", e);
    }
  }

  /**
   * Recherche les projets mis en avant.
   * Permet de récupérer les projets marqués comme "featured" pour
   * l'affichage en page d'accueil.
   *
   * @param featured État de mise en avant recherché
   * @return Liste des projets mis en avant
   */
  public List<Project> findByFeatured(boolean featured) {
    log.debug("Recherche des projets featured={}", featured);

    try {
      TypedQuery<Project> query = entityManager.createQuery(
          "SELECT p FROM Project p " +
          "WHERE p.featured = :featured " +
          "ORDER BY p.completionDate DESC",
          Project.class
      );

      query.setParameter("featured", featured);
      return query.getResultList();

    } catch (Exception e) {
      throw new RepositoryException(
          "Erreur lors de la recherche des projets mis en avant", e);
    }
  }

  /**
   * Recherche les projets complétés dans une période donnée.
   *
   * @param startDate Date de début
   * @param endDate Date de fin
   * @return Liste des projets pour la période
   */
  public List<Project> findByCompletionDateBetween(
      @NotNull LocalDateTime startDate,
      @NotNull LocalDateTime endDate) {

    log.debug("Recherche des projets complétés entre {} et {}",
              startDate, endDate);

    try {
      TypedQuery<Project> query = entityManager.createQuery(
          "SELECT p FROM Project p " +
          "WHERE p.completionDate BETWEEN :startDate AND :endDate " +
          "ORDER BY p.completionDate DESC",
          Project.class
      );

      query.setParameter("startDate", startDate);
      query.setParameter("endDate", endDate);
      return query.getResultList();

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

  /**
   * Récupère les projets les plus récents.
   *
   * @param limit Nombre maximum de projets à retourner
   * @return Liste limitée des projets les plus récents
   */
  public List<Project> findRecentProjects(int limit) {
    log.debug("Recherche des {} projets les plus récents", limit);

    try {
      TypedQuery<Project> query = entityManager.createQuery(
          "SELECT p FROM Project p " +
          "ORDER BY p.completionDate DESC",
          Project.class
      );

      query.setMaxResults(limit);
      return query.getResultList();

    } catch (Exception e) {
      throw new RepositoryException(
          "Erreur lors de la recherche des projets récents", e);
    }
  }

  /**
   * Recherche les projets par technologie utilisée.
   *
   * @param technology Technologie recherchée
   * @return Liste des projets utilisant cette technologie
   */
  public List<Project> findByTechnology(@NotNull String technology) {
    log.debug("Recherche des projets utilisant la technologie : {}",
              technology);

    try {
      TypedQuery<Project> query = entityManager.createQuery(
          "SELECT p FROM Project p " +
          "JOIN p.technologies t " +
          "WHERE LOWER(t) = LOWER(:technology)",
          Project.class
      );

      query.setParameter("technology", technology.toLowerCase());
      return query.getResultList();

    } catch (Exception e) {
      throw new RepositoryException(
          "Erreur lors de la recherche des projets par technologie", e);
    }
  }

  /**
   * Met à jour le statut "featured" d'un projet.
   *
   * @param projectId Identifiant du projet
   * @param featured Nouveau statut featured
   */
  @Transactional
  public void updateFeaturedStatus(@NotNull String projectId, boolean featured) {
    log.debug("Mise à jour du statut featured={} pour le projet {}",
              featured, projectId);

    try {
      Project project = findById(projectId)
          .orElseThrow(() -> new RepositoryException("Projet non trouvé"));

      project.setFeatured(featured);
      update(project);

      log.info("Statut featured mis à jour pour le projet {}", projectId);

    } catch (Exception e) {
      throw new RepositoryException(
          "Erreur lors de la mise à jour du statut featured", e);
    }
  }

  /**
   * Compte les projets par technologie.
   *
   * @return Nombre de projets par technologie
   */
  public Map<String, Long> countByTechnology() {
    log.debug("Comptage des projets par technologie");

    try {
      List<Object[]> results = entityManager.createQuery(
          "SELECT t, COUNT(p) FROM Project p " +
          "JOIN p.technologies t " +
          "GROUP BY t",
          Object[].class
      ).getResultList();

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

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