package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.solidarite.EvaluationAideDTO;
import dev.lions.unionflow.server.api.dto.solidarite.DemandeAideDTO;
import dev.lions.unionflow.server.api.dto.solidarite.PropositionAideDTO;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.NotBlank;
import org.jboss.logging.Logger;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Service de gestion des évaluations d'aide
 * 
 * Ce service gère le cycle de vie des évaluations :
 * création, validation, calcul des moyennes, détection de fraude.
 * 
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-16
 */
@ApplicationScoped
public class EvaluationService {
    
    private static final Logger LOG = Logger.getLogger(EvaluationService.class);
    
    @Inject
    DemandeAideService demandeAideService;
    
    @Inject
    PropositionAideService propositionAideService;
    
    // Cache des évaluations récentes
    private final Map<String, List<EvaluationAideDTO>> cacheEvaluationsParDemande = new HashMap<>();
    private final Map<String, List<EvaluationAideDTO>> cacheEvaluationsParProposition = new HashMap<>();
    
    // === CRÉATION ET GESTION DES ÉVALUATIONS ===
    
    /**
     * Crée une nouvelle évaluation d'aide
     * 
     * @param evaluationDTO L'évaluation à créer
     * @return L'évaluation créée avec ID généré
     */
    @Transactional
    public EvaluationAideDTO creerEvaluation(@Valid EvaluationAideDTO evaluationDTO) {
        LOG.infof("Création d'une nouvelle évaluation pour la demande: %s", 
                 evaluationDTO.getDemandeAideId());
        
        // Validation préalable
        validerEvaluationAvantCreation(evaluationDTO);
        
        // Génération de l'ID et initialisation
        evaluationDTO.setId(UUID.randomUUID().toString());
        LocalDateTime maintenant = LocalDateTime.now();
        evaluationDTO.setDateCreation(maintenant);
        evaluationDTO.setDateModification(maintenant);
        
        // Statut initial
        if (evaluationDTO.getStatut() == null) {
            evaluationDTO.setStatut(EvaluationAideDTO.StatutEvaluation.ACTIVE);
        }
        
        // Calcul du score de qualité
        double scoreQualite = evaluationDTO.getScoreQualite();
        
        // Détection de fraude potentielle
        if (detecterFraudePotentielle(evaluationDTO)) {
            evaluationDTO.setStatut(EvaluationAideDTO.StatutEvaluation.SIGNALEE);
            LOG.warnf("Évaluation potentiellement frauduleuse détectée: %s", evaluationDTO.getId());
        }
        
        // Mise à jour du cache
        ajouterAuCache(evaluationDTO);
        
        // Mise à jour des moyennes
        mettreAJourMoyennesAsync(evaluationDTO);
        
        LOG.infof("Évaluation créée avec succès: %s (score: %.2f)", 
                 evaluationDTO.getId(), scoreQualite);
        return evaluationDTO;
    }
    
    /**
     * Met à jour une évaluation existante
     * 
     * @param evaluationDTO L'évaluation à mettre à jour
     * @return L'évaluation mise à jour
     */
    @Transactional
    public EvaluationAideDTO mettreAJourEvaluation(@Valid EvaluationAideDTO evaluationDTO) {
        LOG.infof("Mise à jour de l'évaluation: %s", evaluationDTO.getId());
        
        // Vérification que l'évaluation peut être modifiée
        if (evaluationDTO.getStatut() == EvaluationAideDTO.StatutEvaluation.SUPPRIMEE) {
            throw new IllegalStateException("Impossible de modifier une évaluation supprimée");
        }
        
        // Mise à jour des dates
        evaluationDTO.setDateModification(LocalDateTime.now());
        evaluationDTO.setEstModifie(true);
        
        // Nouvelle détection de fraude si changements significatifs
        if (detecterChangementsSignificatifs(evaluationDTO)) {
            if (detecterFraudePotentielle(evaluationDTO)) {
                evaluationDTO.setStatut(EvaluationAideDTO.StatutEvaluation.SIGNALEE);
            }
        }
        
        // Mise à jour du cache
        ajouterAuCache(evaluationDTO);
        
        // Recalcul des moyennes
        mettreAJourMoyennesAsync(evaluationDTO);
        
        return evaluationDTO;
    }
    
    /**
     * Obtient une évaluation par son ID
     * 
     * @param id ID de l'évaluation
     * @return L'évaluation trouvée
     */
    public EvaluationAideDTO obtenirParId(@NotBlank String id) {
        LOG.debugf("Récupération de l'évaluation: %s", id);
        
        // Simulation de récupération - dans une vraie implémentation,
        // ceci ferait appel au repository
        return simulerRecuperationBDD(id);
    }
    
    /**
     * Obtient les évaluations d'une demande d'aide
     * 
     * @param demandeId ID de la demande d'aide
     * @return Liste des évaluations
     */
    public List<EvaluationAideDTO> obtenirEvaluationsDemande(@NotBlank String demandeId) {
        LOG.debugf("Récupération des évaluations pour la demande: %s", demandeId);
        
        // Vérification du cache
        List<EvaluationAideDTO> evaluationsCachees = cacheEvaluationsParDemande.get(demandeId);
        if (evaluationsCachees != null) {
            return evaluationsCachees.stream()
                    .filter(e -> e.getStatut() == EvaluationAideDTO.StatutEvaluation.ACTIVE)
                    .sorted((e1, e2) -> e2.getDateCreation().compareTo(e1.getDateCreation()))
                    .collect(Collectors.toList());
        }
        
        // Simulation de récupération depuis la base
        return simulerRecuperationEvaluationsDemande(demandeId);
    }
    
    /**
     * Obtient les évaluations d'une proposition d'aide
     * 
     * @param propositionId ID de la proposition d'aide
     * @return Liste des évaluations
     */
    public List<EvaluationAideDTO> obtenirEvaluationsProposition(@NotBlank String propositionId) {
        LOG.debugf("Récupération des évaluations pour la proposition: %s", propositionId);
        
        List<EvaluationAideDTO> evaluationsCachees = cacheEvaluationsParProposition.get(propositionId);
        if (evaluationsCachees != null) {
            return evaluationsCachees.stream()
                    .filter(e -> e.getStatut() == EvaluationAideDTO.StatutEvaluation.ACTIVE)
                    .sorted((e1, e2) -> e2.getDateCreation().compareTo(e1.getDateCreation()))
                    .collect(Collectors.toList());
        }
        
        return simulerRecuperationEvaluationsProposition(propositionId);
    }
    
    // === CALCULS DE MOYENNES ET STATISTIQUES ===
    
    /**
     * Calcule la note moyenne d'une demande d'aide
     * 
     * @param demandeId ID de la demande
     * @return Note moyenne et nombre d'évaluations
     */
    public Map<String, Object> calculerMoyenneDemande(@NotBlank String demandeId) {
        List<EvaluationAideDTO> evaluations = obtenirEvaluationsDemande(demandeId);
        
        if (evaluations.isEmpty()) {
            return Map.of(
                "noteMoyenne", 0.0,
                "nombreEvaluations", 0,
                "repartitionNotes", new HashMap<Integer, Integer>()
            );
        }
        
        double moyenne = evaluations.stream()
                .mapToDouble(EvaluationAideDTO::getNoteGlobale)
                .average()
                .orElse(0.0);
        
        Map<Integer, Integer> repartition = new HashMap<>();
        for (int i = 1; i <= 5; i++) {
            final int note = i;
            int count = (int) evaluations.stream()
                    .mapToDouble(EvaluationAideDTO::getNoteGlobale)
                    .filter(n -> Math.floor(n) == note)
                    .count();
            repartition.put(note, count);
        }
        
        return Map.of(
            "noteMoyenne", Math.round(moyenne * 100.0) / 100.0,
            "nombreEvaluations", evaluations.size(),
            "repartitionNotes", repartition,
            "pourcentagePositives", calculerPourcentagePositives(evaluations),
            "derniereMiseAJour", LocalDateTime.now()
        );
    }
    
    /**
     * Calcule la note moyenne d'une proposition d'aide
     * 
     * @param propositionId ID de la proposition
     * @return Note moyenne et statistiques détaillées
     */
    public Map<String, Object> calculerMoyenneProposition(@NotBlank String propositionId) {
        List<EvaluationAideDTO> evaluations = obtenirEvaluationsProposition(propositionId);
        
        if (evaluations.isEmpty()) {
            return Map.of(
                "noteMoyenne", 0.0,
                "nombreEvaluations", 0,
                "notesDetaillees", new HashMap<String, Double>()
            );
        }
        
        double moyenne = evaluations.stream()
                .mapToDouble(EvaluationAideDTO::getNoteGlobale)
                .average()
                .orElse(0.0);
        
        // Calcul des moyennes détaillées
        Map<String, Double> notesDetaillees = new HashMap<>();
        notesDetaillees.put("delaiReponse", calculerMoyenneNote(evaluations, 
                e -> e.getNoteDelaiReponse()));
        notesDetaillees.put("communication", calculerMoyenneNote(evaluations, 
                e -> e.getNoteCommunication()));
        notesDetaillees.put("professionnalisme", calculerMoyenneNote(evaluations, 
                e -> e.getNoteProfessionnalisme()));
        notesDetaillees.put("respectEngagements", calculerMoyenneNote(evaluations, 
                e -> e.getNoteRespectEngagements()));
        
        return Map.of(
            "noteMoyenne", Math.round(moyenne * 100.0) / 100.0,
            "nombreEvaluations", evaluations.size(),
            "notesDetaillees", notesDetaillees,
            "pourcentageRecommandations", calculerPourcentageRecommandations(evaluations),
            "scoreQualiteMoyen", calculerScoreQualiteMoyen(evaluations)
        );
    }
    
    // === MODÉRATION ET VALIDATION ===
    
    /**
     * Signale une évaluation comme inappropriée
     * 
     * @param evaluationId ID de l'évaluation
     * @param motif Motif du signalement
     * @return L'évaluation mise à jour
     */
    @Transactional
    public EvaluationAideDTO signalerEvaluation(@NotBlank String evaluationId, String motif) {
        LOG.infof("Signalement de l'évaluation: %s pour motif: %s", evaluationId, motif);
        
        EvaluationAideDTO evaluation = obtenirParId(evaluationId);
        if (evaluation == null) {
            throw new IllegalArgumentException("Évaluation non trouvée: " + evaluationId);
        }
        
        evaluation.setNombreSignalements(evaluation.getNombreSignalements() + 1);
        
        // Masquage automatique si trop de signalements
        if (evaluation.getNombreSignalements() >= 3) {
            evaluation.setStatut(EvaluationAideDTO.StatutEvaluation.MASQUEE);
            LOG.warnf("Évaluation automatiquement masquée: %s", evaluationId);
        } else {
            evaluation.setStatut(EvaluationAideDTO.StatutEvaluation.SIGNALEE);
        }
        
        // Mise à jour du cache
        ajouterAuCache(evaluation);
        
        return evaluation;
    }
    
    /**
     * Valide une évaluation après vérification
     * 
     * @param evaluationId ID de l'évaluation
     * @param verificateurId ID du vérificateur
     * @return L'évaluation validée
     */
    @Transactional
    public EvaluationAideDTO validerEvaluation(@NotBlank String evaluationId, 
                                              @NotBlank String verificateurId) {
        LOG.infof("Validation de l'évaluation: %s par: %s", evaluationId, verificateurId);
        
        EvaluationAideDTO evaluation = obtenirParId(evaluationId);
        if (evaluation == null) {
            throw new IllegalArgumentException("Évaluation non trouvée: " + evaluationId);
        }
        
        evaluation.setEstVerifiee(true);
        evaluation.setDateVerification(LocalDateTime.now());
        evaluation.setVerificateurId(verificateurId);
        evaluation.setStatut(EvaluationAideDTO.StatutEvaluation.ACTIVE);
        
        // Remise à zéro des signalements si validation positive
        evaluation.setNombreSignalements(0);
        
        ajouterAuCache(evaluation);
        
        return evaluation;
    }
    
    // === MÉTHODES UTILITAIRES PRIVÉES ===
    
    /**
     * Valide une évaluation avant création
     */
    private void validerEvaluationAvantCreation(EvaluationAideDTO evaluation) {
        // Vérifier que la demande existe
        DemandeAideDTO demande = demandeAideService.obtenirParId(evaluation.getDemandeAideId());
        if (demande == null) {
            throw new IllegalArgumentException("Demande d'aide non trouvée: " + 
                                             evaluation.getDemandeAideId());
        }
        
        // Vérifier que la demande est terminée
        if (!demande.isTerminee()) {
            throw new IllegalStateException("Impossible d'évaluer une demande non terminée");
        }
        
        // Vérifier qu'il n'y a pas déjà une évaluation du même évaluateur
        List<EvaluationAideDTO> evaluationsExistantes = obtenirEvaluationsDemande(evaluation.getDemandeAideId());
        boolean dejaEvalue = evaluationsExistantes.stream()
                .anyMatch(e -> e.getEvaluateurId().equals(evaluation.getEvaluateurId()));
        
        if (dejaEvalue) {
            throw new IllegalStateException("Cet évaluateur a déjà évalué cette demande");
        }
    }
    
    /**
     * Détecte une fraude potentielle dans une évaluation
     */
    private boolean detecterFraudePotentielle(EvaluationAideDTO evaluation) {
        // Critères de détection de fraude
        
        // 1. Note extrême avec commentaire très court
        if ((evaluation.getNoteGlobale() <= 1.0 || evaluation.getNoteGlobale() >= 5.0) &&
            (evaluation.getCommentairePrincipal() == null || 
             evaluation.getCommentairePrincipal().length() < 20)) {
            return true;
        }
        
        // 2. Toutes les notes identiques (suspect)
        if (evaluation.getNotesDetaillees() != null && 
            evaluation.getNotesDetaillees().size() > 1) {
            Set<Double> notesUniques = new HashSet<>(evaluation.getNotesDetaillees().values());
            if (notesUniques.size() == 1) {
                return true;
            }
        }
        
        // 3. Évaluation créée trop rapidement après la fin de l'aide
        // (simulation - dans une vraie implémentation, on vérifierait la date de fin réelle)
        
        return false;
    }
    
    /**
     * Détecte des changements significatifs dans une évaluation
     */
    private boolean detecterChangementsSignificatifs(EvaluationAideDTO evaluation) {
        // Simulation - dans une vraie implémentation, on comparerait avec la version précédente
        return evaluation.getEstModifie();
    }
    
    /**
     * Met à jour les moyennes de manière asynchrone
     */
    private void mettreAJourMoyennesAsync(EvaluationAideDTO evaluation) {
        // Simulation d'une mise à jour asynchrone
        // Dans une vraie implémentation, ceci utiliserait @Async ou un message queue
        
        try {
            // Mise à jour de la moyenne de la demande
            Map<String, Object> moyenneDemande = calculerMoyenneDemande(evaluation.getDemandeAideId());
            
            // Mise à jour de la moyenne de la proposition si applicable
            if (evaluation.getPropositionAideId() != null) {
                Map<String, Object> moyenneProposition = calculerMoyenneProposition(evaluation.getPropositionAideId());
                
                // Mise à jour de la proposition avec la nouvelle moyenne
                PropositionAideDTO proposition = propositionAideService.obtenirParId(evaluation.getPropositionAideId());
                if (proposition != null) {
                    proposition.setNoteMoyenne((Double) moyenneProposition.get("noteMoyenne"));
                    proposition.setNombreEvaluations((Integer) moyenneProposition.get("nombreEvaluations"));
                    propositionAideService.mettreAJour(proposition);
                }
            }
            
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de la mise à jour des moyennes pour l'évaluation: %s", 
                      evaluation.getId());
        }
    }
    
    /**
     * Calcule la moyenne d'une note spécifique
     */
    private double calculerMoyenneNote(List<EvaluationAideDTO> evaluations, 
                                      java.util.function.Function<EvaluationAideDTO, Double> extracteur) {
        return evaluations.stream()
                .map(extracteur)
                .filter(Objects::nonNull)
                .mapToDouble(Double::doubleValue)
                .average()
                .orElse(0.0);
    }
    
    /**
     * Calcule le pourcentage d'évaluations positives
     */
    private double calculerPourcentagePositives(List<EvaluationAideDTO> evaluations) {
        if (evaluations.isEmpty()) return 0.0;
        
        long positives = evaluations.stream()
                .mapToDouble(EvaluationAideDTO::getNoteGlobale)
                .filter(note -> note >= 4.0)
                .count();
        
        return (positives * 100.0) / evaluations.size();
    }
    
    /**
     * Calcule le pourcentage de recommandations
     */
    private double calculerPourcentageRecommandations(List<EvaluationAideDTO> evaluations) {
        if (evaluations.isEmpty()) return 0.0;
        
        long recommandations = evaluations.stream()
                .filter(e -> e.getRecommande() != null && e.getRecommande())
                .count();
        
        return (recommandations * 100.0) / evaluations.size();
    }
    
    /**
     * Calcule le score de qualité moyen
     */
    private double calculerScoreQualiteMoyen(List<EvaluationAideDTO> evaluations) {
        return evaluations.stream()
                .mapToDouble(EvaluationAideDTO::getScoreQualite)
                .average()
                .orElse(0.0);
    }
    
    // === GESTION DU CACHE ===
    
    private void ajouterAuCache(EvaluationAideDTO evaluation) {
        // Cache par demande
        cacheEvaluationsParDemande.computeIfAbsent(evaluation.getDemandeAideId(), 
                k -> new ArrayList<>()).add(evaluation);
        
        // Cache par proposition si applicable
        if (evaluation.getPropositionAideId() != null) {
            cacheEvaluationsParProposition.computeIfAbsent(evaluation.getPropositionAideId(), 
                    k -> new ArrayList<>()).add(evaluation);
        }
    }
    
    // === RECHERCHE ET FILTRAGE ===

    /**
     * Recherche des évaluations avec filtres
     *
     * @param filtres Critères de recherche
     * @return Liste des évaluations correspondantes
     */
    public List<EvaluationAideDTO> rechercherEvaluations(Map<String, Object> filtres) {
        LOG.debugf("Recherche d'évaluations avec filtres: %s", filtres);

        // Simulation de recherche - dans une vraie implémentation,
        // ceci utiliserait des requêtes de base de données
        return new ArrayList<>();
    }

    /**
     * Obtient les évaluations récentes pour le tableau de bord
     *
     * @param organisationId ID de l'organisation
     * @param limite Nombre maximum d'évaluations
     * @return Liste des évaluations récentes
     */
    public List<EvaluationAideDTO> obtenirEvaluationsRecentes(String organisationId, int limite) {
        LOG.debugf("Récupération des %d évaluations récentes pour: %s", limite, organisationId);

        // Simulation - filtrage par organisation et tri par date
        return new ArrayList<>();
    }

    // === MÉTHODES DE SIMULATION ===

    private EvaluationAideDTO simulerRecuperationBDD(String id) {
        return null; // Simulation
    }

    private List<EvaluationAideDTO> simulerRecuperationEvaluationsDemande(String demandeId) {
        return new ArrayList<>(); // Simulation
    }

    private List<EvaluationAideDTO> simulerRecuperationEvaluationsProposition(String propositionId) {
        return new ArrayList<>(); // Simulation
    }
}
