package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.solidarite.DemandeAideDTO;
import dev.lions.unionflow.server.api.dto.solidarite.HistoriqueStatutDTO;
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
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.jboss.logging.Logger;

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

/**
 * Service spécialisé pour la gestion des demandes d'aide
 * 
 * Ce service gère le cycle de vie complet des demandes d'aide :
 * création, validation, changements de statut, recherche et suivi.
 * 
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-16
 */
@ApplicationScoped
public class DemandeAideService {
    
    private static final Logger LOG = Logger.getLogger(DemandeAideService.class);
    
    // Cache en mémoire pour les demandes fréquemment consultées
    private final Map<String, DemandeAideDTO> cacheDemandesRecentes = new HashMap<>();
    private final Map<String, LocalDateTime> cacheTimestamps = new HashMap<>();
    private static final long CACHE_DURATION_MINUTES = 15;
    
    // === OPÉRATIONS CRUD ===
    
    /**
     * Crée une nouvelle demande d'aide
     * 
     * @param demandeDTO La demande à créer
     * @return La demande créée avec ID généré
     */
    @Transactional
    public DemandeAideDTO creerDemande(@Valid DemandeAideDTO demandeDTO) {
        LOG.infof("Création d'une nouvelle demande d'aide: %s", demandeDTO.getTitre());
        
        // Génération des identifiants
        demandeDTO.setId(UUID.randomUUID().toString());
        demandeDTO.setNumeroReference(genererNumeroReference());
        
        // Initialisation des dates
        LocalDateTime maintenant = LocalDateTime.now();
        demandeDTO.setDateCreation(maintenant);
        demandeDTO.setDateModification(maintenant);
        
        // Statut initial
        if (demandeDTO.getStatut() == null) {
            demandeDTO.setStatut(StatutAide.BROUILLON);
        }
        
        // Priorité par défaut si non définie
        if (demandeDTO.getPriorite() == null) {
            demandeDTO.setPriorite(PrioriteAide.NORMALE);
        }
        
        // Initialisation de l'historique
        HistoriqueStatutDTO historiqueInitial = HistoriqueStatutDTO.builder()
                .id(UUID.randomUUID().toString())
                .ancienStatut(null)
                .nouveauStatut(demandeDTO.getStatut())
                .dateChangement(maintenant)
                .auteurId(demandeDTO.getDemandeurId())
                .motif("Création de la demande")
                .estAutomatique(true)
                .build();
        
        demandeDTO.setHistoriqueStatuts(List.of(historiqueInitial));
        
        // Calcul du score de priorité
        demandeDTO.setScorePriorite(calculerScorePriorite(demandeDTO));
        
        // Sauvegarde en cache
        ajouterAuCache(demandeDTO);
        
        LOG.infof("Demande d'aide créée avec succès: %s", demandeDTO.getId());
        return demandeDTO;
    }
    
    /**
     * Met à jour une demande d'aide existante
     * 
     * @param demandeDTO La demande à mettre à jour
     * @return La demande mise à jour
     */
    @Transactional
    public DemandeAideDTO mettreAJour(@Valid DemandeAideDTO demandeDTO) {
        LOG.infof("Mise à jour de la demande d'aide: %s", demandeDTO.getId());
        
        // Vérification que la demande peut être modifiée
        if (!demandeDTO.isModifiable()) {
            throw new IllegalStateException("Cette demande ne peut plus être modifiée");
        }
        
        // Mise à jour de la date de modification
        demandeDTO.setDateModification(LocalDateTime.now());
        demandeDTO.setVersion(demandeDTO.getVersion() + 1);
        
        // Recalcul du score de priorité
        demandeDTO.setScorePriorite(calculerScorePriorite(demandeDTO));
        
        // Mise à jour du cache
        ajouterAuCache(demandeDTO);
        
        LOG.infof("Demande d'aide mise à jour avec succès: %s", demandeDTO.getId());
        return demandeDTO;
    }
    
    /**
     * Obtient une demande d'aide par son ID
     * 
     * @param id ID de la demande
     * @return La demande trouvée
     */
    public DemandeAideDTO obtenirParId(@NotBlank String id) {
        LOG.debugf("Récupération de la demande d'aide: %s", id);
        
        // Vérification du cache
        DemandeAideDTO demandeCachee = obtenirDuCache(id);
        if (demandeCachee != null) {
            LOG.debugf("Demande trouvée dans le cache: %s", id);
            return demandeCachee;
        }
        
        // Simulation de récupération depuis la base de données
        // Dans une vraie implémentation, ceci ferait appel au repository
        DemandeAideDTO demande = simulerRecuperationBDD(id);
        
        if (demande != null) {
            ajouterAuCache(demande);
        }
        
        return demande;
    }
    
    /**
     * Change le statut d'une demande d'aide
     * 
     * @param demandeId ID de la demande
     * @param nouveauStatut Nouveau statut
     * @param motif Motif du changement
     * @return La demande avec le nouveau statut
     */
    @Transactional
    public DemandeAideDTO changerStatut(@NotBlank String demandeId, 
                                       @NotNull StatutAide nouveauStatut, 
                                       String motif) {
        LOG.infof("Changement de statut pour la demande %s: %s", demandeId, nouveauStatut);
        
        DemandeAideDTO demande = obtenirParId(demandeId);
        if (demande == null) {
            throw new IllegalArgumentException("Demande non trouvée: " + demandeId);
        }
        
        StatutAide ancienStatut = demande.getStatut();
        
        // Validation de la transition
        if (!ancienStatut.peutTransitionnerVers(nouveauStatut)) {
            throw new IllegalStateException(
                String.format("Transition invalide de %s vers %s", ancienStatut, nouveauStatut));
        }
        
        // Mise à jour du statut
        demande.setStatut(nouveauStatut);
        demande.setDateModification(LocalDateTime.now());
        
        // Ajout à l'historique
        HistoriqueStatutDTO nouvelHistorique = HistoriqueStatutDTO.builder()
                .id(UUID.randomUUID().toString())
                .ancienStatut(ancienStatut)
                .nouveauStatut(nouveauStatut)
                .dateChangement(LocalDateTime.now())
                .motif(motif)
                .estAutomatique(false)
                .build();
        
        List<HistoriqueStatutDTO> historique = new ArrayList<>(demande.getHistoriqueStatuts());
        historique.add(nouvelHistorique);
        demande.setHistoriqueStatuts(historique);
        
        // Actions spécifiques selon le nouveau statut
        switch (nouveauStatut) {
            case SOUMISE -> demande.setDateSoumission(LocalDateTime.now());
            case APPROUVEE, APPROUVEE_PARTIELLEMENT -> demande.setDateApprobation(LocalDateTime.now());
            case VERSEE -> demande.setDateVersement(LocalDateTime.now());
            case CLOTUREE -> demande.setDateCloture(LocalDateTime.now());
        }
        
        // Mise à jour du cache
        ajouterAuCache(demande);
        
        LOG.infof("Statut changé avec succès pour la demande %s: %s -> %s", 
                 demandeId, ancienStatut, nouveauStatut);
        return demande;
    }
    
    // === RECHERCHE ET FILTRAGE ===
    
    /**
     * Recherche des demandes avec filtres
     * 
     * @param filtres Map des critères de recherche
     * @return Liste des demandes correspondantes
     */
    public List<DemandeAideDTO> rechercherAvecFiltres(Map<String, Object> filtres) {
        LOG.debugf("Recherche de demandes avec filtres: %s", filtres);
        
        // Simulation de recherche - dans une vraie implémentation,
        // ceci utiliserait des requêtes de base de données optimisées
        List<DemandeAideDTO> toutesLesDemandes = simulerRecuperationToutesLesDemandes();
        
        return toutesLesDemandes.stream()
                .filter(demande -> correspondAuxFiltres(demande, filtres))
                .sorted(this::comparerParPriorite)
                .collect(Collectors.toList());
    }
    
    /**
     * Obtient les demandes urgentes pour une organisation
     * 
     * @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: %s", organisationId);
        
        Map<String, Object> filtres = Map.of(
            "organisationId", organisationId,
            "priorite", List.of(PrioriteAide.CRITIQUE, PrioriteAide.URGENTE),
            "statut", List.of(StatutAide.SOUMISE, StatutAide.EN_ATTENTE, 
                             StatutAide.EN_COURS_EVALUATION, StatutAide.APPROUVEE)
        );
        
        return rechercherAvecFiltres(filtres);
    }
    
    /**
     * Obtient les demandes en retard (délai dépassé)
     * 
     * @param organisationId ID de l'organisation
     * @return Liste des demandes en retard
     */
    public List<DemandeAideDTO> obtenirDemandesEnRetard(String organisationId) {
        LOG.debugf("Récupération des demandes en retard pour: %s", organisationId);
        
        return simulerRecuperationToutesLesDemandes().stream()
                .filter(demande -> demande.getOrganisationId().equals(organisationId))
                .filter(DemandeAideDTO::isDelaiDepasse)
                .filter(demande -> !demande.isTerminee())
                .sorted(this::comparerParPriorite)
                .collect(Collectors.toList());
    }
    
    // === MÉTHODES UTILITAIRES PRIVÉES ===
    
    /**
     * Génère un numéro de référence unique
     */
    private String genererNumeroReference() {
        int annee = LocalDateTime.now().getYear();
        int numero = (int) (Math.random() * 999999) + 1;
        return String.format("DA-%04d-%06d", annee, numero);
    }
    
    /**
     * Calcule le score de priorité d'une demande
     */
    private double calculerScorePriorite(DemandeAideDTO demande) {
        double score = demande.getPriorite().getScorePriorite();
        
        // Bonus pour type d'aide urgent
        if (demande.getTypeAide().isUrgent()) {
            score -= 1.0;
        }
        
        // Bonus pour montant élevé (aide financière)
        if (demande.getTypeAide().isFinancier() && demande.getMontantDemande() != null) {
            if (demande.getMontantDemande() > 50000) {
                score -= 0.5;
            }
        }
        
        // Malus pour ancienneté
        long joursDepuisCreation = java.time.Duration.between(
            demande.getDateCreation(), LocalDateTime.now()).toDays();
        if (joursDepuisCreation > 7) {
            score += 0.3;
        }
        
        return Math.max(0.1, score);
    }
    
    /**
     * Vérifie si une demande correspond aux filtres
     */
    private boolean correspondAuxFiltres(DemandeAideDTO demande, Map<String, Object> filtres) {
        for (Map.Entry<String, Object> filtre : filtres.entrySet()) {
            String cle = filtre.getKey();
            Object valeur = filtre.getValue();
            
            switch (cle) {
                case "organisationId" -> {
                    if (!demande.getOrganisationId().equals(valeur)) return false;
                }
                case "typeAide" -> {
                    if (valeur instanceof List<?> liste) {
                        if (!liste.contains(demande.getTypeAide())) return false;
                    } else if (!demande.getTypeAide().equals(valeur)) {
                        return false;
                    }
                }
                case "statut" -> {
                    if (valeur instanceof List<?> liste) {
                        if (!liste.contains(demande.getStatut())) return false;
                    } else if (!demande.getStatut().equals(valeur)) {
                        return false;
                    }
                }
                case "priorite" -> {
                    if (valeur instanceof List<?> liste) {
                        if (!liste.contains(demande.getPriorite())) return false;
                    } else if (!demande.getPriorite().equals(valeur)) {
                        return false;
                    }
                }
                case "demandeurId" -> {
                    if (!demande.getDemandeurId().equals(valeur)) return false;
                }
            }
        }
        return true;
    }
    
    /**
     * Compare deux demandes par priorité
     */
    private int comparerParPriorite(DemandeAideDTO d1, DemandeAideDTO d2) {
        // D'abord par score de priorité (plus bas = plus prioritaire)
        int comparaisonScore = Double.compare(d1.getScorePriorite(), d2.getScorePriorite());
        if (comparaisonScore != 0) return comparaisonScore;
        
        // Puis par date de création (plus ancien = plus prioritaire)
        return d1.getDateCreation().compareTo(d2.getDateCreation());
    }
    
    // === GESTION DU CACHE ===
    
    private void ajouterAuCache(DemandeAideDTO demande) {
        cacheDemandesRecentes.put(demande.getId(), demande);
        cacheTimestamps.put(demande.getId(), LocalDateTime.now());
        
        // Nettoyage du cache si trop volumineux
        if (cacheDemandesRecentes.size() > 100) {
            nettoyerCache();
        }
    }
    
    private DemandeAideDTO obtenirDuCache(String id) {
        LocalDateTime timestamp = cacheTimestamps.get(id);
        if (timestamp == null) return null;
        
        // Vérification de l'expiration
        if (LocalDateTime.now().minusMinutes(CACHE_DURATION_MINUTES).isAfter(timestamp)) {
            cacheDemandesRecentes.remove(id);
            cacheTimestamps.remove(id);
            return null;
        }
        
        return cacheDemandesRecentes.get(id);
    }
    
    private void nettoyerCache() {
        LocalDateTime limite = LocalDateTime.now().minusMinutes(CACHE_DURATION_MINUTES);
        
        cacheTimestamps.entrySet().removeIf(entry -> entry.getValue().isBefore(limite));
        cacheDemandesRecentes.keySet().retainAll(cacheTimestamps.keySet());
    }
    
    // === MÉTHODES DE SIMULATION (À REMPLACER PAR DE VRAIS REPOSITORIES) ===
    
    private DemandeAideDTO simulerRecuperationBDD(String id) {
        // Simulation - dans une vraie implémentation, ceci ferait appel au repository
        return null;
    }
    
    private List<DemandeAideDTO> simulerRecuperationToutesLesDemandes() {
        // Simulation - dans une vraie implémentation, ceci ferait appel au repository
        return new ArrayList<>();
    }
}
