package dev.lions.services;

import dev.lions.events.ProjectUpdateEvent;
import dev.lions.exceptions.BusinessException;
import dev.lions.models.Project;
import dev.lions.models.Testimonial;
import dev.lions.repositories.ProjectRepository;
import dev.lions.utils.ImageProcessor;
import dev.lions.config.ApplicationConfig;

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

import java.util.Set;
import lombok.Builder;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Optional;
import java.time.LocalDateTime;

/**
 * Service gérant la logique métier des projets.
 * Cette classe assure la gestion complète du cycle de vie des projets,
 * incluant leur création, mise à jour, recherche et validation.
 */
@Slf4j
@ApplicationScoped
@Builder
public class ProjectService {

  @Inject
  ProjectRepository projectRepository;

  @Inject
  ImageProcessor imageProcessor;

  @Inject
  ApplicationConfig config;

  @Inject
  Event<ProjectUpdateEvent> projectUpdateEvent;

  /**
   * Crée un nouveau projet avec son image associée.
   *
   * @param project Données du projet
   * @param imageData Image du projet en bytes
   * @return Projet créé
   */
  @Transactional
  public Project createProject(@Valid @NotNull Project project, byte[] imageData) {
    log.info("Création d'un nouveau projet : {}", project.getTitle());

    try {
      validateProject(project);
      processProjectImage(project, imageData);

      Project savedProject = projectRepository.save(project);
      notifyProjectCreation(savedProject);

      log.info("Projet créé avec succès - ID: {}", savedProject.getId());
      return savedProject;

    } catch (BusinessException be) {
      log.warn("Validation échouée pour le projet", be);
      throw be;
    } catch (Exception e) {
      log.error("Erreur lors de la création du projet", e);
      throw new BusinessException("Impossible de créer le projet", e);
    }
  }

  /**
   * Met à jour un projet existant.
   *
   * @param project Projet à mettre à jour
   * @param imageData Nouvelle image optionnelle
   * @return Projet mis à jour
   */
  @Transactional
  public Project updateProject(@Valid @NotNull Project project, byte[] imageData) {
    log.info("Mise à jour du projet : {}", project.getId());

    try {
      validateProject(project);

      if (imageData != null) {
        processProjectImage(project, imageData);
      }

      Project updatedProject = projectRepository.update(project);
      notifyProjectUpdate(updatedProject);

      log.info("Projet mis à jour avec succès - ID: {}", updatedProject.getId());
      return updatedProject;

    } catch (Exception e) {
      log.error("Erreur lors de la mise à jour du projet", e);
      throw new BusinessException("Impossible de mettre à jour le projet", e);
    }
  }

  /**
   * Traite et stocke l'image du projet.
   */
  private void processProjectImage(Project project, byte[] imageData) {
    if (imageData == null || imageData.length == 0) {
      throw new BusinessException("L'image du projet est requise");
    }

    String imageUrl = imageProcessor.processAndStoreProjectImage(
        imageData,
        project.getTitle(),
        config.getImageStoragePath()
    );

    project.setImageUrl(imageUrl);
  }

  /**
   * Récupère un projet par son identifiant.
   */
  public Optional<Project> findById(@NotNull String projectId) {
    log.debug("Recherche du projet : {}", projectId);
    return projectRepository.findById(projectId);
  }

  /**
   * Vérifie l'existence d'un projet.
   */
  public boolean existsById(@NotNull String projectId) {
    return projectRepository.existsById(projectId);
  }

  /**
   * Récupère les projets filtrés par tag.
   */
  public List<Project> getFilteredProjects(String filter) {
    log.debug("Filtrage des projets avec le critère : {}", filter);

    if ("all".equalsIgnoreCase(filter)) {
      return projectRepository.findAll();
    }

    return projectRepository.findByTags(Set.of(filter.toLowerCase()));
  }

  /**
   * Récupère les projets mis en avant.
   */
  public List<Project> getFeaturedProjects() {
    log.debug("Récupération des projets mis en avant");
    return projectRepository.findByFeatured(true);
  }

  /**
   * Récupère les projets récents.
   */
  public List<Project> getRecentProjects(int limit) {
    log.debug("Récupération des {} projets les plus récents", limit);
    return projectRepository.findRecentProjects(limit);
  }

  /**
   * Récupère le nombre total de projets.
   */
  public long getProjectCount() {
    log.debug("Récupération du nombre total de projets");
    return projectRepository.count();
  }


  /**
   * Récupère les témoignages mis en avant.
   */
  public List<Testimonial> getFeaturedTestimonials() {
    log.debug("Récupération des témoignages mis en avant");

    return projectRepository.findByFeatured(true).stream()
                            .filter(project -> !project.getTestimonials().isEmpty())
                            .map(this::createTestimonialFromProject)
                            .limit(3)
                            .toList();
  }

  /**
   * Valide les données d'un projet.
   */
  private void validateProject(Project project) {
    if (project.getTitle() == null || project.getTitle().trim().isEmpty()) {
      throw new BusinessException("Le titre du projet est requis");
    }
    if (project.getDescription() == null || project.getDescription().trim().isEmpty()) {
      throw new BusinessException("La description du projet est requise");
    }
    if (project.getShortDescription() == null ||
        project.getShortDescription().trim().isEmpty()) {
      throw new BusinessException("La description courte du projet est requise");
    }
  }

  /**
   * Crée un témoignage à partir d'un projet.
   */
  private Testimonial createTestimonialFromProject(Project project) {
    return Testimonial.builder()
                      .clientName(project.getClientName())
                      .content(project.getTestimonials().get(0))
                      .projectTitle(project.getTitle())
                      .date(project.getCompletionDate())
                      .build();

  }

  /**
   * Notifie la création d'un projet.
   */
  private void notifyProjectCreation(Project project) {
    projectUpdateEvent.fire(new ProjectUpdateEvent(
        project.getId(),
        "CREATE",
        LocalDateTime.now()
    ));
  }

  /**
   * Notifie la mise à jour d'un projet.
   */
  private void notifyProjectUpdate(Project project) {
    projectUpdateEvent.fire(new ProjectUpdateEvent(
        project.getId(),
        "UPDATE",
        LocalDateTime.now()
    ));
  }
}