package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.dto.solidarite.request.CreateDemandeAideRequest;
import dev.lions.unionflow.server.api.dto.solidarite.request.UpdateDemandeAideRequest;
import dev.lions.unionflow.server.api.dto.solidarite.response.DemandeAideResponse;
import dev.lions.unionflow.server.api.dto.solidarite.HistoriqueStatutDTO;
import dev.lions.unionflow.server.api.enums.solidarite.PrioriteAide;
import dev.lions.unionflow.server.api.enums.solidarite.StatutAide;
import dev.lions.unionflow.server.entity.DemandeAide;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.mapper.DemandeAideMapper;
import dev.lions.unionflow.server.repository.DemandeAideRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Sort;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
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 demandes d'aide
 *
 * <p>
 * Ce service gère le cycle de vie complet des demandes d'aide : création,
 * validation,
 * changements de statut, recherche et suivi. Persistance via
 * DemandeAideRepository.
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-16
 */
@ApplicationScoped
public class DemandeAideService {

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

  @Inject
  DemandeAideRepository demandeAideRepository;
  @Inject
  DemandeAideMapper demandeAideMapper;
  @Inject
  MembreRepository membreRepository;
  @Inject
  OrganisationRepository organisationRepository;

  // Cache en mémoire pour les demandes fréquemment consultées
  private final Map<UUID, DemandeAideResponse> cacheDemandesRecentes = new HashMap<>();
  private final Map<UUID, LocalDateTime> cacheTimestamps = new HashMap<>();
  private static final long CACHE_DURATION_MINUTES = 15;

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

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

    Membre demandeur = null;
    if (request.membreDemandeurId() != null) {
      demandeur = membreRepository.findById(request.membreDemandeurId());
      if (demandeur == null) {
        throw new IllegalArgumentException("Membre demandeur non trouvé: " + request.membreDemandeurId());
      }
    }
    Organisation organisation = null;
    if (request.associationId() != null) {
      organisation = organisationRepository.findById(request.associationId());
      if (organisation == null) {
        throw new IllegalArgumentException("Organisation non trouvée: " + request.associationId());
      }
    }

    DemandeAide entity = demandeAideMapper.toEntity(request, demandeur, null, organisation);
    demandeAideRepository.persist(entity);

    DemandeAideResponse response = demandeAideMapper.toDTO(entity);
    response.setNumeroReference(genererNumeroReference());
    response.setScorePriorite(calculerScorePriorite(response));

    LocalDateTime maintenant = LocalDateTime.now();
    HistoriqueStatutDTO historiqueInitial = HistoriqueStatutDTO.builder()
        .id(UUID.randomUUID().toString())
        .ancienStatut(null)
        .nouveauStatut(response.getStatut())
        .dateChangement(maintenant)
        .auteurId(response.getMembreDemandeurId() != null ? response.getMembreDemandeurId().toString() : null)
        .motif("Création de la demande")
        .estAutomatique(true)
        .build();
    response.setHistoriqueStatuts(List.of(historiqueInitial));

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

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

    DemandeAide entity = demandeAideRepository.findById(id);
    if (entity == null) {
      throw new IllegalArgumentException("Demande non trouvée: " + id);
    }

    if (!entity.getStatut().permetModification()) {
      throw new IllegalStateException("Cette demande ne peut plus être modifiée");
    }

    demandeAideMapper.updateEntityFromDTO(entity, request);
    entity = demandeAideRepository.update(entity);

    DemandeAideResponse response = demandeAideMapper.toDTO(entity);
    response.setScorePriorite(calculerScorePriorite(response));
    ajouterAuCache(response);
    LOG.infof("Demande d'aide mise à jour avec succès: %s", response.getId());
    return response;
  }

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

    DemandeAideResponse demandeCachee = obtenirDuCache(id);
    if (demandeCachee != null) {
      LOG.debugf("Demande trouvée dans le cache: %s", id);
      return demandeCachee;
    }

    DemandeAide entity = demandeAideRepository.findById(id);
    DemandeAideResponse response = entity != null ? demandeAideMapper.toDTO(entity) : null;
    if (response != null) {
      ajouterAuCache(response);
    }
    return response;
  }

  /**
   * Change le statut d'une demande d'aide
   *
   * @param demandeId     UUID de la demande
   * @param nouveauStatut Nouveau statut
   * @param motif         Motif du changement
   * @return La demande avec le nouveau statut
   */
  @Transactional
  public DemandeAideResponse changerStatut(
      @NotNull UUID demandeId, @NotNull StatutAide nouveauStatut, String motif) {
    LOG.infof("Changement de statut pour la demande %s: %s", demandeId, nouveauStatut);

    DemandeAide entity = demandeAideRepository.findById(demandeId);
    if (entity == null) {
      throw new IllegalArgumentException("Demande non trouvée: " + demandeId);
    }
    StatutAide ancienStatut = entity.getStatut();
    if (!ancienStatut.peutTransitionnerVers(nouveauStatut)) {
      throw new IllegalStateException(
          String.format("Transition invalide de %s vers %s", ancienStatut, nouveauStatut));
    }

    entity.setStatut(nouveauStatut);
    if (motif != null && !motif.isBlank()) {
      entity.setCommentaireEvaluation(
          entity.getCommentaireEvaluation() != null
              ? entity.getCommentaireEvaluation() + "\n" + motif
              : motif);
    }
    LocalDateTime now = LocalDateTime.now();
    entity.setDateModification(now);
    switch (nouveauStatut) {
      case SOUMISE -> entity.setDateDemande(now);
      case APPROUVEE, APPROUVEE_PARTIELLEMENT -> entity.setDateEvaluation(now);
      case VERSEE -> entity.setDateVersement(now);
      default -> {
      }
    }
    entity = demandeAideRepository.update(entity);

    DemandeAideResponse response = demandeAideMapper.toDTO(entity);
    HistoriqueStatutDTO nouvelHistorique = HistoriqueStatutDTO.builder()
        .id(UUID.randomUUID().toString())
        .ancienStatut(ancienStatut)
        .nouveauStatut(nouveauStatut)
        .dateChangement(now)
        .motif(motif)
        .estAutomatique(false)
        .build();
    List<HistoriqueStatutDTO> historique = new ArrayList<>(
        response.getHistoriqueStatuts() != null ? response.getHistoriqueStatuts() : List.of());
    historique.add(nouvelHistorique);
    response.setHistoriqueStatuts(historique);
    ajouterAuCache(response);
    LOG.infof(
        "Statut changé avec succès pour la demande %s: %s -> %s",
        demandeId, ancienStatut, nouveauStatut);
    return response;
  }

  // === RECHERCHE ET FILTRAGE ===

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

    List<DemandeAideResponse> toutesLesDemandes = chargerToutesLesDemandesDepuisBDD();
    return toutesLesDemandes.stream()
        .filter(demande -> correspondAuxFiltres(demande, filtres))
        .sorted(this::comparerParPriorite)
        .collect(Collectors.toList());
  }

  /**
   * Obtient les demandes urgentes pour une organisation
   *
   * @param organisationId UUID de l'organisation
   * @return Liste des demandes urgentes
   */
  public List<DemandeAideResponse> obtenirDemandesUrgentes(UUID 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<DemandeAideResponse> obtenirDemandesEnRetard(UUID organisationId) {
    LOG.debugf("Récupération des demandes en retard pour: %s", organisationId);

    return chargerToutesLesDemandesDepuisBDD().stream()
        .filter(demande -> demande.getAssociationId() != null && demande.getAssociationId().equals(organisationId))
        .filter(DemandeAideResponse::estDelaiDepasse)
        .filter(demande -> !demande.estTerminee())
        .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(DemandeAideResponse 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().compareTo(new BigDecimal("50000")) > 0) {
        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(DemandeAideResponse 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.getAssociationId().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.getMembreDemandeurId() == null || !demande.getMembreDemandeurId().equals(valeur))
            return false;
        }
      }
    }
    return true;
  }

  /** Compare deux demandes par priorité */
  private int comparerParPriorite(DemandeAideResponse d1, DemandeAideResponse d2) {
    double s1 = d1.getScorePriorite() != null ? d1.getScorePriorite() : Double.MAX_VALUE;
    double s2 = d2.getScorePriorite() != null ? d2.getScorePriorite() : Double.MAX_VALUE;
    int comparaisonScore = Double.compare(s1, s2);
    if (comparaisonScore != 0)
      return comparaisonScore;

    LocalDateTime c1 = d1.getDateCreation() != null ? d1.getDateCreation() : LocalDateTime.MIN;
    LocalDateTime c2 = d2.getDateCreation() != null ? d2.getDateCreation() : LocalDateTime.MIN;
    return c1.compareTo(c2);
  }

  // === GESTION DU CACHE ===

  private void ajouterAuCache(DemandeAideResponse demande) {
    cacheDemandesRecentes.put(demande.getId(), demande);
    cacheTimestamps.put(demande.getId(), LocalDateTime.now());

    // Nettoyage du cache si trop volumineux
    if (cacheDemandesRecentes.size() > 100) {
      nettoyerCache();
    }
  }

  private DemandeAideResponse obtenirDuCache(UUID 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());
  }

  /**
   * Charge les demandes depuis la base avec une limite de 1000 enregistrements.
   * Log un avertissement si le résultat dépasse 500 éléments pour anticiper les risques OOM.
   */
  private List<DemandeAideResponse> chargerToutesLesDemandesDepuisBDD() {
    int limite = 1000;
    List<DemandeAide> entities = demandeAideRepository.findAll(
        Page.ofSize(limite),
        Sort.by("dateDemande", Sort.Direction.Descending)
    );
    if (entities.size() > 500) {
      LOG.warnf("chargerToutesLesDemandesDepuisBDD : %d demandes chargées en mémoire — risque OOM si la volumétrie continue de croître", entities.size());
    }
    return entities.stream()
        .map(demandeAideMapper::toDTO)
        .collect(Collectors.toList());
  }
}
