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 jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.NotBlank;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;

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

/**
 * Service principal de gestion de la solidarité UnionFlow
 * 
 * Ce service orchestre toutes les opérations liées au système de solidarité :
 * demandes d'aide, propositions d'aide, matching, évaluations et suivi.
 * 
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-16
 */
@ApplicationScoped
public class SolidariteService {
    
    private static final Logger LOG = Logger.getLogger(SolidariteService.class);
    
    @Inject
    DemandeAideService demandeAideService;
    
    @Inject
    PropositionAideService propositionAideService;
    
    @Inject
    MatchingService matchingService;
    
    // @Inject
    // EvaluationService evaluationService;

    // @Inject
    // NotificationSolidariteService notificationService;
    
    @Inject
    SolidariteAnalyticsService analyticsService;
    
    @ConfigProperty(name = "unionflow.solidarite.auto-matching.enabled", defaultValue = "true")
    boolean autoMatchingEnabled;
    
    @ConfigProperty(name = "unionflow.solidarite.notification.enabled", defaultValue = "true")
    boolean notificationEnabled;
    
    @ConfigProperty(name = "unionflow.solidarite.evaluation.obligatoire", defaultValue = "false")
    boolean evaluationObligatoire;
    
    // === GESTION DES DEMANDES D'AIDE ===
    
    /**
     * Crée une nouvelle demande d'aide
     * 
     * @param demandeDTO La demande d'aide à créer
     * @return La demande d'aide créée
     */
    @Transactional
    public CompletableFuture<DemandeAideDTO> creerDemandeAide(@Valid DemandeAideDTO demandeDTO) {
        LOG.infof("Création d'une nouvelle demande d'aide: %s", demandeDTO.getTitre());
        
        return CompletableFuture.supplyAsync(() -> {
            try {
                // 1. Créer la demande
                DemandeAideDTO demandeCree = demandeAideService.creerDemande(demandeDTO);
                
                // 2. Calcul automatique de la priorité si non définie
                // if (demandeCree.getPriorite() == null) {
                //     PrioriteAide prioriteCalculee = PrioriteAide.determinerPriorite(demandeCree.getTypeAide());
                //     demandeCree.setPriorite(prioriteCalculee);
                //     demandeCree = demandeAideService.mettreAJour(demandeCree);
                // }
                
                // 3. Matching automatique si activé
                if (autoMatchingEnabled) {
                    CompletableFuture.runAsync(() -> {
                        try {
                            List<PropositionAideDTO> propositionsCompatibles = 
                                matchingService.trouverPropositionsCompatibles(demandeCree);
                            
                            if (!propositionsCompatibles.isEmpty()) {
                                LOG.infof("Trouvé %d propositions compatibles pour la demande %s", 
                                         propositionsCompatibles.size(), demandeCree.getId());
                                
                                // Notification aux proposants
                                if (notificationEnabled) {
                                    notificationService.notifierProposantsCompatibles(
                                        demandeCree, propositionsCompatibles);
                                }
                            }
                        } catch (Exception e) {
                            LOG.errorf(e, "Erreur lors du matching automatique pour la demande %s", 
                                      demandeCree.getId());
                        }
                    });
                }
                
                // 4. Notifications
                if (notificationEnabled) {
                    notificationService.notifierCreationDemande(demandeCree);
                    
                    // Notification d'urgence si priorité critique
                    if (demandeCree.getPriorite() == PrioriteAide.CRITIQUE) {
                        notificationService.notifierUrgenceCritique(demandeCree);
                    }
                }
                
                // 5. Mise à jour des analytics
                analyticsService.enregistrerNouvelledemande(demandeCree);
                
                LOG.infof("Demande d'aide créée avec succès: %s", demandeCree.getId());
                return demandeCree;
                
            } catch (Exception e) {
                LOG.errorf(e, "Erreur lors de la création de la demande d'aide");
                throw new RuntimeException("Erreur lors de la création de la demande d'aide", e);
            }
        });
    }
    
    /**
     * Soumet une demande d'aide de manière asynchrone (passage de brouillon à soumise)
     *
     * @param demandeId ID de la demande
     * @return La demande soumise
     */
    @Transactional
    public CompletableFuture<DemandeAideDTO> soumettreDemandeeAsync(@NotBlank String demandeId) {
        LOG.infof("Soumission de la demande d'aide: %s", demandeId);
        
        return CompletableFuture.supplyAsync(() -> {
            try {
                // 1. Récupérer et valider la demande
                DemandeAideDTO demande = demandeAideService.obtenirParId(demandeId);
                
                if (demande.getStatut() != StatutAide.BROUILLON) {
                    throw new IllegalStateException("Seules les demandes en brouillon peuvent être soumises");
                }
                
                // 2. Validation complète de la demande
                validerDemandeAvantSoumission(demande);
                
                // 3. Changement de statut
                demande = demandeAideService.changerStatut(demandeId, StatutAide.SOUMISE, 
                                                          "Demande soumise par le demandeur");
                
                // 4. Calcul de la date limite de traitement
                LocalDateTime dateLimite = demande.getPriorite().getDateLimiteTraitement();
                demande.setDateLimiteTraitement(dateLimite);
                demande = demandeAideService.mettreAJour(demande);
                
                // 5. Notifications
                if (notificationEnabled) {
                    notificationService.notifierSoumissionDemande(demande);
                    notificationService.notifierEvaluateurs(demande);
                }
                
                // 6. Programmation des rappels automatiques
                programmerRappelsAutomatiques(demande);
                
                LOG.infof("Demande soumise avec succès: %s", demandeId);
                return demande;
                
            } catch (Exception e) {
                LOG.errorf(e, "Erreur lors de la soumission de la demande: %s", demandeId);
                throw new RuntimeException("Erreur lors de la soumission de la demande", e);
            }
        });
    }
    
    /**
     * Évalue une demande d'aide
     * 
     * @param demandeId ID de la demande
     * @param decision Décision d'évaluation (APPROUVEE, REJETEE, etc.)
     * @param commentaires Commentaires de l'évaluateur
     * @param montantApprouve Montant approuvé (si différent du montant demandé)
     * @return La demande évaluée
     */
    @Transactional
    public CompletableFuture<DemandeAideDTO> evaluerDemande(
            @NotBlank String demandeId,
            @NotNull StatutAide decision,
            String commentaires,
            Double montantApprouve) {
        
        LOG.infof("Évaluation de la demande: %s avec décision: %s", demandeId, decision);
        
        return CompletableFuture.supplyAsync(() -> {
            try {
                // 1. Récupérer la demande
                DemandeAideDTO demande = demandeAideService.obtenirParId(demandeId);
                
                // 2. Valider que la demande peut être évaluée
                if (!demande.getStatut().peutTransitionnerVers(decision)) {
                    throw new IllegalStateException(
                        String.format("Transition invalide de %s vers %s", 
                                     demande.getStatut(), decision));
                }
                
                // 3. Mise à jour de la demande
                demande.setCommentairesEvaluateur(commentaires);
                demande.setDateEvaluation(LocalDateTime.now());
                
                if (montantApprouve != null) {
                    demande.setMontantApprouve(montantApprouve);
                }
                
                // 4. Changement de statut
                demande = demandeAideService.changerStatut(demandeId, decision, commentaires);
                
                // 5. Actions spécifiques selon la décision
                switch (decision) {
                    case APPROUVEE, APPROUVEE_PARTIELLEMENT -> {
                        demande.setDateApprobation(LocalDateTime.now());
                        
                        // Recherche automatique de proposants si pas de montant spécifique
                        if (demande.getTypeAide().isFinancier() && autoMatchingEnabled) {
                            CompletableFuture.runAsync(() -> 
                                matchingService.rechercherProposantsFinanciers(demande));
                        }
                    }
                    case REJETEE -> {
                        // Enregistrement des raisons de rejet pour analytics
                        analyticsService.enregistrerRejetDemande(demande, commentaires);
                    }
                    case INFORMATIONS_REQUISES -> {
                        // Programmation d'un rappel
                        programmerRappelInformationsRequises(demande);
                    }
                }
                
                // 6. Notifications
                if (notificationEnabled) {
                    notificationService.notifierDecisionEvaluation(demande);
                }
                
                // 7. Mise à jour des analytics
                analyticsService.enregistrerEvaluationDemande(demande);
                
                LOG.infof("Demande évaluée avec succès: %s", demandeId);
                return demande;
                
            } catch (Exception e) {
                LOG.errorf(e, "Erreur lors de l'évaluation de la demande: %s", demandeId);
                throw new RuntimeException("Erreur lors de l'évaluation de la demande", e);
            }
        });
    }
    
    // === GESTION DES PROPOSITIONS D'AIDE ===
    
    /**
     * Crée une nouvelle proposition d'aide
     * 
     * @param propositionDTO La proposition d'aide à créer
     * @return La proposition d'aide créée
     */
    @Transactional
    public CompletableFuture<PropositionAideDTO> creerPropositionAide(@Valid PropositionAideDTO propositionDTO) {
        LOG.infof("Création d'une nouvelle proposition d'aide: %s", propositionDTO.getTitre());
        
        return CompletableFuture.supplyAsync(() -> {
            try {
                // 1. Créer la proposition
                PropositionAideDTO propositionCreee = propositionAideService.creerProposition(propositionDTO);
                
                // 2. Matching automatique avec les demandes existantes
                if (autoMatchingEnabled) {
                    CompletableFuture.runAsync(() -> {
                        try {
                            List<DemandeAideDTO> demandesCompatibles = 
                                matchingService.trouverDemandesCompatibles(propositionCreee);
                            
                            if (!demandesCompatibles.isEmpty()) {
                                LOG.infof("Trouvé %d demandes compatibles pour la proposition %s", 
                                         demandesCompatibles.size(), propositionCreee.getId());
                                
                                // Notification au proposant
                                if (notificationEnabled) {
                                    notificationService.notifierDemandesCompatibles(
                                        propositionCreee, demandesCompatibles);
                                }
                            }
                        } catch (Exception e) {
                            LOG.errorf(e, "Erreur lors du matching automatique pour la proposition %s", 
                                      propositionCreee.getId());
                        }
                    });
                }
                
                // 3. Notifications
                if (notificationEnabled) {
                    notificationService.notifierCreationProposition(propositionCreee);
                }
                
                // 4. Mise à jour des analytics
                analyticsService.enregistrerNouvelleProposition(propositionCreee);
                
                LOG.infof("Proposition d'aide créée avec succès: %s", propositionCreee.getId());
                return propositionCreee;
                
            } catch (Exception e) {
                LOG.errorf(e, "Erreur lors de la création de la proposition d'aide");
                throw new RuntimeException("Erreur lors de la création de la proposition d'aide", e);
            }
        });
    }

    /**
     * Obtient une proposition d'aide par son ID
     *
     * @param propositionId ID de la proposition
     * @return La proposition trouvée
     */
    public PropositionAideDTO obtenirPropositionAide(@NotBlank String propositionId) {
        LOG.debugf("Récupération de la proposition d'aide: %s", propositionId);

        try {
            return propositionAideService.obtenirParId(propositionId);
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de la récupération de la proposition: %s", propositionId);
            return null;
        }
    }

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

        try {
            return propositionAideService.rechercherAvecFiltres(filtres);
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de la recherche de propositions");
            return new ArrayList<>();
        }
    }

    /**
     * Trouve les propositions compatibles avec une demande
     *
     * @param demandeId ID de la demande
     * @return Liste des propositions compatibles
     */
    public List<PropositionAideDTO> trouverPropositionsCompatibles(@NotBlank String demandeId) {
        LOG.infof("Recherche de propositions compatibles pour la demande: %s", demandeId);

        try {
            DemandeAideDTO demande = demandeAideService.obtenirParId(demandeId);
            if (demande == null) {
                throw new IllegalArgumentException("Demande non trouvée: " + demandeId);
            }

            return matchingService.trouverPropositionsCompatibles(demande);
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de la recherche de propositions compatibles");
            return new ArrayList<>();
        }
    }

    /**
     * Trouve les demandes compatibles avec une proposition
     *
     * @param propositionId ID de la proposition
     * @return Liste des demandes compatibles
     */
    public List<DemandeAideDTO> trouverDemandesCompatibles(@NotBlank String propositionId) {
        LOG.infof("Recherche de demandes compatibles pour la proposition: %s", propositionId);

        try {
            PropositionAideDTO proposition = propositionAideService.obtenirParId(propositionId);
            if (proposition == null) {
                throw new IllegalArgumentException("Proposition non trouvée: " + propositionId);
            }

            return matchingService.trouverDemandesCompatibles(proposition);
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de la recherche de demandes compatibles");
            return new ArrayList<>();
        }
    }

    // === RECHERCHE ET FILTRAGE ===

    /**
     * Obtient une demande d'aide par son ID
     *
     * @param demandeId ID de la demande
     * @return La demande trouvée
     */
    public DemandeAideDTO obtenirDemandeAide(@NotBlank String demandeId) {
        LOG.debugf("Récupération de la demande d'aide: %s", demandeId);

        try {
            return demandeAideService.obtenirParId(demandeId);
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de la récupération de la demande: %s", demandeId);
            return null;
        }
    }

    /**
     * Met à jour une demande d'aide
     *
     * @param demandeDTO La demande à mettre à jour
     * @return La demande mise à jour
     */
    @Transactional
    public DemandeAideDTO mettreAJourDemandeAide(@Valid DemandeAideDTO demandeDTO) {
        LOG.infof("Mise à jour de la demande d'aide: %s", demandeDTO.getId());

        try {
            return demandeAideService.mettreAJour(demandeDTO);
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de la mise à jour de la demande: %s", demandeDTO.getId());
            throw new RuntimeException("Erreur lors de la mise à jour de la demande", e);
        }
    }

    /**
     * Évalue une demande d'aide (version synchrone pour l'API REST)
     *
     * @param demandeId ID de la demande
     * @param evaluateurId ID de l'évaluateur
     * @param decision Décision d'évaluation
     * @param commentaire Commentaire de l'évaluateur
     * @param montantApprouve Montant approuvé
     * @return La demande évaluée
     */
    @Transactional
    public DemandeAideDTO evaluerDemande(@NotBlank String demandeId,
                                        @NotBlank String evaluateurId,
                                        @NotNull StatutAide decision,
                                        String commentaire,
                                        Double montantApprouve) {
        LOG.infof("Évaluation synchrone de la demande: %s par: %s", demandeId, evaluateurId);

        try {
            // Utilisation de la version asynchrone et attente du résultat
            return evaluerDemande(demandeId, decision, commentaire, montantApprouve).get();
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de l'évaluation synchrone de la demande: %s", demandeId);
            throw new RuntimeException("Erreur lors de l'évaluation de la demande", e);
        }
    }

    /**
     * Soumet une demande d'aide (version synchrone pour l'API REST)
     *
     * @param demandeId ID de la demande
     * @return La demande soumise
     */
    @Transactional
    public DemandeAideDTO soumettreDemande(@NotBlank String demandeId) {
        LOG.infof("Soumission synchrone de la demande: %s", demandeId);

        try {
            // Utilisation de la version asynchrone et attente du résultat
            return soumettreDemandeeAsync(demandeId).get();
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de la soumission synchrone de la demande: %s", demandeId);
            throw new RuntimeException("Erreur lors de la soumission de la demande", e);
        }
    }

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

        try {
            return demandeAideService.rechercherAvecFiltres(filtres);
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de la recherche de demandes");
            return new ArrayList<>();
        }
    }

    /**
     * Obtient les demandes urgentes nécessitant une attention immédiate
     *
     * @param organisationId ID de l'organisation
     * @return Liste des demandes urgentes
     */
    public List<DemandeAideDTO> obtenirDemandesUrgentes(String organisationId) {
        LOG.debugf("Récupération des demandes urgentes pour l'organisation: %s", organisationId);

        try {
            return demandeAideService.obtenirDemandesUrgentes(organisationId);
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors de la récupération des demandes urgentes");
            return new ArrayList<>();
        }
    }

    /**
     * Obtient les statistiques de solidarité
     *
     * @param organisationId ID de l'organisation
     * @return Map des statistiques
     */
    public Map<String, Object> obtenirStatistiquesSolidarite(String organisationId) {
        LOG.debugf("Calcul des statistiques de solidarité pour: %s", organisationId);

        try {
            return analyticsService.calculerStatistiquesSolidarite(organisationId);
        } catch (Exception e) {
            LOG.errorf(e, "Erreur lors du calcul des statistiques");
            return new HashMap<>();
        }
    }

    // === MÉTHODES UTILITAIRES PRIVÉES ===

    /**
     * Valide une demande avant soumission
     */
    private void validerDemandeAvantSoumission(DemandeAideDTO demande) {
        List<String> erreurs = new ArrayList<>();

        if (demande.getTitre() == null || demande.getTitre().trim().isEmpty()) {
            erreurs.add("Le titre est obligatoire");
        }

        if (demande.getDescription() == null || demande.getDescription().trim().isEmpty()) {
            erreurs.add("La description est obligatoire");
        }

        if (demande.getTypeAide().isNecessiteMontant() && demande.getMontantDemande() == null) {
            erreurs.add("Le montant est obligatoire pour ce type d'aide");
        }

        if (demande.getMontantDemande() != null &&
            !demande.getTypeAide().isMontantValide(demande.getMontantDemande())) {
            erreurs.add("Le montant demandé n'est pas dans la fourchette autorisée");
        }

        if (!erreurs.isEmpty()) {
            throw new IllegalArgumentException("Erreurs de validation: " + String.join(", ", erreurs));
        }
    }

    /**
     * Programme les rappels automatiques pour une demande
     */
    private void programmerRappelsAutomatiques(DemandeAideDTO demande) {
        if (!notificationEnabled) return;

        try {
            // Rappel à 50% du délai
            LocalDateTime rappel50 = demande.getDateCreation()
                .plusHours(demande.getPriorite().getDelaiTraitementHeures() / 2);

            // Rappel à 80% du délai
            LocalDateTime rappel80 = demande.getDateCreation()
                .plusHours((long) (demande.getPriorite().getDelaiTraitementHeures() * 0.8));

            // Rappel de dépassement
            LocalDateTime rappelDepassement = demande.getDateLimiteTraitement().plusHours(1);

            notificationService.programmerRappels(demande, rappel50, rappel80, rappelDepassement);

        } catch (Exception e) {
            LOG.warnf(e, "Erreur lors de la programmation des rappels pour la demande %s",
                     demande.getId());
        }
    }

    /**
     * Programme un rappel pour les informations requises
     */
    private void programmerRappelInformationsRequises(DemandeAideDTO demande) {
        if (!notificationEnabled) return;

        try {
            // Rappel dans 48h si pas de réponse
            LocalDateTime rappel = LocalDateTime.now().plusHours(48);
            notificationService.programmerRappelInformationsRequises(demande, rappel);

        } catch (Exception e) {
            LOG.warnf(e, "Erreur lors de la programmation du rappel d'informations pour la demande %s",
                     demande.getId());
        }
    }
}
