package dev.lions.unionflow.server.resource;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

import dev.lions.unionflow.server.api.dto.cotisation.response.CotisationResponse;
import dev.lions.unionflow.server.service.CotisationService;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import io.restassured.http.ContentType;
import jakarta.ws.rs.NotFoundException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
 * Tests unitaires (mocks) pour CotisationResource.
 * Couvre les branches catch (Exception e) non atteignables avec les tests d'intégration.
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2026-03-21
 */
@QuarkusTest
class CotisationResourceMockTest {

    @InjectMock
    CotisationService cotisationService;

    // =========================================================
    // GET /api/cotisations/public — catch (Exception e) L93-97
    // =========================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET /api/cotisations/public retourne 500 quand le service lève une exception")
    void getCotisationsPublic_serviceThrowsException_returns500() {
        when(cotisationService.getAllCotisations(anyInt(), anyInt()))
                .thenThrow(new RuntimeException("Erreur simulée - public"));

        given()
                .queryParam("page", 0)
                .queryParam("size", 20)
                .when()
                .get("/api/cotisations/public")
                .then()
                .statusCode(500)
                .body("error", notNullValue());
    }

    // =========================================================
    // GET /api/cotisations — catch (Exception e) L118-122
    // =========================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET /api/cotisations retourne 500 quand le service lève une exception")
    void getAllCotisations_serviceThrowsException_returns500() {
        when(cotisationService.getAllCotisations(anyInt(), anyInt()))
                .thenThrow(new RuntimeException("Erreur simulée - lister"));

        given()
                .queryParam("page", 0)
                .queryParam("size", 20)
                .when()
                .get("/api/cotisations")
                .then()
                .statusCode(500)
                .body("error", notNullValue());
    }

    // =========================================================
    // GET /api/cotisations/{id} — catch (Exception e) L142-143
    // =========================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET /api/cotisations/{id} retourne 500 quand une exception inattendue est levée")
    void getCotisationById_unexpectedException_returns500() {
        UUID id = UUID.randomUUID();
        when(cotisationService.getCotisationById(eq(id)))
                .thenThrow(new RuntimeException("Erreur inattendue"));

        given()
                .pathParam("id", id)
                .when()
                .get("/api/cotisations/{id}")
                .then()
                .statusCode(500)
                .body("error", notNullValue());
    }

    // =========================================================
    // GET /api/cotisations/reference/{ref} — catch (Exception e) L159-160
    // =========================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET /api/cotisations/reference/{ref} retourne 500 quand exception inattendue")
    void getCotisationByReference_unexpectedException_returns500() {
        when(cotisationService.getCotisationByReference(anyString()))
                .thenThrow(new RuntimeException("Erreur inattendue - référence"));

        given()
                .pathParam("numeroReference", "REF-TEST-500")
                .when()
                .get("/api/cotisations/reference/{numeroReference}")
                .then()
                .statusCode(500)
                .body("error", notNullValue());
    }

    // =========================================================
    // POST /api/cotisations — catch (NotFoundException) L178-179,
    //   catch (IllegalArgumentException) L180-181, catch (Exception) L182-183
    // =========================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("POST /api/cotisations retourne 404 quand le membre n'existe pas (NotFoundException)")
    void createCotisation_notFoundException_returns404() {
        when(cotisationService.createCotisation(any()))
                .thenThrow(new NotFoundException("Membre introuvable"));

        Map<String, Object> body = Map.of(
                "membreId", UUID.randomUUID().toString(),
                "organisationId", UUID.randomUUID().toString(),
                "typeCotisation", "MENSUELLE",
                "libelle", "Cotisation mensuelle",
                "montantDu", "5000.00",
                "dateEcheance", LocalDate.now().plusMonths(1).toString(),
                "annee", LocalDate.now().getYear()
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/cotisations")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("POST /api/cotisations retourne 400 quand IllegalArgumentException")
    void createCotisation_illegalArgumentException_returns400() {
        when(cotisationService.createCotisation(any()))
                .thenThrow(new IllegalArgumentException("Argument invalide"));

        Map<String, Object> body = Map.of(
                "membreId", UUID.randomUUID().toString(),
                "organisationId", UUID.randomUUID().toString(),
                "typeCotisation", "MENSUELLE",
                "libelle", "Cotisation mensuelle",
                "montantDu", "5000.00",
                "dateEcheance", LocalDate.now().plusMonths(1).toString(),
                "annee", LocalDate.now().getYear()
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/cotisations")
                .then()
                .statusCode(400);
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("POST /api/cotisations retourne 500 quand exception inattendue")
    void createCotisation_unexpectedException_returns500() {
        when(cotisationService.createCotisation(any()))
                .thenThrow(new RuntimeException("Erreur inattendue création"));

        Map<String, Object> body = Map.of(
                "membreId", UUID.randomUUID().toString(),
                "organisationId", UUID.randomUUID().toString(),
                "typeCotisation", "MENSUELLE",
                "libelle", "Cotisation mensuelle",
                "montantDu", "5000.00",
                "dateEcheance", LocalDate.now().plusMonths(1).toString(),
                "annee", LocalDate.now().getYear()
        );

        given()
                .contentType(ContentType.JSON)
                .body(body)
                .when()
                .post("/api/cotisations")
                .then()
                .statusCode(500);
    }

    // =========================================================
    // PUT /api/cotisations/{id} — happy path (L196-197) + catch (Exception) L200-201
    // =========================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("PUT /api/cotisations/{id} retourne 200 quand mise à jour réussie")
    void updateCotisation_success_returns200() {
        UUID id = UUID.randomUUID();
        CotisationResponse response = new CotisationResponse();
        when(cotisationService.updateCotisation(eq(id), any()))
                .thenReturn(response);

        Map<String, Object> body = Map.of("statut", "EN_ATTENTE");

        given()
                .contentType(ContentType.JSON)
                .pathParam("id", id)
                .body(body)
                .when()
                .put("/api/cotisations/{id}")
                .then()
                .statusCode(200);
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("PUT /api/cotisations/{id} retourne 500 quand exception inattendue")
    void updateCotisation_unexpectedException_returns500() {
        UUID id = UUID.randomUUID();
        when(cotisationService.updateCotisation(eq(id), any()))
                .thenThrow(new RuntimeException("Erreur inattendue mise à jour"));

        Map<String, Object> body = Map.of("statut", "EN_ATTENTE");

        given()
                .contentType(ContentType.JSON)
                .pathParam("id", id)
                .body(body)
                .when()
                .put("/api/cotisations/{id}")
                .then()
                .statusCode(500)
                .body("error", notNullValue());
    }

    // =========================================================
    // DELETE /api/cotisations/{id} — happy path (L214-215) + catch (Exception) L220-221
    // =========================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("DELETE /api/cotisations/{id} retourne 204 après suppression réussie")
    void deleteCotisation_success_returns204() {
        UUID id = UUID.randomUUID();
        doNothing().when(cotisationService).deleteCotisation(eq(id));

        given()
                .pathParam("id", id)
                .when()
                .delete("/api/cotisations/{id}")
                .then()
                .statusCode(204);
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("DELETE /api/cotisations/{id} retourne 500 quand exception inattendue")
    void deleteCotisation_unexpectedException_returns500() {
        UUID id = UUID.randomUUID();
        doThrow(new RuntimeException("Erreur inattendue suppression"))
                .when(cotisationService).deleteCotisation(eq(id));

        given()
                .pathParam("id", id)
                .when()
                .delete("/api/cotisations/{id}")
                .then()
                .statusCode(500)
                .body("error", notNullValue());
    }

    // =========================================================
    // GET /api/cotisations/statut/{statut} — catch (Exception) L256-257
    // =========================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET /api/cotisations/statut/{statut} retourne 500 quand exception")
    void getCotisationsByStatut_exception_returns500() {
        when(cotisationService.getCotisationsByStatut(anyString(), anyInt(), anyInt()))
                .thenThrow(new RuntimeException("Erreur statut"));

        given()
                .pathParam("statut", "EN_ATTENTE")
                .queryParam("page", 0)
                .queryParam("size", 20)
                .when()
                .get("/api/cotisations/statut/{statut}")
                .then()
                .statusCode(500)
                .body("error", notNullValue());
    }

    // =========================================================
    // GET /api/cotisations/en-retard — catch (Exception) L273-274
    // =========================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET /api/cotisations/en-retard retourne 500 quand exception")
    void getCotisationsEnRetard_exception_returns500() {
        when(cotisationService.getCotisationsEnRetard(anyInt(), anyInt()))
                .thenThrow(new RuntimeException("Erreur en-retard"));

        given()
                .queryParam("page", 0)
                .queryParam("size", 20)
                .when()
                .get("/api/cotisations/en-retard")
                .then()
                .statusCode(500)
                .body("error", notNullValue());
    }

    // =========================================================
    // GET /api/cotisations/recherche — catch (Exception) L296-297
    // =========================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET /api/cotisations/recherche retourne 500 quand exception")
    void rechercherCotisations_exception_returns500() {
        when(cotisationService.rechercherCotisations(isNull(), anyString(), isNull(), isNull(), isNull(), anyInt(), anyInt()))
                .thenThrow(new RuntimeException("Erreur recherche"));

        given()
                .queryParam("page", 0)
                .queryParam("size", 20)
                .queryParam("statut", "EN_ATTENTE")
                .when()
                .get("/api/cotisations/recherche")
                .then()
                .statusCode(500)
                .body("error", notNullValue());
    }

    // =========================================================
    // GET /api/cotisations/stats — catch (Exception) L310-311
    // =========================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET /api/cotisations/stats retourne 500 quand exception")
    void getStatistiquesCotisationsStats_exception_returns500() {
        when(cotisationService.getStatistiquesCotisations())
                .thenThrow(new RuntimeException("Erreur stats"));

        given()
                .when()
                .get("/api/cotisations/stats")
                .then()
                .statusCode(500)
                .body("error", notNullValue());
    }

    // =========================================================
    // GET /api/cotisations/statistiques — catch (Exception) L324-325
    // =========================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET /api/cotisations/statistiques retourne 500 quand exception")
    void getStatistiquesCotisations_exception_returns500() {
        when(cotisationService.getStatistiquesCotisations())
                .thenThrow(new RuntimeException("Erreur statistiques"));

        given()
                .when()
                .get("/api/cotisations/statistiques")
                .then()
                .statusCode(500)
                .body("error", notNullValue());
    }

    // =========================================================
    // GET /api/cotisations/statistiques/periode — catch (Exception) L338-339
    // =========================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET /api/cotisations/statistiques/periode retourne 500 quand exception")
    void getStatistiquesPeriode_exception_returns500() {
        when(cotisationService.getStatistiquesPeriode(anyInt(), any()))
                .thenThrow(new RuntimeException("Erreur période"));

        given()
                .queryParam("annee", 2025)
                .queryParam("mois", 1)
                .when()
                .get("/api/cotisations/statistiques/periode")
                .then()
                .statusCode(500)
                .body("error", notNullValue());
    }

    // =========================================================
    // PUT /api/cotisations/{id}/payer — catch (Exception) L367-368
    // =========================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("PUT /api/cotisations/{id}/payer retourne 500 quand exception inattendue")
    void enregistrerPaiement_unexpectedException_returns500() {
        UUID id = UUID.randomUUID();
        when(cotisationService.enregistrerPaiement(eq(id), any(), any(), any(), any()))
                .thenThrow(new RuntimeException("Erreur inattendue paiement"));

        given()
                .pathParam("id", id)
                .contentType(ContentType.JSON)
                .body(Map.of("montantPaye", "5000", "modePaiement", "ESPECES"))
                .when()
                .put("/api/cotisations/{id}/payer")
                .then()
                .statusCode(500)
                .body("error", notNullValue());
    }

    // =========================================================
    // POST /api/cotisations/rappels/groupes — catch (Exception) L367-368
    // =========================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("POST /api/cotisations/rappels/groupes retourne 500 quand exception")
    void envoyerRappelsGroupes_exception_returns500() {
        when(cotisationService.envoyerRappelsCotisationsGroupes(any()))
                .thenThrow(new RuntimeException("Erreur rappels"));

        given()
                .contentType(ContentType.JSON)
                .body(List.of(UUID.randomUUID()))
                .when()
                .post("/api/cotisations/rappels/groupes")
                .then()
                .statusCode(500)
                .body("error", notNullValue());
    }

    // =========================================================
    // GET /api/cotisations/mes-cotisations — catch (Exception) L404-408
    // =========================================================

    @Test
    @TestSecurity(user = "membre@unionflow.com", roles = {"MEMBRE"})
    @DisplayName("GET /api/cotisations/mes-cotisations retourne 500 quand exception")
    void getMesCotisations_exception_returns500() {
        when(cotisationService.getMesCotisations(anyInt(), anyInt()))
                .thenThrow(new RuntimeException("Erreur mes-cotisations"));

        given()
                .queryParam("page", 0)
                .queryParam("size", 50)
                .when()
                .get("/api/cotisations/mes-cotisations")
                .then()
                .statusCode(500)
                .body("error", notNullValue());
    }

    // =========================================================
    // GET /api/cotisations/mes-cotisations/en-attente — catch (Exception) L428-432
    // =========================================================

    @Test
    @TestSecurity(user = "membre@unionflow.com", roles = {"MEMBRE"})
    @DisplayName("GET /api/cotisations/mes-cotisations/en-attente retourne 500 quand exception")
    void getMesCotisationsEnAttente_exception_returns500() {
        when(cotisationService.getMesCotisationsEnAttente())
                .thenThrow(new RuntimeException("Erreur mes-cotisations en-attente"));

        given()
                .when()
                .get("/api/cotisations/mes-cotisations/en-attente")
                .then()
                .statusCode(500)
                .body("error", notNullValue());
    }

    // =========================================================
    // GET /api/cotisations/mes-cotisations/synthese — catch (Exception) L452-456
    // =========================================================

    @Test
    @TestSecurity(user = "membre@unionflow.com", roles = {"MEMBRE"})
    @DisplayName("GET /api/cotisations/mes-cotisations/synthese retourne 500 quand exception")
    void getMesCotisationsSynthese_exception_returns500() {
        when(cotisationService.getMesCotisationsSynthese())
                .thenThrow(new RuntimeException("Erreur synthèse"));

        given()
                .when()
                .get("/api/cotisations/mes-cotisations/synthese")
                .then()
                .statusCode(500)
                .body("error", notNullValue());
    }

    // =========================================================
    // GET /api/cotisations/public — success path (vérification stats)
    // =========================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET /api/cotisations/public retourne 200 avec données mockées")
    void getCotisationsPublic_withMockedData_returns200() {
        CotisationResponse summary = CotisationResponse.builder()
                .numeroReference("COT-001")
                .nomMembre("Jean Dupont")
                .montantDu(new BigDecimal("5000"))
                .montantPaye(new BigDecimal("0"))
                .statut("EN_ATTENTE")
                .statutLibelle("En attente")
                .dateEcheance(LocalDate.now().plusMonths(1))
                .build();
        when(cotisationService.getAllCotisations(anyInt(), anyInt()))
                .thenReturn(List.of(summary));
        when(cotisationService.getStatistiquesCotisations())
                .thenReturn(Map.of("totalCotisations", 1L));

        given()
                .queryParam("page", 0)
                .queryParam("size", 20)
                .when()
                .get("/api/cotisations/public")
                .then()
                .statusCode(200)
                .body("content", notNullValue())
                .body("totalElements", notNullValue());
    }

    // =========================================================
    // GET /api/cotisations/public — branche totalCotisations == null (ligne 80-82)
    // Quand getStatistiquesCotisations().get("totalCotisations") est null
    // → totalElements = content.size()
    // =========================================================

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = {"ADMIN"})
    @DisplayName("GET /api/cotisations/public avec totalCotisations null utilise content.size() (branche ligne 82)")
    void getCotisationsPublic_totalCotisationsNull_usesContentSize() {
        CotisationResponse summary = CotisationResponse.builder()
                .numeroReference("COT-002")
                .nomMembre("Marie Dupont")
                .montantDu(new BigDecimal("3000"))
                .montantPaye(new BigDecimal("0"))
                .statut("EN_ATTENTE")
                .statutLibelle("En attente")
                .dateEcheance(LocalDate.now().plusMonths(2))
                .build();
        when(cotisationService.getAllCotisations(anyInt(), anyInt()))
                .thenReturn(List.of(summary));
        // getStatistiquesCotisations() retourne une map SANS "totalCotisations"
        // → .get("totalCotisations") retourne null → branche ternaire : content.size()
        when(cotisationService.getStatistiquesCotisations())
                .thenReturn(java.util.Collections.emptyMap());

        given()
                .queryParam("page", 0)
                .queryParam("size", 20)
                .when()
                .get("/api/cotisations/public")
                .then()
                .statusCode(200)
                .body("content", notNullValue())
                // totalElements = content.size() = 1
                .body("totalElements", equalTo(1));
    }
}
