package dev.lions.unionflow.client.service;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validator;
import jakarta.validation.groups.Default;
import org.jboss.logging.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * Service de validation centralisé avec feedback en temps réel
 * 
 * Fournit des méthodes de validation pour les beans avec :
 * - Validation complète d'objets
 * - Validation de propriétés individuelles (temps réel)
 * - Validation de valeurs
 * - Support des groupes de validation
 * - Intégration avec ErrorHandlerService
 * 
 * @author UnionFlow Team
 * @version 3.0
 * @since 2026-01-04
 */
@ApplicationScoped
public class ValidationService {
    
    private static final Logger LOG = Logger.getLogger(ValidationService.class);
    
    @Inject
    Validator validator;
    
    @Inject
    ErrorHandlerService errorHandler;
    
    @Inject
    MetricsService metricsService;
    
    /**
     * Valide un objet et retourne la liste des erreurs
     * 
     * @param object L'objet à valider
     * @return Résultat de validation avec détails des erreurs
     */
    public <T> ValidationResult validate(T object) {
        if (object == null) {
            LOG.warn("Tentative de validation d'un objet null");
            ValidationResult result = new ValidationResult();
            result.setValid(false);
            result.addErrorMessage("L'objet à valider est null");
            return result;
        }
        
        Set<ConstraintViolation<T>> violations = validator.validate(object);
        LOG.debugf("Validation de %s : %d violation(s)", object.getClass().getSimpleName(), violations.size());
        
        ValidationResult result = new ValidationResult();
        result.setValid(violations.isEmpty());
        
        for (ConstraintViolation<T> violation : violations) {
            String message = violation.getPropertyPath() + ": " + violation.getMessage();
            result.addErrorMessage(message);
            LOG.debugf("Violation: %s", message);
        }
        
        // Enregistrer les métriques de validation
        if (metricsService != null) {
            metricsService.recordValidation(result.isValid());
        }
        
        return result;
    }
    
    /**
     * Valide un objet avec groupes de validation spécifiques
     * 
     * @param object L'objet à valider
     * @param groups Groupes de validation à appliquer
     * @return Résultat de validation
     */
    public <T> ValidationResult validate(T object, Class<?>... groups) {
        if (object == null) {
            ValidationResult result = new ValidationResult();
            result.setValid(false);
            result.addErrorMessage("L'objet à valider est null");
            return result;
        }
        
        Set<ConstraintViolation<T>> violations = validator.validate(object, groups);
        LOG.debugf("Validation de %s avec groupes : %d violation(s)", 
            object.getClass().getSimpleName(), violations.size());
        
        ValidationResult result = new ValidationResult();
        result.setValid(violations.isEmpty());
        
        for (ConstraintViolation<T> violation : violations) {
            result.addErrorMessage(violation.getPropertyPath() + ": " + violation.getMessage());
        }
        
        return result;
    }
    
    /**
     * Valide un objet et affiche les erreurs automatiquement via ErrorHandlerService
     * 
     * @param object L'objet à valider
     * @param showMessagesToUser Si true, affiche les erreurs à l'utilisateur
     * @return true si valide, false sinon
     */
    public <T> boolean validateAndShow(T object, boolean showMessagesToUser) {
        ValidationResult result = validate(object);
        
        if (!result.isValid() && showMessagesToUser) {
            for (String message : result.getErrorMessages()) {
                errorHandler.showWarning("Validation", message);
            }
        }
        
        return result.isValid();
    }
    
    /**
     * Valide une propriété spécifique d'un objet (utile pour validation en temps réel)
     * 
     * @param object L'objet contenant la propriété
     * @param propertyName Nom de la propriété à valider
     * @return Résultat de validation
     */
    public <T> ValidationResult validateProperty(T object, String propertyName) {
        if (object == null || propertyName == null || propertyName.isEmpty()) {
            ValidationResult result = new ValidationResult();
            result.setValid(false);
            result.addErrorMessage("Paramètres de validation invalides");
            return result;
        }
        
        Set<ConstraintViolation<T>> violations = validator.validateProperty(object, propertyName);
        LOG.debugf("Validation propriété %s.%s : %d violation(s)", 
            object.getClass().getSimpleName(), propertyName, violations.size());
        
        ValidationResult result = new ValidationResult();
        result.setValid(violations.isEmpty());
        result.setPropertyName(propertyName);
        
        for (ConstraintViolation<T> violation : violations) {
            result.addErrorMessage(violation.getMessage());
        }
        
        return result;
    }
    
    /**
     * Valide une propriété et affiche l'erreur si invalide
     * Utilisé pour validation AJAX en temps réel
     * 
     * @param object L'objet
     * @param propertyName Propriété à valider
     * @param componentId ID du composant JSF pour cibler le message
     * @return true si valide
     */
    public <T> boolean validatePropertyAndShow(T object, String propertyName, String componentId) {
        ValidationResult result = validateProperty(object, propertyName);
        
        if (!result.isValid()) {
            String message = result.getFirstErrorMessage();
            if (message != null) {
                errorHandler.showWarning(propertyName, message);
            }
        }
        
        return result.isValid();
    }
    
    /**
     * Valide une valeur contre les contraintes d'une propriété
     * Utile pour valider une valeur avant de l'assigner
     * 
     * @param beanType Classe du bean
     * @param propertyName Nom de la propriété
     * @param value Valeur à valider
     * @return Résultat de validation
     */
    public <T> ValidationResult validateValue(Class<T> beanType, String propertyName, Object value) {
        Set<ConstraintViolation<T>> violations = validator.validateValue(beanType, propertyName, value);
        LOG.debugf("Validation valeur %s.%s = %s : %d violation(s)", 
            beanType.getSimpleName(), propertyName, value, violations.size());
        
        ValidationResult result = new ValidationResult();
        result.setValid(violations.isEmpty());
        result.setPropertyName(propertyName);
        
        for (ConstraintViolation<T> violation : violations) {
            result.addErrorMessage(violation.getMessage());
        }
        
        return result;
    }
    
    /**
     * Valide plusieurs objets en une seule opération
     * 
     * @param objects Liste d'objets à valider
     * @return Résultat de validation global
     */
    public ValidationResult validateMultiple(Object... objects) {
        ValidationResult globalResult = new ValidationResult();
        globalResult.setValid(true);
        
        for (Object object : objects) {
            ValidationResult result = validate(object);
            if (!result.isValid()) {
                globalResult.setValid(false);
                globalResult.getErrorMessages().addAll(result.getErrorMessages());
            }
        }
        
        return globalResult;
    }
    
    /**
     * Vérifie si un objet est valide (méthode simplifiée)
     * 
     * @param object L'objet à valider
     * @return true si valide, false sinon
     */
    public <T> boolean isValid(T object) {
        return object != null && validator.validate(object).isEmpty();
    }
    
    /**
     * Vérifie si une propriété est valide (méthode simplifiée)
     * 
     * @param object L'objet
     * @param propertyName Propriété à vérifier
     * @return true si valide
     */
    public <T> boolean isPropertyValid(T object, String propertyName) {
        return object != null && 
               propertyName != null && 
               validator.validateProperty(object, propertyName).isEmpty();
    }
    
    /**
     * Classe pour encapsuler le résultat de validation
     */
    public static class ValidationResult {
        private boolean valid = true;
        private List<String> errorMessages = new ArrayList<>();
        private String propertyName;
        
        public boolean isValid() {
            return valid;
        }
        
        public void setValid(boolean valid) {
            this.valid = valid;
        }
        
        public List<String> getErrorMessages() {
            return errorMessages;
        }
        
        public void setErrorMessages(List<String> errorMessages) {
            this.errorMessages = errorMessages;
        }
        
        public void addErrorMessage(String message) {
            this.errorMessages.add(message);
        }
        
        public String getPropertyName() {
            return propertyName;
        }
        
        public void setPropertyName(String propertyName) {
            this.propertyName = propertyName;
        }
        
        public String getFirstErrorMessage() {
            return errorMessages.isEmpty() ? null : errorMessages.get(0);
        }
        
        public String getAllErrorMessages() {
            return String.join(", ", errorMessages);
        }
        
        public int getErrorCount() {
            return errorMessages.size();
        }
        
        public boolean hasErrors() {
            return !valid || !errorMessages.isEmpty();
        }
        
        @Override
        public String toString() {
            return "ValidationResult{" +
                   "valid=" + valid +
                   ", errorCount=" + errorMessages.size() +
                   ", propertyName='" + propertyName + '\'' +
                   '}';
        }
    }
}
