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.enums.solidarite.TypeAide;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import org.jboss.logging.Logger;

/**
 * Service spécialisé pour la gestion des propositions d'aide
 *
 * <p>Ce service gère le cycle de vie des propositions d'aide : création, activation, matching,
 * suivi des performances.
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-16
 */
@ApplicationScoped
public class PropositionAideService {

  private static final Logger LOG = Logger.getLogger(PropositionAideService.class);

  // Cache pour les propositions actives
  private final Map<String, PropositionAideDTO> cachePropositionsActives = new HashMap<>();
  private final Map<TypeAide, List<PropositionAideDTO>> indexParType = new HashMap<>();

  // === OPÉRATIONS CRUD ===

  /**
   * Crée une nouvelle proposition d'aide
   *
   * @param propositionDTO La proposition à créer
   * @return La proposition créée avec ID généré
   */
  @Transactional
  public PropositionAideDTO creerProposition(@Valid PropositionAideDTO propositionDTO) {
    LOG.infof("Création d'une nouvelle proposition d'aide: %s", propositionDTO.getTitre());

    // Génération des identifiants
    propositionDTO.setId(UUID.randomUUID().toString());
    propositionDTO.setNumeroReference(genererNumeroReference());

    // Initialisation des dates
    LocalDateTime maintenant = LocalDateTime.now();
    propositionDTO.setDateCreation(maintenant);
    propositionDTO.setDateModification(maintenant);

    // Statut initial
    if (propositionDTO.getStatut() == null) {
      propositionDTO.setStatut(PropositionAideDTO.StatutProposition.ACTIVE);
    }

    // Calcul de la date d'expiration si non définie
    if (propositionDTO.getDateExpiration() == null) {
      propositionDTO.setDateExpiration(maintenant.plusMonths(6)); // 6 mois par défaut
    }

    // Initialisation des compteurs
    propositionDTO.setNombreDemandesTraitees(0);
    propositionDTO.setNombreBeneficiairesAides(0);
    propositionDTO.setMontantTotalVerse(0.0);
    propositionDTO.setNombreVues(0);
    propositionDTO.setNombreCandidatures(0);
    propositionDTO.setNombreEvaluations(0);

    // Calcul du score de pertinence initial
    propositionDTO.setScorePertinence(calculerScorePertinence(propositionDTO));

    // Ajout au cache et index
    ajouterAuCache(propositionDTO);
    ajouterAIndex(propositionDTO);

    LOG.infof("Proposition d'aide créée avec succès: %s", propositionDTO.getId());
    return propositionDTO;
  }

  /**
   * Met à jour une proposition d'aide existante
   *
   * @param propositionDTO La proposition à mettre à jour
   * @return La proposition mise à jour
   */
  @Transactional
  public PropositionAideDTO mettreAJour(@Valid PropositionAideDTO propositionDTO) {
    LOG.infof("Mise à jour de la proposition d'aide: %s", propositionDTO.getId());

    // Mise à jour de la date de modification
    propositionDTO.setDateModification(LocalDateTime.now());

    // Recalcul du score de pertinence
    propositionDTO.setScorePertinence(calculerScorePertinence(propositionDTO));

    // Mise à jour du cache et index
    ajouterAuCache(propositionDTO);
    mettreAJourIndex(propositionDTO);

    LOG.infof("Proposition d'aide mise à jour avec succès: %s", propositionDTO.getId());
    return propositionDTO;
  }

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

    // Vérification du cache
    PropositionAideDTO propositionCachee = cachePropositionsActives.get(id);
    if (propositionCachee != null) {
      // Incrémenter le nombre de vues
      propositionCachee.setNombreVues(propositionCachee.getNombreVues() + 1);
      return propositionCachee;
    }

    // Simulation de récupération depuis la base de données
    PropositionAideDTO proposition = simulerRecuperationBDD(id);

    if (proposition != null) {
      ajouterAuCache(proposition);
      ajouterAIndex(proposition);
    }

    return proposition;
  }

  /**
   * Active ou désactive une proposition d'aide
   *
   * @param propositionId ID de la proposition
   * @param activer true pour activer, false pour désactiver
   * @return La proposition mise à jour
   */
  @Transactional
  public PropositionAideDTO changerStatutActivation(
      @NotBlank String propositionId, boolean activer) {
    LOG.infof(
        "Changement de statut d'activation pour la proposition %s: %s",
        propositionId, activer ? "ACTIVE" : "SUSPENDUE");

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

    if (activer) {
      // Vérifications avant activation
      if (proposition.isExpiree()) {
        throw new IllegalStateException("Impossible d'activer une proposition expirée");
      }
      proposition.setStatut(PropositionAideDTO.StatutProposition.ACTIVE);
      proposition.setEstDisponible(true);
    } else {
      proposition.setStatut(PropositionAideDTO.StatutProposition.SUSPENDUE);
      proposition.setEstDisponible(false);
    }

    proposition.setDateModification(LocalDateTime.now());

    // Mise à jour du cache et index
    ajouterAuCache(proposition);
    mettreAJourIndex(proposition);

    return proposition;
  }

  // === RECHERCHE ET MATCHING ===

  /**
   * Recherche des propositions compatibles avec une demande
   *
   * @param demande La demande d'aide
   * @return Liste des propositions compatibles triées par score
   */
  public List<PropositionAideDTO> rechercherPropositionsCompatibles(DemandeAideDTO demande) {
    LOG.debugf("Recherche de propositions compatibles pour la demande: %s", demande.getId());

    // Recherche par type d'aide d'abord
    List<PropositionAideDTO> candidats =
        indexParType.getOrDefault(demande.getTypeAide(), new ArrayList<>());

    // Si pas de correspondance exacte, chercher dans la même catégorie
    if (candidats.isEmpty()) {
      candidats =
          cachePropositionsActives.values().stream()
              .filter(
                  p -> p.getTypeAide().getCategorie().equals(demande.getTypeAide().getCategorie()))
              .collect(Collectors.toList());
    }

    // Filtrage et scoring
    return candidats.stream()
        .filter(PropositionAideDTO::isActiveEtDisponible)
        .filter(p -> p.peutAccepterBeneficiaires())
        .map(
            p -> {
              double score = p.getScoreCompatibilite(demande);
              // Stocker le score temporairement dans les données personnalisées
              if (p.getDonneesPersonnalisees() == null) {
                p.setDonneesPersonnalisees(new HashMap<>());
              }
              p.getDonneesPersonnalisees().put("scoreCompatibilite", score);
              return p;
            })
        .filter(p -> (Double) p.getDonneesPersonnalisees().get("scoreCompatibilite") >= 30.0)
        .sorted(
            (p1, p2) -> {
              Double score1 = (Double) p1.getDonneesPersonnalisees().get("scoreCompatibilite");
              Double score2 = (Double) p2.getDonneesPersonnalisees().get("scoreCompatibilite");
              return Double.compare(score2, score1); // Ordre décroissant
            })
        .limit(10) // Limiter à 10 meilleures propositions
        .collect(Collectors.toList());
  }

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

    return cachePropositionsActives.values().stream()
        .filter(proposition -> correspondAuxFiltres(proposition, filtres))
        .sorted(this::comparerParPertinence)
        .collect(Collectors.toList());
  }

  /**
   * Obtient les propositions actives pour un type d'aide
   *
   * @param typeAide Type d'aide recherché
   * @return Liste des propositions actives
   */
  public List<PropositionAideDTO> obtenirPropositionsActives(TypeAide typeAide) {
    LOG.debugf("Récupération des propositions actives pour le type: %s", typeAide);

    return indexParType.getOrDefault(typeAide, new ArrayList<>()).stream()
        .filter(PropositionAideDTO::isActiveEtDisponible)
        .sorted(this::comparerParPertinence)
        .collect(Collectors.toList());
  }

  /**
   * Obtient les meilleures propositions (top performers)
   *
   * @param limite Nombre maximum de propositions à retourner
   * @return Liste des meilleures propositions
   */
  public List<PropositionAideDTO> obtenirMeilleuresPropositions(int limite) {
    LOG.debugf("Récupération des %d meilleures propositions", limite);

    return cachePropositionsActives.values().stream()
        .filter(PropositionAideDTO::isActiveEtDisponible)
        .filter(p -> p.getNombreEvaluations() >= 3) // Au moins 3 évaluations
        .filter(p -> p.getNoteMoyenne() != null && p.getNoteMoyenne() >= 4.0)
        .sorted(
            (p1, p2) -> {
              // Tri par note moyenne puis par nombre d'aides réalisées
              int compareNote = Double.compare(p2.getNoteMoyenne(), p1.getNoteMoyenne());
              if (compareNote != 0) return compareNote;
              return Integer.compare(
                  p2.getNombreBeneficiairesAides(), p1.getNombreBeneficiairesAides());
            })
        .limit(limite)
        .collect(Collectors.toList());
  }

  // === GESTION DES PERFORMANCES ===

  /**
   * Met à jour les statistiques d'une proposition après une aide fournie
   *
   * @param propositionId ID de la proposition
   * @param montantVerse Montant versé (si applicable)
   * @param nombreBeneficiaires Nombre de bénéficiaires aidés
   * @return La proposition mise à jour
   */
  @Transactional
  public PropositionAideDTO mettreAJourStatistiques(
      @NotBlank String propositionId, Double montantVerse, int nombreBeneficiaires) {
    LOG.infof("Mise à jour des statistiques pour la proposition: %s", propositionId);

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

    // Mise à jour des compteurs
    proposition.setNombreDemandesTraitees(proposition.getNombreDemandesTraitees() + 1);
    proposition.setNombreBeneficiairesAides(
        proposition.getNombreBeneficiairesAides() + nombreBeneficiaires);

    if (montantVerse != null) {
      proposition.setMontantTotalVerse(proposition.getMontantTotalVerse() + montantVerse);
    }

    // Recalcul du score de pertinence
    proposition.setScorePertinence(calculerScorePertinence(proposition));

    // Vérification si la capacité maximale est atteinte
    if (proposition.getNombreBeneficiairesAides() >= proposition.getNombreMaxBeneficiaires()) {
      proposition.setEstDisponible(false);
      proposition.setStatut(PropositionAideDTO.StatutProposition.TERMINEE);
    }

    proposition.setDateModification(LocalDateTime.now());

    // Mise à jour du cache
    ajouterAuCache(proposition);

    return proposition;
  }

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

  /** Génère un numéro de référence unique pour les propositions */
  private String genererNumeroReference() {
    int annee = LocalDateTime.now().getYear();
    int numero = (int) (Math.random() * 999999) + 1;
    return String.format("PA-%04d-%06d", annee, numero);
  }

  /** Calcule le score de pertinence d'une proposition */
  private double calculerScorePertinence(PropositionAideDTO proposition) {
    double score = 50.0; // Score de base

    // Bonus pour l'expérience (nombre d'aides réalisées)
    score += Math.min(20.0, proposition.getNombreBeneficiairesAides() * 2.0);

    // Bonus pour la note moyenne
    if (proposition.getNoteMoyenne() != null) {
      score += (proposition.getNoteMoyenne() - 3.0) * 10.0; // +10 par point au-dessus de 3
    }

    // Bonus pour la récence
    long joursDepuisCreation =
        java.time.Duration.between(proposition.getDateCreation(), LocalDateTime.now()).toDays();
    if (joursDepuisCreation <= 30) {
      score += 10.0;
    } else if (joursDepuisCreation <= 90) {
      score += 5.0;
    }

    // Bonus pour la disponibilité
    if (proposition.isActiveEtDisponible()) {
      score += 15.0;
    }

    // Malus pour l'inactivité
    if (proposition.getNombreVues() == 0) {
      score -= 10.0;
    }

    return Math.max(0.0, Math.min(100.0, score));
  }

  /** Vérifie si une proposition correspond aux filtres */
  private boolean correspondAuxFiltres(
      PropositionAideDTO proposition, Map<String, Object> filtres) {
    for (Map.Entry<String, Object> filtre : filtres.entrySet()) {
      String cle = filtre.getKey();
      Object valeur = filtre.getValue();

      switch (cle) {
        case "typeAide" -> {
          if (!proposition.getTypeAide().equals(valeur)) return false;
        }
        case "statut" -> {
          if (!proposition.getStatut().equals(valeur)) return false;
        }
        case "proposantId" -> {
          if (!proposition.getProposantId().equals(valeur)) return false;
        }
        case "organisationId" -> {
          if (!proposition.getOrganisationId().equals(valeur)) return false;
        }
        case "estDisponible" -> {
          if (!proposition.getEstDisponible().equals(valeur)) return false;
        }
        case "montantMaximum" -> {
          if (proposition.getMontantMaximum() == null
              || proposition.getMontantMaximum().compareTo(BigDecimal.valueOf((Double) valeur)) < 0) return false;
        }
      }
    }
    return true;
  }

  /** Compare deux propositions par pertinence */
  private int comparerParPertinence(PropositionAideDTO p1, PropositionAideDTO p2) {
    // D'abord par score de pertinence (plus haut = meilleur)
    int compareScore = Double.compare(p2.getScorePertinence(), p1.getScorePertinence());
    if (compareScore != 0) return compareScore;

    // Puis par date de création (plus récent = meilleur)
    return p2.getDateCreation().compareTo(p1.getDateCreation());
  }

  // === GESTION DU CACHE ET INDEX ===

  private void ajouterAuCache(PropositionAideDTO proposition) {
    cachePropositionsActives.put(proposition.getId(), proposition);
  }

  private void ajouterAIndex(PropositionAideDTO proposition) {
    indexParType
        .computeIfAbsent(proposition.getTypeAide(), k -> new ArrayList<>())
        .add(proposition);
  }

  private void mettreAJourIndex(PropositionAideDTO proposition) {
    // Supprimer de tous les index
    indexParType
        .values()
        .forEach(liste -> liste.removeIf(p -> p.getId().equals(proposition.getId())));

    // Ré-ajouter si la proposition est active
    if (proposition.isActiveEtDisponible()) {
      ajouterAIndex(proposition);
    }
  }

  // === MÉTHODES DE SIMULATION (À REMPLACER PAR DE VRAIS REPOSITORIES) ===

  private PropositionAideDTO simulerRecuperationBDD(String id) {
    // Simulation - dans une vraie implémentation, ceci ferait appel au repository
    return null;
  }
}
