package dev.lions.unionflow.server.resource;

import static io.restassured.RestAssured.given;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;

import dev.lions.unionflow.server.api.dto.analytics.AnalyticsDataResponse;
import dev.lions.unionflow.server.api.dto.analytics.DashboardWidgetResponse;
import dev.lions.unionflow.server.api.dto.analytics.KPITrendResponse;
import dev.lions.unionflow.server.api.enums.analytics.PeriodeAnalyse;
import dev.lions.unionflow.server.api.enums.analytics.TypeMetrique;
import dev.lions.unionflow.server.service.AnalyticsService;
import dev.lions.unionflow.server.service.KPICalculatorService;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * Tests d'intégration REST pour AnalyticsResource.
 *
 * <p>Utilise @InjectMock sur AnalyticsService et KPICalculatorService pour contrôler
 * tous les chemins de code sans base de données.
 *
 * @author UnionFlow Team
 */
@QuarkusTest
class AnalyticsResourceTest {

    private static final String BASE = "/api/v1/analytics";

    @InjectMock
    AnalyticsService analyticsService;

    @InjectMock
    KPICalculatorService kpiCalculatorService;

    private UUID orgId;
    private UUID userId;
    private AnalyticsDataResponse dataResponse;
    private KPITrendResponse trendResponse;

    @BeforeEach
    void setup() {
        orgId = UUID.randomUUID();
        userId = UUID.randomUUID();

        dataResponse = new AnalyticsDataResponse(
                TypeMetrique.NOMBRE_MEMBRES_ACTIFS,
                PeriodeAnalyse.CE_MOIS,
                new BigDecimal("42"));

        trendResponse = KPITrendResponse.builder()
                .typeMetrique(TypeMetrique.NOMBRE_MEMBRES_ACTIFS)
                .periodeAnalyse(PeriodeAnalyse.CE_MOIS)
                .valeurActuelle(new BigDecimal("42"))
                .pointsDonnees(Collections.emptyList())
                .dateDebut(LocalDateTime.now().minusDays(30))
                .dateFin(LocalDateTime.now())
                .build();
    }

    // =========================================================================
    // GET /api/v1/analytics/metriques/{typeMetrique}
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /metriques/{type} - succès → 200")
    void calculerMetrique_success_returns200() {
        when(analyticsService.calculerMetrique(
                eq(TypeMetrique.NOMBRE_MEMBRES_ACTIFS),
                eq(PeriodeAnalyse.CE_MOIS),
                any()))
                .thenReturn(dataResponse);

        given()
                .pathParam("typeMetrique", "NOMBRE_MEMBRES_ACTIFS")
                .queryParam("periode", "CE_MOIS")
                .when()
                .get(BASE + "/metriques/{typeMetrique}")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /metriques/{type} - avec organisationId → 200")
    void calculerMetrique_avecOrgId_returns200() {
        when(analyticsService.calculerMetrique(any(), any(), eq(orgId)))
                .thenReturn(dataResponse);

        given()
                .pathParam("typeMetrique", "TOTAL_COTISATIONS_COLLECTEES")
                .queryParam("periode", "CETTE_ANNEE")
                .queryParam("organisationId", orgId.toString())
                .when()
                .get(BASE + "/metriques/{typeMetrique}")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /metriques/{type} - exception service → 500")
    void calculerMetrique_exception_returns500() {
        when(analyticsService.calculerMetrique(any(), any(), any()))
                .thenThrow(new RuntimeException("Query timeout"));

        given()
                .pathParam("typeMetrique", "NOMBRE_MEMBRES_ACTIFS")
                .queryParam("periode", "CE_MOIS")
                .when()
                .get(BASE + "/metriques/{typeMetrique}")
                .then()
                .statusCode(500);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /metriques/{type} - métrique COTISATIONS_EN_ATTENTE → 200")
    void calculerMetrique_cotisationsEnAttente_returns200() {
        AnalyticsDataResponse cotisResponse = new AnalyticsDataResponse(
                TypeMetrique.COTISATIONS_EN_ATTENTE,
                PeriodeAnalyse.CETTE_ANNEE,
                new BigDecimal("15000"));

        when(analyticsService.calculerMetrique(
                eq(TypeMetrique.COTISATIONS_EN_ATTENTE),
                eq(PeriodeAnalyse.CETTE_ANNEE),
                any()))
                .thenReturn(cotisResponse);

        given()
                .pathParam("typeMetrique", "COTISATIONS_EN_ATTENTE")
                .queryParam("periode", "CETTE_ANNEE")
                .when()
                .get(BASE + "/metriques/{typeMetrique}")
                .then()
                .statusCode(200);
    }

    // =========================================================================
    // GET /api/v1/analytics/tendances/{typeMetrique}
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /tendances/{type} - succès → 200")
    void calculerTendanceKPI_success_returns200() {
        when(analyticsService.calculerTendanceKPI(
                eq(TypeMetrique.NOMBRE_MEMBRES_ACTIFS),
                eq(PeriodeAnalyse.SIX_DERNIERS_MOIS),
                any()))
                .thenReturn(trendResponse);

        given()
                .pathParam("typeMetrique", "NOMBRE_MEMBRES_ACTIFS")
                .queryParam("periode", "SIX_DERNIERS_MOIS")
                .when()
                .get(BASE + "/tendances/{typeMetrique}")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /tendances/{type} - avec organisationId → 200")
    void calculerTendanceKPI_avecOrgId_returns200() {
        when(analyticsService.calculerTendanceKPI(any(), any(), eq(orgId)))
                .thenReturn(trendResponse);

        given()
                .pathParam("typeMetrique", "TAUX_RECOUVREMENT_COTISATIONS")
                .queryParam("periode", "TROIS_DERNIERS_MOIS")
                .queryParam("organisationId", orgId.toString())
                .when()
                .get(BASE + "/tendances/{typeMetrique}")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /tendances/{type} - exception → 500")
    void calculerTendanceKPI_exception_returns500() {
        when(analyticsService.calculerTendanceKPI(any(), any(), any()))
                .thenThrow(new RuntimeException("Trend calculation failed"));

        given()
                .pathParam("typeMetrique", "NOMBRE_MEMBRES_ACTIFS")
                .queryParam("periode", "CE_MOIS")
                .when()
                .get(BASE + "/tendances/{typeMetrique}")
                .then()
                .statusCode(500);
    }

    // =========================================================================
    // GET /api/v1/analytics/kpis
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /kpis - succès → 200")
    void obtenirTousLesKPI_success_returns200() {
        Map<TypeMetrique, BigDecimal> kpis = new HashMap<>();
        kpis.put(TypeMetrique.NOMBRE_MEMBRES_ACTIFS, new BigDecimal("150"));
        kpis.put(TypeMetrique.TOTAL_COTISATIONS_COLLECTEES, new BigDecimal("500000"));

        when(kpiCalculatorService.calculerTousLesKPI(any(), any(), any())).thenReturn(kpis);

        given()
                .queryParam("periode", "CE_MOIS")
                .when()
                .get(BASE + "/kpis")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /kpis - avec organisationId → 200")
    void obtenirTousLesKPI_avecOrgId_returns200() {
        when(kpiCalculatorService.calculerTousLesKPI(eq(orgId), any(), any()))
                .thenReturn(Collections.emptyMap());

        given()
                .queryParam("periode", "CETTE_ANNEE")
                .queryParam("organisationId", orgId.toString())
                .when()
                .get(BASE + "/kpis")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /kpis - exception → 500")
    void obtenirTousLesKPI_exception_returns500() {
        when(kpiCalculatorService.calculerTousLesKPI(any(), any(), any()))
                .thenThrow(new RuntimeException("KPI calculation error"));

        given()
                .queryParam("periode", "CE_MOIS")
                .when()
                .get(BASE + "/kpis")
                .then()
                .statusCode(500);
    }

    // =========================================================================
    // GET /api/v1/analytics/performance-globale
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /performance-globale - succès → 200")
    void calculerPerformanceGlobale_success_returns200() {
        when(kpiCalculatorService.calculerKPIPerformanceGlobale(any(), any(), any()))
                .thenReturn(new BigDecimal("78.5"));

        given()
                .queryParam("periode", "CETTE_ANNEE")
                .queryParam("organisationId", orgId.toString())
                .when()
                .get(BASE + "/performance-globale")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /performance-globale - sans organisationId → 200")
    void calculerPerformanceGlobale_sansOrgId_returns200() {
        when(kpiCalculatorService.calculerKPIPerformanceGlobale(any(), any(), any()))
                .thenReturn(new BigDecimal("65.0"));

        given()
                .queryParam("periode", "CE_MOIS")
                .queryParam("organisationId", "00000000-0000-0000-0000-000000000001")
                .when()
                .get(BASE + "/performance-globale")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /performance-globale - exception → 500")
    void calculerPerformanceGlobale_exception_returns500() {
        when(kpiCalculatorService.calculerKPIPerformanceGlobale(any(), any(), any()))
                .thenThrow(new RuntimeException("Performance calculation failed"));

        given()
                .queryParam("periode", "CETTE_ANNEE")
                .when()
                .get(BASE + "/performance-globale")
                .then()
                .statusCode(500);
    }

    @Test
    @TestSecurity(user = "user@test.com", roles = {"MEMBER"})
    @DisplayName("GET /performance-globale - rôle MEMBER → 403")
    void calculerPerformanceGlobale_memberRole_returns403() {
        given()
                .queryParam("periode", "CE_MOIS")
                .when()
                .get(BASE + "/performance-globale")
                .then()
                .statusCode(403);
    }

    // =========================================================================
    // GET /api/v1/analytics/evolutions
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /evolutions - succès → 200")
    void obtenirEvolutionsKPI_success_returns200() {
        Map<TypeMetrique, BigDecimal> evolutions = new HashMap<>();
        evolutions.put(TypeMetrique.NOMBRE_MEMBRES_ACTIFS, new BigDecimal("12.5"));

        when(kpiCalculatorService.calculerEvolutionsKPI(any(), any(), any()))
                .thenReturn(evolutions);

        given()
                .queryParam("periode", "MOIS_DERNIER")
                .when()
                .get(BASE + "/evolutions")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /evolutions - avec organisationId → 200")
    void obtenirEvolutionsKPI_avecOrgId_returns200() {
        when(kpiCalculatorService.calculerEvolutionsKPI(eq(orgId), any(), any()))
                .thenReturn(Collections.emptyMap());

        given()
                .queryParam("periode", "TROIS_DERNIERS_MOIS")
                .queryParam("organisationId", orgId.toString())
                .when()
                .get(BASE + "/evolutions")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /evolutions - exception → 500")
    void obtenirEvolutionsKPI_exception_returns500() {
        when(kpiCalculatorService.calculerEvolutionsKPI(any(), any(), any()))
                .thenThrow(new RuntimeException("Evolution calculation failed"));

        given()
                .queryParam("periode", "CE_MOIS")
                .when()
                .get(BASE + "/evolutions")
                .then()
                .statusCode(500);
    }

    // =========================================================================
    // GET /api/v1/analytics/dashboard/widgets
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /dashboard/widgets - succès → 200")
    void obtenirWidgetsTableauBord_success_returns200() {
        DashboardWidgetResponse widget = DashboardWidgetResponse.builder()
                .titre("Membres actifs")
                .typeWidget("kpi")
                .utilisateurProprietaireId(userId)
                .positionX(0)
                .positionY(0)
                .largeur(3)
                .hauteur(2)
                .build();

        when(analyticsService.obtenirMetriquesTableauBord(any(), eq(userId)))
                .thenReturn(List.of(widget));

        given()
                .queryParam("utilisateurId", userId.toString())
                .when()
                .get(BASE + "/dashboard/widgets")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /dashboard/widgets - avec organisationId → 200")
    void obtenirWidgetsTableauBord_avecOrgId_returns200() {
        when(analyticsService.obtenirMetriquesTableauBord(eq(orgId), eq(userId)))
                .thenReturn(Collections.emptyList());

        given()
                .queryParam("utilisateurId", userId.toString())
                .queryParam("organisationId", orgId.toString())
                .when()
                .get(BASE + "/dashboard/widgets")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /dashboard/widgets - exception → 500")
    void obtenirWidgetsTableauBord_exception_returns500() {
        when(analyticsService.obtenirMetriquesTableauBord(any(), any()))
                .thenThrow(new RuntimeException("Widget load error"));

        given()
                .queryParam("utilisateurId", userId.toString())
                .when()
                .get(BASE + "/dashboard/widgets")
                .then()
                .statusCode(500);
    }

    // =========================================================================
    // GET /api/v1/analytics/types-metriques
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /types-metriques - succès → 200 avec liste complète")
    void obtenirTypesMetriques_returns200() {
        given()
                .when()
                .get(BASE + "/types-metriques")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "user@test.com", roles = {"MEMBER"})
    @DisplayName("GET /types-metriques - rôle MEMBER → 200 (accessible à tous)")
    void obtenirTypesMetriques_memberRole_returns200() {
        given()
                .when()
                .get(BASE + "/types-metriques")
                .then()
                .statusCode(200);
    }

    // =========================================================================
    // GET /api/v1/analytics/periodes-analyse
    // =========================================================================

    @Test
    @TestSecurity(user = "admin@test.com", roles = {"ADMIN"})
    @DisplayName("GET /periodes-analyse - succès → 200 avec liste complète")
    void obtenirPeriodesAnalyse_returns200() {
        given()
                .when()
                .get(BASE + "/periodes-analyse")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "user@test.com", roles = {"MEMBER"})
    @DisplayName("GET /periodes-analyse - rôle MEMBER → 200")
    void obtenirPeriodesAnalyse_memberRole_returns200() {
        given()
                .when()
                .get(BASE + "/periodes-analyse")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "manager@test.com", roles = {"MANAGER"})
    @DisplayName("GET /periodes-analyse - rôle MANAGER → 200")
    void obtenirPeriodesAnalyse_managerRole_returns200() {
        given()
                .when()
                .get(BASE + "/periodes-analyse")
                .then()
                .statusCode(200);
    }
}
