package dev.lions.components;

import jakarta.annotation.PostConstruct;
import jakarta.faces.model.SelectItem;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Named;
import jakarta.faces.context.FacesContext;
import jakarta.faces.application.FacesMessage;
import jakarta.validation.constraints.NotNull;

import java.io.Serial;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import dev.lions.utils.FilterOperator;
import dev.lions.utils.FilterCriteria;
import dev.lions.exceptions.FilterException;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.*;

/**
 * Composant de gestion des filtres dynamiques.
 * Permet la création, la validation et l'application de filtres
 * pour des tableaux de données.
 *
 * <p>Fonctionnalités incluses :
 * <ul>
 *   <li>Ajout de filtres avec validation des entrées</li>
 *   <li>Suppression de filtres</li>
 *   <li>Application des filtres sur des listes d'objets</li>
 *   <li>Interface utilisateur avec feedback via les messages JSF</li>
 * </ul>
 *
 * @author Lions Dev Team
 * @version 2.2
 */
@Slf4j
@Named
@ViewScoped
public class FilterComponent implements Serializable {

  @Serial
  private static final long serialVersionUID = 1L;
  private static final int MAX_FILTERS = 10;

  @Getter @Setter
  private List<FilterCriteria> criteria = new ArrayList<>();

  @Getter @Setter
  private String selectedField;

  @Getter @Setter
  private FilterOperator selectedOperator;

  @Getter @Setter
  private String filterValue;

  @Getter
  private List<SelectItem> availableFields;

  @Getter
  private List<SelectItem> availableOperators;

  private final Map<String, String> fieldConfigurations = new LinkedHashMap<>();

  @PostConstruct
  public void init() {
    log.debug("Initialisation du composant de filtrage");
    initializeFieldConfigurations();
    initializeAvailableFields();
    initializeAvailableOperators();
  }

  /**
   * Initialise la configuration des champs disponibles.
   */
  private void initializeFieldConfigurations() {
    fieldConfigurations.put("name", "Nom");
    fieldConfigurations.put("date", "Date");
    fieldConfigurations.put("status", "Statut");
    fieldConfigurations.put("category", "Catégorie");
    fieldConfigurations.put("price", "Prix");
    log.info("Champs disponibles pour le filtrage : {}", fieldConfigurations.keySet());
  }

  /**
   * Remplit la liste des champs disponibles.
   */
  private void initializeAvailableFields() {
    availableFields = new ArrayList<>();
    fieldConfigurations.forEach((key, value) ->
                                    availableFields.add(new SelectItem(key, value))
    );
  }

  /**
   * Remplit la liste des opérateurs disponibles.
   */
  private void initializeAvailableOperators() {
    availableOperators = new ArrayList<>();
    for (FilterOperator operator : FilterOperator.values()) {
      availableOperators.add(new SelectItem(operator, operator.getLabel()));
    }
  }

  /**
   * Ajoute un critère de filtrage après validation.
   */
  public void addFilter() {
    log.debug("Ajout d'un filtre : Champ = {}, Opérateur = {}, Valeur = {}",
              selectedField, selectedOperator, filterValue);

    try {
      validateFilterInput();
      validateFilterLimit();

      FilterCriteria newCriteria = new FilterCriteria(selectedField, selectedOperator, filterValue);
      criteria.add(newCriteria);

      addMessage(FacesMessage.SEVERITY_INFO, "Filtre ajouté", "Filtre appliqué avec succès.");
      log.info("Filtre ajouté avec succès : {}", newCriteria);
      resetForm();
    } catch (FilterException e) {
      log.warn("Erreur de validation du filtre", e);
      addMessage(FacesMessage.SEVERITY_ERROR, "Erreur", e.getMessage());
    }
  }

  /**
   * Supprime un critère de filtrage.
   *
   * @param filter Le critère à supprimer.
   */
  public void removeFilter(@NotNull FilterCriteria filter) {
    log.debug("Suppression du filtre : {}", filter);
    criteria.remove(filter);
    addMessage(FacesMessage.SEVERITY_INFO, "Filtre supprimé", "Le filtre a été retiré.");
  }

  /**
   * Efface tous les filtres existants.
   */
  public void clearAllFilters() {
    log.info("Suppression de tous les filtres ({})", criteria.size());
    criteria.clear();
    addMessage(FacesMessage.SEVERITY_INFO, "Filtres effacés", "Tous les filtres ont été supprimés.");
  }

  /**
   * Applique les filtres sur une liste de données.
   *
   * @param data Liste des objets à filtrer.
   * @return Liste filtrée.
   */
  public List<Object> applyFilters(List<Object> data) {
    if (criteria.isEmpty()) {
      return data;
    }
    log.debug("Application des filtres sur {} éléments", data.size());
    return data.stream().filter(this::matchesAllCriteria).toList();
  }

  /**
   * Valide les entrées du filtre.
   */
  private void validateFilterInput() {
    if (selectedField == null || selectedOperator == null || filterValue == null) {
      throw new FilterException("Tous les champs du filtre doivent être remplis.");
    }
    if (selectedOperator.isNumericComparison()) {
      try {
        Double.parseDouble(filterValue);
      } catch (NumberFormatException e) {
        throw new FilterException("La valeur doit être numérique pour cet opérateur.");
      }
    }
  }

  private void validateFilterLimit() {
    if (criteria.size() >= MAX_FILTERS) {
      throw new FilterException("Nombre maximum de filtres atteint (" + MAX_FILTERS + ")");
    }
  }

  private boolean matchesAllCriteria(Object item) {
    return criteria.stream().allMatch(filter -> matchesCriteria(item, filter));
  }

  private boolean matchesCriteria(Object item, FilterCriteria filter) {
    try {
      Object value = getPropertyValue(item, filter.getField());
      return filter.getOperator().apply(value, (String) filter.getValue());
    } catch (Exception e) {
      log.warn("Erreur d'accès à la propriété : {}", filter.getField(), e);
      return false;
    }
  }

  private Object getPropertyValue(Object item, String property) {
    try {
      Method getter = item.getClass().getMethod("get" + capitalize(property));
      return getter.invoke(item);
    } catch (Exception e) {
      throw new FilterException("Propriété inaccessible : " + property);
    }
  }

  private String capitalize(String str) {
    return str.substring(0, 1).toUpperCase() + str.substring(1);
  }

  private void resetForm() {
    selectedField = null;
    selectedOperator = null;
    filterValue = null;
  }

  private void addMessage(FacesMessage.Severity severity, String summary, String detail) {
    FacesContext.getCurrentInstance()
                .addMessage(null, new FacesMessage(severity, summary, detail));
  }
}
