package dev.lions.unionflow.server.service;

import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique;
import io.quarkus.test.TestTransaction;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;

@QuarkusTest
class KPICalculatorServiceTest {

    @Inject
    KPICalculatorService kpiCalculatorService;

    @Test
    @TestTransaction
    @DisplayName("calculerTousLesKPI retourne une map contenant les métriques attendues")
    void calculerTousLesKPI_returnsMetrics() {
        UUID orgId = UUID.randomUUID();
        LocalDateTime debut = LocalDateTime.now().minusMonths(1);
        LocalDateTime fin = LocalDateTime.now();

        Map<TypeMetrique, BigDecimal> result = kpiCalculatorService.calculerTousLesKPI(orgId, debut, fin);

        assertThat(result).isNotNull();
        assertThat(result).containsKey(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
        assertThat(result).containsKey(TypeMetrique.TOTAL_COTISATIONS_COLLECTEES);
    }

    @Test
    @TestTransaction
    @DisplayName("calculerKPIPerformanceGlobale retourne un score entre 0 et 100")
    void calculerKPIPerformanceGlobale_returnsScore() {
        UUID orgId = UUID.randomUUID();
        LocalDateTime debut = LocalDateTime.now().minusMonths(1);
        LocalDateTime fin = LocalDateTime.now();

        BigDecimal score = kpiCalculatorService.calculerKPIPerformanceGlobale(orgId, debut, fin);

        assertThat(score).isNotNull();
        assertThat(score).isBetween(BigDecimal.ZERO, new BigDecimal("100"));
    }

    @Test
    @TestTransaction
    @DisplayName("calculerEvolutionsKPI retourne une map d'évolutions pour toutes les métriques")
    void calculerEvolutionsKPI_returnsEvolutionsMap() {
        UUID orgId = UUID.randomUUID();
        LocalDateTime debut = LocalDateTime.now().minusMonths(1);
        LocalDateTime fin = LocalDateTime.now();

        Map<TypeMetrique, BigDecimal> evolutions = kpiCalculatorService.calculerEvolutionsKPI(orgId, debut, fin);

        assertThat(evolutions).isNotNull();
        assertThat(evolutions).containsKey(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
        assertThat(evolutions).containsKey(TypeMetrique.TOTAL_COTISATIONS_COLLECTEES);
        assertThat(evolutions).containsKey(TypeMetrique.NOMBRE_EVENEMENTS_ORGANISES);
        assertThat(evolutions).containsKey(TypeMetrique.NOMBRE_DEMANDES_AIDE);
    }

    @Test
    @TestTransaction
    @DisplayName("calculerEvolutionsKPI retourne zéro quand la valeur précédente est zéro")
    void calculerEvolutionsKPI_zeroPreviousValue_returnsZero() {
        UUID orgId = UUID.randomUUID();
        LocalDateTime debut = LocalDateTime.now().minusDays(1);
        LocalDateTime fin = LocalDateTime.now();

        Map<TypeMetrique, BigDecimal> evolutions = kpiCalculatorService.calculerEvolutionsKPI(orgId, debut, fin);

        assertThat(evolutions).isNotEmpty();
        evolutions.values().forEach(v -> assertThat(v).isNotNull());
    }

    @Test
    @TestTransaction
    @DisplayName("calculerTousLesKPI contient toutes les métriques membres, financières, événements et solidarité")
    void calculerTousLesKPI_containsAllMetrics() {
        UUID orgId = UUID.randomUUID();
        LocalDateTime debut = LocalDateTime.now().minusMonths(3);
        LocalDateTime fin = LocalDateTime.now();

        Map<TypeMetrique, BigDecimal> result = kpiCalculatorService.calculerTousLesKPI(orgId, debut, fin);

        assertThat(result).containsKey(TypeMetrique.NOMBRE_MEMBRES_ACTIFS);
        assertThat(result).containsKey(TypeMetrique.NOMBRE_MEMBRES_INACTIFS);
        assertThat(result).containsKey(TypeMetrique.TAUX_CROISSANCE_MEMBRES);
        assertThat(result).containsKey(TypeMetrique.MOYENNE_AGE_MEMBRES);
        assertThat(result).containsKey(TypeMetrique.TOTAL_COTISATIONS_COLLECTEES);
        assertThat(result).containsKey(TypeMetrique.COTISATIONS_EN_ATTENTE);
        assertThat(result).containsKey(TypeMetrique.TAUX_RECOUVREMENT_COTISATIONS);
        assertThat(result).containsKey(TypeMetrique.MOYENNE_COTISATION_MEMBRE);
        assertThat(result).containsKey(TypeMetrique.NOMBRE_EVENEMENTS_ORGANISES);
        assertThat(result).containsKey(TypeMetrique.TAUX_PARTICIPATION_EVENEMENTS);
        assertThat(result).containsKey(TypeMetrique.MOYENNE_PARTICIPANTS_EVENEMENT);
        assertThat(result).containsKey(TypeMetrique.NOMBRE_DEMANDES_AIDE);
        assertThat(result).containsKey(TypeMetrique.MONTANT_AIDES_ACCORDEES);
        assertThat(result).containsKey(TypeMetrique.TAUX_APPROBATION_AIDES);
        assertThat(result).hasSize(14);
    }

    @Test
    @TestTransaction
    @DisplayName("calculerKPIPerformanceGlobale avec une longue période couvre les scores internes")
    void calculerKPIPerformanceGlobale_longPeriod_returnsScore() {
        UUID orgId = UUID.randomUUID();
        LocalDateTime debut = LocalDateTime.now().minusYears(1);
        LocalDateTime fin = LocalDateTime.now();

        BigDecimal score = kpiCalculatorService.calculerKPIPerformanceGlobale(orgId, debut, fin);

        assertThat(score).isNotNull();
        assertThat(score).isGreaterThanOrEqualTo(BigDecimal.ZERO);
    }

    @Test
    @TestTransaction
    @DisplayName("calculerEvolutionsKPI avec deux périodes différentes couvre la comparaison")
    void calculerEvolutionsKPI_twoDifferentPeriods_coversComparison() {
        UUID orgId = UUID.randomUUID();
        LocalDateTime debut = LocalDateTime.now().minusMonths(3);
        LocalDateTime fin = LocalDateTime.now();

        Map<TypeMetrique, BigDecimal> evolutions = kpiCalculatorService.calculerEvolutionsKPI(orgId, debut, fin);

        assertThat(evolutions).hasSize(14);
        assertThat(evolutions).containsKey(TypeMetrique.TOTAL_COTISATIONS_COLLECTEES);
        assertThat(evolutions).containsKey(TypeMetrique.TAUX_APPROBATION_AIDES);
        evolutions.values().forEach(v -> assertThat(v).isNotNull());
    }

    @Test
    @TestTransaction
    @DisplayName("calculerTousLesKPI avec période future retourne des valeurs nulles ou zéro")
    void calculerTousLesKPI_futurePeriod_returnsZeroValues() {
        UUID orgId = UUID.randomUUID();
        LocalDateTime debut = LocalDateTime.now().plusMonths(1);
        LocalDateTime fin = LocalDateTime.now().plusMonths(2);

        Map<TypeMetrique, BigDecimal> result = kpiCalculatorService.calculerTousLesKPI(orgId, debut, fin);

        assertThat(result).isNotNull();
        assertThat(result).hasSize(14);
        result.values().forEach(v -> {
            assertThat(v).isNotNull();
            assertThat(v).isGreaterThanOrEqualTo(BigDecimal.ZERO);
        });
    }
}
