package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.solidarite.request.CreatePropositionAideRequest;
import dev.lions.unionflow.server.api.dto.solidarite.request.UpdatePropositionAideRequest;
import dev.lions.unionflow.server.api.dto.solidarite.response.PropositionAideResponse;
import dev.lions.unionflow.server.api.enums.solidarite.StatutProposition;
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, PropositionAideResponse> cachePropositionsActives = new HashMap<>();
  private final Map<TypeAide, List<PropositionAideResponse>> indexParType = new HashMap<>();

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

  /**
   * Crée une nouvelle proposition d'aide
   *
   * @param request La requête de création
   * @return La proposition créée
   */
  @Transactional
  public PropositionAideResponse creerProposition(@Valid CreatePropositionAideRequest request) {
    LOG.infof("Création d'une nouvelle proposition d'aide: %s", request.titre());

    PropositionAideResponse response = new PropositionAideResponse();
    response.setId(UUID.randomUUID());
    response.setNumeroReference(genererNumeroReference());

    response.setTypeAide(request.typeAide());
    response.setTitre(request.titre());
    response.setDescription(request.description());
    response.setConditions(request.conditions());
    response.setMontantMaximum(request.montantMaximum());
    response.setNombreMaxBeneficiaires(request.nombreMaxBeneficiaires() != null ? request.nombreMaxBeneficiaires() : 1);
    response.setDevise(request.devise() != null ? request.devise() : "FCFA");
    response.setProposantId(request.proposantId());
    response.setOrganisationId(request.organisationId());
    response.setDemandeAideId(request.demandeAideId());
    response.setDelaiReponseHeures(request.delaiReponseHeures() != null ? request.delaiReponseHeures() : 72);

    // Initialisation des dates
    LocalDateTime maintenant = LocalDateTime.now();
    response.setDateCreation(maintenant);
    response.setDateModification(maintenant);
    response.setDateExpiration(request.dateExpiration() != null ? request.dateExpiration() : maintenant.plusMonths(6));

    // Statut initial
    response.setStatut(StatutProposition.ACTIVE);
    response.setEstDisponible(true);

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

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

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

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

  /**
   * Met à jour une proposition d'aide existante
   *
   * @param id      Identifiant de la proposition
   * @param request La requête de mise à jour
   * @return La proposition mise à jour
   */
  @Transactional
  public PropositionAideResponse mettreAJour(@NotBlank String id, @Valid UpdatePropositionAideRequest request) {
    LOG.infof("Mise à jour de la proposition d'aide: %s", id);

    PropositionAideResponse response = cachePropositionsActives.get(id);
    if (response == null) {
      // Si non trouvé dans le cache, essayer de simuler depuis BDD
      response = simulerRecuperationBDD(id);
    }
    if (response == null) {
      throw new IllegalArgumentException("Proposition non trouvée: " + id);
    }

    if (request.titre() != null)
      response.setTitre(request.titre());
    if (request.description() != null)
      response.setDescription(request.description());
    if (request.conditions() != null)
      response.setConditions(request.conditions());
    if (request.montantMaximum() != null)
      response.setMontantMaximum(request.montantMaximum());
    if (request.nombreMaxBeneficiaires() != null)
      response.setNombreMaxBeneficiaires(request.nombreMaxBeneficiaires());
    if (request.statut() != null)
      response.setStatut(request.statut());
    if (request.estDisponible() != null)
      response.setEstDisponible(request.estDisponible());
    if (request.dateExpiration() != null)
      response.setDateExpiration(request.dateExpiration());

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

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

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

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

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

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

    // Simulation de récupération depuis la base de données
    // simulerRecuperationBDD retourne toujours null (stub — à remplacer par un vrai repository)
    return simulerRecuperationBDD(id);
  }

  /**
   * 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 PropositionAideResponse changerStatutActivation(
      @NotBlank String propositionId, boolean activer) {
    LOG.infof(
        "Changement de statut d'activation pour la proposition %s: %s",
        propositionId, activer ? "ACTIVE" : "SUSPENDUE");

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

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

    response.setDateModification(LocalDateTime.now());

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

    return response;
  }

  // === 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<PropositionAideResponse> rechercherPropositionsCompatibles(
      dev.lions.unionflow.server.api.dto.solidarite.response.DemandeAideResponse demande) {
    LOG.debugf("Recherche de propositions compatibles pour la demande: %s", demande.getId());

    // Recherche par type d'aide d'abord
    List<PropositionAideResponse> 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(PropositionAideResponse::isActiveEtDisponible)
        .filter(p -> p.peutAccepterBeneficiaires())
        .map(
            p -> {
              // En attendant MatchingService refactoré, on simule le scoring
              double score = 50.0;
              if (p.getTypeAide() == demande.getTypeAide())
                score += 20;

              // 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;
            })
        .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<PropositionAideResponse> 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<PropositionAideResponse> obtenirPropositionsActives(TypeAide typeAide) {
    LOG.debugf("Récupération des propositions actives pour le type: %s", typeAide);

    return indexParType.getOrDefault(typeAide, new ArrayList<>()).stream()
        .filter(PropositionAideResponse::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<PropositionAideResponse> obtenirMeilleuresPropositions(int limite) {
    LOG.debugf("Récupération des %d meilleures propositions", limite);

    return cachePropositionsActives.values().stream()
        .filter(PropositionAideResponse::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 PropositionAideResponse mettreAJourStatistiques(
      @NotBlank String propositionId, Double montantVerse, int nombreBeneficiaires) {
    LOG.infof("Mise à jour des statistiques pour la proposition: %s", propositionId);

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

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

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

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

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

    response.setDateModification(LocalDateTime.now());

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

    return response;
  }

  // === 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(PropositionAideResponse 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() != null ? proposition.getNombreBeneficiairesAides() : 0) * 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() == null || 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(
      PropositionAideResponse 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 (!java.util.Objects.equals(proposition.getOrganisationId(), 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(PropositionAideResponse p1, PropositionAideResponse 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(PropositionAideResponse response) {
    cachePropositionsActives.put(response.getId().toString(), response);
  }

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

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

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

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

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