package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique;
import dev.lions.unionflow.server.repository.CotisationRepository;
import dev.lions.unionflow.server.repository.DemandeAideRepository;
import dev.lions.unionflow.server.repository.EvenementRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;

/**
 * Service spécialisé dans le calcul des KPI (Key Performance Indicators)
 *
 * <p>
 * Ce service fournit des méthodes optimisées pour calculer les indicateurs de
 * performance clés
 * de l'application UnionFlow.
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-16
 */
@ApplicationScoped
@Slf4j
public class KPICalculatorService {

  @Inject
  MembreRepository membreRepository;

  @Inject
  CotisationRepository cotisationRepository;

  @Inject
  EvenementRepository evenementRepository;

  @Inject
  DemandeAideRepository demandeAideRepository;

  /**
   * Calcule tous les KPI principaux pour une organisation
   *
   * @param organisationId L'ID de l'organisation
   * @param dateDebut      Date de début de la période
   * @param dateFin        Date de fin de la période
   * @return Map contenant tous les KPI calculés
   */
  public Map<TypeMetrique, BigDecimal> calculerTousLesKPI(
      UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
    log.info(
        "Calcul de tous les KPI pour l'organisation {} sur la période {} - {}",
        organisationId,
        dateDebut,
        dateFin);

    Map<TypeMetrique, BigDecimal> kpis = new HashMap<>();

    // KPI Membres
    kpis.put(
        TypeMetrique.NOMBRE_MEMBRES_ACTIFS,
        calculerKPIMembresActifs(organisationId, dateDebut, dateFin));
    kpis.put(
        TypeMetrique.NOMBRE_MEMBRES_INACTIFS,
        calculerKPIMembresInactifs(organisationId, dateDebut, dateFin));
    kpis.put(
        TypeMetrique.TAUX_CROISSANCE_MEMBRES,
        calculerKPITauxCroissanceMembres(organisationId, dateDebut, dateFin));
    kpis.put(
        TypeMetrique.MOYENNE_AGE_MEMBRES,
        calculerKPIMoyenneAgeMembres(organisationId, dateDebut, dateFin));

    // KPI Financiers
    kpis.put(
        TypeMetrique.TOTAL_COTISATIONS_COLLECTEES,
        calculerKPITotalCotisations(organisationId, dateDebut, dateFin));
    kpis.put(
        TypeMetrique.COTISATIONS_EN_ATTENTE,
        calculerKPICotisationsEnAttente(organisationId, dateDebut, dateFin));
    kpis.put(
        TypeMetrique.TAUX_RECOUVREMENT_COTISATIONS,
        calculerKPITauxRecouvrement(organisationId, dateDebut, dateFin));
    kpis.put(
        TypeMetrique.MOYENNE_COTISATION_MEMBRE,
        calculerKPIMoyenneCotisationMembre(organisationId, dateDebut, dateFin));

    // KPI Événements
    kpis.put(
        TypeMetrique.NOMBRE_EVENEMENTS_ORGANISES,
        calculerKPINombreEvenements(organisationId, dateDebut, dateFin));
    kpis.put(
        TypeMetrique.TAUX_PARTICIPATION_EVENEMENTS,
        calculerKPITauxParticipation(organisationId, dateDebut, dateFin));
    kpis.put(
        TypeMetrique.MOYENNE_PARTICIPANTS_EVENEMENT,
        calculerKPIMoyenneParticipants(organisationId, dateDebut, dateFin));

    // KPI Solidarité
    kpis.put(
        TypeMetrique.NOMBRE_DEMANDES_AIDE,
        calculerKPINombreDemandesAide(organisationId, dateDebut, dateFin));
    kpis.put(
        TypeMetrique.MONTANT_AIDES_ACCORDEES,
        calculerKPIMontantAides(organisationId, dateDebut, dateFin));
    kpis.put(
        TypeMetrique.TAUX_APPROBATION_AIDES,
        calculerKPITauxApprobationAides(organisationId, dateDebut, dateFin));

    log.info("Calcul terminé : {} KPI calculés", kpis.size());
    return kpis;
  }

  /**
   * Calcule le KPI de performance globale de l'organisation
   *
   * @param organisationId L'ID de l'organisation
   * @param dateDebut      Date de début de la période
   * @param dateFin        Date de fin de la période
   * @return Score de performance global (0-100)
   */
  public BigDecimal calculerKPIPerformanceGlobale(
      UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
    log.info("Calcul du KPI de performance globale pour l'organisation {}", organisationId);

    Map<TypeMetrique, BigDecimal> kpis = calculerTousLesKPI(organisationId, dateDebut, dateFin);

    // Pondération des différents KPI pour le score global
    BigDecimal scoreMembers = calculerScoreMembres(kpis).multiply(new BigDecimal("0.30")); // 30%
    BigDecimal scoreFinancier = calculerScoreFinancier(kpis).multiply(new BigDecimal("0.35")); // 35%
    BigDecimal scoreEvenements = calculerScoreEvenements(kpis).multiply(new BigDecimal("0.20")); // 20%
    BigDecimal scoreSolidarite = calculerScoreSolidarite(kpis).multiply(new BigDecimal("0.15")); // 15%

    BigDecimal scoreGlobal = scoreMembers.add(scoreFinancier).add(scoreEvenements).add(scoreSolidarite);

    log.info("Score de performance globale calculé : {}", scoreGlobal);
    return scoreGlobal.setScale(1, RoundingMode.HALF_UP);
  }

  /**
   * Calcule les KPI de comparaison avec la période précédente
   *
   * @param organisationId L'ID de l'organisation
   * @param dateDebut      Date de début de la période actuelle
   * @param dateFin        Date de fin de la période actuelle
   * @return Map des évolutions en pourcentage
   */
  public Map<TypeMetrique, BigDecimal> calculerEvolutionsKPI(
      UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
    log.info("Calcul des évolutions KPI pour l'organisation {}", organisationId);

    // Période actuelle
    Map<TypeMetrique, BigDecimal> kpisActuels = calculerTousLesKPI(organisationId, dateDebut, dateFin);

    // Période précédente (même durée, décalée)
    long dureeJours = java.time.Duration.between(dateDebut, dateFin).toDays();
    LocalDateTime dateDebutPrecedente = dateDebut.minusDays(dureeJours);
    LocalDateTime dateFinPrecedente = dateFin.minusDays(dureeJours);
    Map<TypeMetrique, BigDecimal> kpisPrecedents = calculerTousLesKPI(organisationId, dateDebutPrecedente,
        dateFinPrecedente);

    Map<TypeMetrique, BigDecimal> evolutions = new HashMap<>();

    for (TypeMetrique typeMetrique : kpisActuels.keySet()) {
      BigDecimal valeurActuelle = kpisActuels.get(typeMetrique);
      BigDecimal valeurPrecedente = kpisPrecedents.get(typeMetrique);

      BigDecimal evolution = calculerPourcentageEvolution(valeurActuelle, valeurPrecedente);
      evolutions.put(typeMetrique, evolution);
    }

    return evolutions;
  }

  // === MÉTHODES PRIVÉES DE CALCUL DES KPI ===

  private BigDecimal calculerKPIMembresActifs(
      UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
    Long count = membreRepository.countMembresActifs(organisationId, dateDebut, dateFin);
    return new BigDecimal(count);
  }

  private BigDecimal calculerKPIMembresInactifs(
      UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
    Long count = membreRepository.countMembresInactifs(organisationId, dateDebut, dateFin);
    return new BigDecimal(count);
  }

  private BigDecimal calculerKPITauxCroissanceMembres(
      UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
    Long membresActuels = membreRepository.countMembresActifs(organisationId, dateDebut, dateFin);
    Long membresPrecedents = membreRepository.countMembresActifs(
        organisationId, dateDebut.minusMonths(1), dateFin.minusMonths(1));

    return calculerTauxCroissance(
        new BigDecimal(membresActuels), new BigDecimal(membresPrecedents));
  }

  private BigDecimal calculerKPIMoyenneAgeMembres(
      UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
    Double moyenneAge = membreRepository.calculerMoyenneAge(organisationId, dateDebut, dateFin);
    return moyenneAge != null
        ? new BigDecimal(moyenneAge).setScale(1, RoundingMode.HALF_UP)
        : BigDecimal.ZERO;
  }

  private BigDecimal calculerKPITotalCotisations(
      UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
    BigDecimal total = cotisationRepository.sumMontantsPayes(organisationId, dateDebut, dateFin);
    return total != null ? total : BigDecimal.ZERO;
  }

  private BigDecimal calculerKPICotisationsEnAttente(
      UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
    BigDecimal total = cotisationRepository.sumMontantsEnAttente(organisationId, dateDebut, dateFin);
    return total != null ? total : BigDecimal.ZERO;
  }

  private BigDecimal calculerKPITauxRecouvrement(
      UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
    BigDecimal collectees = calculerKPITotalCotisations(organisationId, dateDebut, dateFin);
    BigDecimal enAttente = calculerKPICotisationsEnAttente(organisationId, dateDebut, dateFin);
    BigDecimal total = collectees.add(enAttente);

    if (total.compareTo(BigDecimal.ZERO) == 0)
      return BigDecimal.ZERO;

    return collectees.divide(total, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"));
  }

  private BigDecimal calculerKPIMoyenneCotisationMembre(
      UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
    BigDecimal total = calculerKPITotalCotisations(organisationId, dateDebut, dateFin);
    Long nombreMembres = membreRepository.countMembresActifs(organisationId, dateDebut, dateFin);

    if (nombreMembres == 0)
      return BigDecimal.ZERO;

    return total.divide(new BigDecimal(nombreMembres), 2, RoundingMode.HALF_UP);
  }

  private BigDecimal calculerKPINombreEvenements(
      UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
    Long count = evenementRepository.countEvenements(organisationId, dateDebut, dateFin);
    return new BigDecimal(count);
  }

  private BigDecimal calculerKPITauxParticipation(
      UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
    // Calcul basé sur les participations aux événements
    Long totalParticipations = evenementRepository.countTotalParticipations(organisationId, dateDebut, dateFin);
    Long nombreEvenements = evenementRepository.countEvenements(organisationId, dateDebut, dateFin);
    Long nombreMembres = membreRepository.countMembresActifs(organisationId, dateDebut, dateFin);

    if (nombreEvenements == 0 || nombreMembres == 0)
      return BigDecimal.ZERO;

    BigDecimal participationsAttendues = new BigDecimal(nombreEvenements).multiply(new BigDecimal(nombreMembres));
    BigDecimal tauxParticipation = new BigDecimal(totalParticipations)
        .divide(participationsAttendues, 4, RoundingMode.HALF_UP)
        .multiply(new BigDecimal("100"));

    return tauxParticipation;
  }

  private BigDecimal calculerKPIMoyenneParticipants(
      UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
    Double moyenne = evenementRepository.calculerMoyenneParticipants(organisationId, dateDebut, dateFin);
    return moyenne != null
        ? new BigDecimal(moyenne).setScale(1, RoundingMode.HALF_UP)
        : BigDecimal.ZERO;
  }

  private BigDecimal calculerKPINombreDemandesAide(
      UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
    Long count = demandeAideRepository.countDemandes(organisationId, dateDebut, dateFin);
    return new BigDecimal(count);
  }

  private BigDecimal calculerKPIMontantAides(
      UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
    return demandeAideRepository.sumMontantsAccordes(organisationId, dateDebut, dateFin);
  }

  private BigDecimal calculerKPITauxApprobationAides(
      UUID organisationId, LocalDateTime dateDebut, LocalDateTime dateFin) {
    Long totalDemandes = demandeAideRepository.countDemandes(organisationId, dateDebut, dateFin);
    Long demandesApprouvees = demandeAideRepository.countDemandesApprouvees(organisationId, dateDebut, dateFin);

    if (totalDemandes == 0)
      return BigDecimal.ZERO;

    return new BigDecimal(demandesApprouvees)
        .divide(new BigDecimal(totalDemandes), 4, RoundingMode.HALF_UP)
        .multiply(new BigDecimal("100"));
  }

  // === MÉTHODES UTILITAIRES ===

  private BigDecimal calculerTauxCroissance(
      BigDecimal valeurActuelle, BigDecimal valeurPrecedente) {
    if (valeurPrecedente.compareTo(BigDecimal.ZERO) == 0)
      return BigDecimal.ZERO;

    return valeurActuelle
        .subtract(valeurPrecedente)
        .divide(valeurPrecedente, 4, RoundingMode.HALF_UP)
        .multiply(new BigDecimal("100"));
  }

  private BigDecimal calculerPourcentageEvolution(
      BigDecimal valeurActuelle, BigDecimal valeurPrecedente) {
    if (valeurPrecedente == null || valeurPrecedente.compareTo(BigDecimal.ZERO) == 0) {
      return BigDecimal.ZERO;
    }

    return valeurActuelle
        .subtract(valeurPrecedente)
        .divide(valeurPrecedente, 4, RoundingMode.HALF_UP)
        .multiply(new BigDecimal("100"));
  }

  private BigDecimal calculerScoreMembres(Map<TypeMetrique, BigDecimal> kpis) {
    // Score basé sur la croissance et l'activité des membres
    BigDecimal tauxCroissance = kpis.get(TypeMetrique.TAUX_CROISSANCE_MEMBRES);
    BigDecimal nombreActifs = kpis.get(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
    BigDecimal nombreInactifs = kpis.get(TypeMetrique.NOMBRE_MEMBRES_INACTIFS);

    // Calcul du score (logique simplifiée)
    BigDecimal totalMembres = nombreActifs.add(nombreInactifs);
    BigDecimal scoreActivite = totalMembres.compareTo(BigDecimal.ZERO) == 0
        ? BigDecimal.ZERO
        : nombreActifs
            .divide(totalMembres, 2, RoundingMode.HALF_UP)
            .multiply(new BigDecimal("50"));
    BigDecimal scoreCroissance = tauxCroissance.min(new BigDecimal("50")); // Plafonné à 50

    return scoreActivite.add(scoreCroissance);
  }

  private BigDecimal calculerScoreFinancier(Map<TypeMetrique, BigDecimal> kpis) {
    // Score basé sur le recouvrement et les montants
    BigDecimal tauxRecouvrement = kpis.get(TypeMetrique.TAUX_RECOUVREMENT_COTISATIONS);
    return tauxRecouvrement; // Score direct basé sur le taux de recouvrement
  }

  private BigDecimal calculerScoreEvenements(Map<TypeMetrique, BigDecimal> kpis) {
    // Score basé sur la participation aux événements
    BigDecimal tauxParticipation = kpis.get(TypeMetrique.TAUX_PARTICIPATION_EVENEMENTS);
    return tauxParticipation; // Score direct basé sur le taux de participation
  }

  private BigDecimal calculerScoreSolidarite(Map<TypeMetrique, BigDecimal> kpis) {
    // Score basé sur l'efficacité du système de solidarité
    BigDecimal tauxApprobation = kpis.get(TypeMetrique.TAUX_APPROBATION_AIDES);
    return tauxApprobation; // Score direct basé sur le taux d'approbation
  }
}
