package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.solidarite.DemandeAideDTO;
import dev.lions.unionflow.server.api.dto.solidarite.PropositionAideDTO;
import dev.lions.unionflow.server.api.dto.solidarite.EvaluationAideDTO;
import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.jboss.logging.Logger;

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

/**
 * Service d'analytics spécialisé pour le système de solidarité
 * 
 * Ce service calcule les métriques, statistiques et indicateurs
 * de performance du système de solidarité.
 * 
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-16
 */
@ApplicationScoped
public class SolidariteAnalyticsService {
    
    private static final Logger LOG = Logger.getLogger(SolidariteAnalyticsService.class);
    
    @Inject
    DemandeAideService demandeAideService;
    
    @Inject
    PropositionAideService propositionAideService;
    
    @Inject
    EvaluationService evaluationService;
    
    // Cache des statistiques calculées
    private final Map<String, Map<String, Object>> cacheStatistiques = new HashMap<>();
    private final Map<String, LocalDateTime> cacheTimestamps = new HashMap<>();
    private static final long CACHE_DURATION_MINUTES = 30;
    
    // === STATISTIQUES GÉNÉRALES ===
    
    /**
     * Calcule les statistiques générales de solidarité pour une organisation
     * 
     * @param organisationId ID de l'organisation
     * @return Map des statistiques
     */
    public Map<String, Object> calculerStatistiquesSolidarite(String organisationId) {
        LOG.infof("Calcul des statistiques de solidarité pour: %s", organisationId);
        
        // Vérification du cache
        String cacheKey = "stats_" + organisationId;
        Map<String, Object> statsCache = obtenirDuCache(cacheKey);
        if (statsCache != null) {
            return statsCache;
        }
        
        try {
            Map<String, Object> statistiques = new HashMap<>();
            
            // 1. Statistiques des demandes
            Map<String, Object> statsDemandes = calculerStatistiquesDemandes(organisationId);
            statistiques.put("demandes", statsDemandes);
            
            // 2. Statistiques des propositions
            Map<String, Object> statsPropositions = calculerStatistiquesPropositions(organisationId);
            statistiques.put("propositions", statsPropositions);
            
            // 3. Statistiques financières
            Map<String, Object> statsFinancieres = calculerStatistiquesFinancieres(organisationId);
            statistiques.put("financier", statsFinancieres);
            
            // 4. Indicateurs de performance
            Map<String, Object> kpis = calculerKPIsSolidarite(organisationId);
            statistiques.put("kpis", kpis);
            
            // 5. Tendances
            Map<String, Object> tendances = calculerTendances(organisationId);
            statistiques.put("tendances", tendances);
            
            // 6. Métadonnées
            statistiques.put("dateCalcul", LocalDateTime.now());
            statistiques.put("organisationId", organisationId);
            
            // Mise en cache
            ajouterAuCache(cacheKey, statistiques);
            
            return statistiques;
            
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors du calcul des statistiques pour: %s", organisationId);
            return new HashMap<>();
        }
    }
    
    /**
     * Calcule les statistiques des demandes d'aide
     */
    private Map<String, Object> calculerStatistiquesDemandes(String organisationId) {
        Map<String, Object> filtres = Map.of("organisationId", organisationId);
        List<DemandeAideDTO> demandes = demandeAideService.rechercherAvecFiltres(filtres);
        
        Map<String, Object> stats = new HashMap<>();
        
        // Nombre total de demandes
        stats.put("total", demandes.size());
        
        // Répartition par statut
        Map<StatutAide, Long> repartitionStatut = demandes.stream()
                .collect(Collectors.groupingBy(DemandeAideDTO::getStatut, Collectors.counting()));
        stats.put("parStatut", repartitionStatut);
        
        // Répartition par type d'aide
        Map<TypeAide, Long> repartitionType = demandes.stream()
                .collect(Collectors.groupingBy(DemandeAideDTO::getTypeAide, Collectors.counting()));
        stats.put("parType", repartitionType);
        
        // Répartition par priorité
        Map<PrioriteAide, Long> repartitionPriorite = demandes.stream()
                .collect(Collectors.groupingBy(DemandeAideDTO::getPriorite, Collectors.counting()));
        stats.put("parPriorite", repartitionPriorite);
        
        // Demandes urgentes
        long demandesUrgentes = demandes.stream()
                .filter(DemandeAideDTO::isUrgente)
                .count();
        stats.put("urgentes", demandesUrgentes);
        
        // Demandes en retard
        long demandesEnRetard = demandes.stream()
                .filter(DemandeAideDTO::isDelaiDepasse)
                .filter(d -> !d.isTerminee())
                .count();
        stats.put("enRetard", demandesEnRetard);
        
        // Taux d'approbation
        long demandesEvaluees = demandes.stream()
                .filter(d -> d.getStatut().isEstFinal())
                .count();
        long demandesApprouvees = demandes.stream()
                .filter(d -> d.getStatut() == StatutAide.APPROUVEE || 
                           d.getStatut() == StatutAide.APPROUVEE_PARTIELLEMENT)
                .count();
        
        double tauxApprobation = demandesEvaluees > 0 ? 
                                (demandesApprouvees * 100.0) / demandesEvaluees : 0.0;
        stats.put("tauxApprobation", Math.round(tauxApprobation * 100.0) / 100.0);
        
        // Délai moyen de traitement
        double delaiMoyenHeures = demandes.stream()
                .filter(d -> d.isTerminee())
                .mapToLong(DemandeAideDTO::getDureeTraitementJours)
                .average()
                .orElse(0.0) * 24; // Conversion en heures
        stats.put("delaiMoyenTraitementHeures", Math.round(delaiMoyenHeures * 100.0) / 100.0);
        
        return stats;
    }
    
    /**
     * Calcule les statistiques des propositions d'aide
     */
    private Map<String, Object> calculerStatistiquesPropositions(String organisationId) {
        Map<String, Object> filtres = Map.of("organisationId", organisationId);
        List<PropositionAideDTO> propositions = propositionAideService.rechercherAvecFiltres(filtres);
        
        Map<String, Object> stats = new HashMap<>();
        
        // Nombre total de propositions
        stats.put("total", propositions.size());
        
        // Propositions actives
        long propositionsActives = propositions.stream()
                .filter(PropositionAideDTO::isActiveEtDisponible)
                .count();
        stats.put("actives", propositionsActives);
        
        // Répartition par type d'aide
        Map<TypeAide, Long> repartitionType = propositions.stream()
                .collect(Collectors.groupingBy(PropositionAideDTO::getTypeAide, Collectors.counting()));
        stats.put("parType", repartitionType);
        
        // Capacité totale disponible
        int capaciteTotale = propositions.stream()
                .filter(PropositionAideDTO::isActiveEtDisponible)
                .mapToInt(PropositionAideDTO::getPlacesRestantes)
                .sum();
        stats.put("capaciteDisponible", capaciteTotale);
        
        // Taux d'utilisation moyen
        double tauxUtilisationMoyen = propositions.stream()
                .filter(p -> p.getNombreMaxBeneficiaires() > 0)
                .mapToDouble(PropositionAideDTO::getPourcentageCapaciteUtilisee)
                .average()
                .orElse(0.0);
        stats.put("tauxUtilisationMoyen", Math.round(tauxUtilisationMoyen * 100.0) / 100.0);
        
        // Note moyenne des propositions
        double noteMoyenne = propositions.stream()
                .filter(p -> p.getNoteMoyenne() != null)
                .mapToDouble(PropositionAideDTO::getNoteMoyenne)
                .average()
                .orElse(0.0);
        stats.put("noteMoyenne", Math.round(noteMoyenne * 100.0) / 100.0);
        
        return stats;
    }
    
    /**
     * Calcule les statistiques financières
     */
    private Map<String, Object> calculerStatistiquesFinancieres(String organisationId) {
        Map<String, Object> filtres = Map.of("organisationId", organisationId);
        List<DemandeAideDTO> demandes = demandeAideService.rechercherAvecFiltres(filtres);
        List<PropositionAideDTO> propositions = propositionAideService.rechercherAvecFiltres(filtres);
        
        Map<String, Object> stats = new HashMap<>();
        
        // Montant total demandé
        double montantTotalDemande = demandes.stream()
                .filter(d -> d.getMontantDemande() != null)
                .mapToDouble(DemandeAideDTO::getMontantDemande)
                .sum();
        stats.put("montantTotalDemande", montantTotalDemande);
        
        // Montant total approuvé
        double montantTotalApprouve = demandes.stream()
                .filter(d -> d.getMontantApprouve() != null)
                .mapToDouble(DemandeAideDTO::getMontantApprouve)
                .sum();
        stats.put("montantTotalApprouve", montantTotalApprouve);
        
        // Montant total versé
        double montantTotalVerse = demandes.stream()
                .filter(d -> d.getMontantVerse() != null)
                .mapToDouble(DemandeAideDTO::getMontantVerse)
                .sum();
        stats.put("montantTotalVerse", montantTotalVerse);
        
        // Capacité financière disponible (propositions)
        double capaciteFinanciere = propositions.stream()
                .filter(p -> p.getMontantMaximum() != null)
                .filter(PropositionAideDTO::isActiveEtDisponible)
                .mapToDouble(PropositionAideDTO::getMontantMaximum)
                .sum();
        stats.put("capaciteFinanciereDisponible", capaciteFinanciere);
        
        // Montant moyen par demande
        double montantMoyenDemande = demandes.stream()
                .filter(d -> d.getMontantDemande() != null)
                .mapToDouble(DemandeAideDTO::getMontantDemande)
                .average()
                .orElse(0.0);
        stats.put("montantMoyenDemande", Math.round(montantMoyenDemande * 100.0) / 100.0);
        
        // Taux de versement
        double tauxVersement = montantTotalApprouve > 0 ? 
                              (montantTotalVerse * 100.0) / montantTotalApprouve : 0.0;
        stats.put("tauxVersement", Math.round(tauxVersement * 100.0) / 100.0);
        
        return stats;
    }
    
    /**
     * Calcule les KPIs de solidarité
     */
    private Map<String, Object> calculerKPIsSolidarite(String organisationId) {
        Map<String, Object> kpis = new HashMap<>();
        
        // Simulation de calculs KPI - dans une vraie implémentation,
        // ces calculs seraient plus complexes et basés sur des données historiques
        
        // Efficacité du matching
        kpis.put("efficaciteMatching", 78.5); // Pourcentage de demandes matchées avec succès
        
        // Temps de réponse moyen
        kpis.put("tempsReponseMoyenHeures", 24.3);
        
        // Satisfaction globale
        kpis.put("satisfactionGlobale", 4.2); // Sur 5
        
        // Taux de résolution
        kpis.put("tauxResolution", 85.7); // Pourcentage de demandes résolues
        
        // Impact social (nombre de personnes aidées)
        kpis.put("personnesAidees", 156);
        
        // Engagement communautaire
        kpis.put("engagementCommunautaire", 67.8); // Pourcentage de membres actifs
        
        return kpis;
    }
    
    /**
     * Calcule les tendances sur les 30 derniers jours
     */
    private Map<String, Object> calculerTendances(String organisationId) {
        Map<String, Object> tendances = new HashMap<>();
        
        // Simulation de calculs de tendances
        // Dans une vraie implémentation, on comparerait avec la période précédente
        
        tendances.put("evolutionDemandes", "+12.5%"); // Évolution du nombre de demandes
        tendances.put("evolutionPropositions", "+8.3%"); // Évolution du nombre de propositions
        tendances.put("evolutionMontants", "+15.7%"); // Évolution des montants
        tendances.put("evolutionSatisfaction", "+2.1%"); // Évolution de la satisfaction
        
        // Prédictions pour le mois prochain
        Map<String, Object> predictions = new HashMap<>();
        predictions.put("demandesPrevues", 45);
        predictions.put("montantPrevu", 125000.0);
        predictions.put("capaciteRequise", 38);
        
        tendances.put("predictions", predictions);
        
        return tendances;
    }
    
    // === ENREGISTREMENT D'ÉVÉNEMENTS ===
    
    /**
     * Enregistre une nouvelle demande pour les analytics
     * 
     * @param demande La nouvelle demande
     */
    public void enregistrerNouvelledemande(DemandeAideDTO demande) {
        LOG.debugf("Enregistrement d'une nouvelle demande pour analytics: %s", demande.getId());
        
        try {
            // Invalidation du cache pour forcer le recalcul
            invaliderCache(demande.getOrganisationId());
            
            // Dans une vraie implémentation, on enregistrerait l'événement
            // dans une base de données d'événements ou un système de métriques
            
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de l'enregistrement de la nouvelle demande: %s", demande.getId());
        }
    }
    
    /**
     * Enregistre une nouvelle proposition pour les analytics
     * 
     * @param proposition La nouvelle proposition
     */
    public void enregistrerNouvelleProposition(PropositionAideDTO proposition) {
        LOG.debugf("Enregistrement d'une nouvelle proposition pour analytics: %s", proposition.getId());
        
        try {
            invaliderCache(proposition.getOrganisationId());
            
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de l'enregistrement de la nouvelle proposition: %s", 
                      proposition.getId());
        }
    }
    
    /**
     * Enregistre l'évaluation d'une demande
     * 
     * @param demande La demande évaluée
     */
    public void enregistrerEvaluationDemande(DemandeAideDTO demande) {
        LOG.debugf("Enregistrement de l'évaluation pour analytics: %s", demande.getId());
        
        try {
            invaliderCache(demande.getOrganisationId());
            
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de l'enregistrement de l'évaluation: %s", demande.getId());
        }
    }
    
    /**
     * Enregistre le rejet d'une demande avec motif
     * 
     * @param demande La demande rejetée
     * @param motif Le motif de rejet
     */
    public void enregistrerRejetDemande(DemandeAideDTO demande, String motif) {
        LOG.debugf("Enregistrement du rejet pour analytics: %s - motif: %s", demande.getId(), motif);
        
        try {
            invaliderCache(demande.getOrganisationId());
            
            // Dans une vraie implémentation, on analyserait les motifs de rejet
            // pour identifier les problèmes récurrents
            
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de l'enregistrement du rejet: %s", demande.getId());
        }
    }
    
    // === GESTION DU CACHE ===
    
    private Map<String, Object> obtenirDuCache(String cacheKey) {
        LocalDateTime timestamp = cacheTimestamps.get(cacheKey);
        if (timestamp == null) return null;
        
        // Vérification de l'expiration
        if (LocalDateTime.now().minusMinutes(CACHE_DURATION_MINUTES).isAfter(timestamp)) {
            cacheStatistiques.remove(cacheKey);
            cacheTimestamps.remove(cacheKey);
            return null;
        }
        
        return cacheStatistiques.get(cacheKey);
    }
    
    private void ajouterAuCache(String cacheKey, Map<String, Object> statistiques) {
        cacheStatistiques.put(cacheKey, statistiques);
        cacheTimestamps.put(cacheKey, LocalDateTime.now());
        
        // Nettoyage du cache si trop volumineux
        if (cacheStatistiques.size() > 50) {
            nettoyerCache();
        }
    }
    
    private void invaliderCache(String organisationId) {
        String cacheKey = "stats_" + organisationId;
        cacheStatistiques.remove(cacheKey);
        cacheTimestamps.remove(cacheKey);
    }
    
    private void nettoyerCache() {
        LocalDateTime limite = LocalDateTime.now().minusMinutes(CACHE_DURATION_MINUTES);
        
        cacheTimestamps.entrySet().removeIf(entry -> entry.getValue().isBefore(limite));
        cacheStatistiques.keySet().retainAll(cacheTimestamps.keySet());
    }
}
