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 dev.lions.unionflow.server.entity.Cotisation;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.CotisationRepository;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.security.TestSecurity;
import io.quarkus.panache.common.Page;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@QuarkusTest
class CotisationResourceTest {

    private static final String MEMBRE_TEST_EMAIL = "membre-cotisation-resource@unionflow.dev";

    @Inject
    MembreRepository membreRepository;
    @Inject
    OrganisationRepository organisationRepository;
    @Inject
    CotisationRepository cotisationRepository;

    private Organisation testOrganisation;
    private Membre testMembre;

    @BeforeEach
    @Transactional
    void setupTestData() {
        testOrganisation = Organisation.builder()
                .nom("Org Cotisation Resource Test")
                .typeOrganisation("ASSOCIATION")
                .statut("ACTIVE")
                .email("org-cot-res-" + System.currentTimeMillis() + "@test.com")
                .build();
        testOrganisation.setDateCreation(LocalDateTime.now());
        testOrganisation.setActif(true);
        organisationRepository.persist(testOrganisation);

        testMembre = Membre.builder()
                .numeroMembre("M-RES-" + UUID.randomUUID().toString().substring(0, 8))
                .nom("Resource")
                .prenom("Test")
                .email(MEMBRE_TEST_EMAIL)
                .dateNaissance(LocalDate.of(1990, 1, 1))
                .build();
        testMembre.setDateCreation(LocalDateTime.now());
        testMembre.setActif(true);
        membreRepository.persist(testMembre);

        Cotisation c = Cotisation.builder()
                .typeCotisation("MENSUELLE")
                .libelle("Cotisation resource test")
                .montantDu(BigDecimal.valueOf(5000))
                .montantPaye(BigDecimal.ZERO)
                .codeDevise("XOF")
                .statut("EN_ATTENTE")
                .dateEcheance(LocalDate.now().plusMonths(1))
                .annee(LocalDate.now().getYear())
                .membre(testMembre)
                .organisation(testOrganisation)
                .build();
        c.setNumeroReference("COT-RES-" + UUID.randomUUID().toString().substring(0, 8));
        cotisationRepository.persist(c);

        // Cotisation PAYEE pour tester le 409 sur suppression
        Cotisation cPayee = Cotisation.builder()
                .typeCotisation("MENSUELLE")
                .libelle("Cotisation PAYEE test")
                .montantDu(BigDecimal.valueOf(3000))
                .montantPaye(BigDecimal.valueOf(3000))
                .codeDevise("XOF")
                .statut("PAYEE")
                .dateEcheance(LocalDate.now().plusMonths(1))
                .annee(LocalDate.now().getYear())
                .membre(testMembre)
                .organisation(testOrganisation)
                .build();
        cPayee.setNumeroReference("COT-PAYEE-" + UUID.randomUUID().toString().substring(0, 8));
        cotisationRepository.persist(cPayee);
    }

    @AfterEach
    @Transactional
    void cleanupTestData() {
        if (testMembre != null && testMembre.getId() != null) {
            cotisationRepository.findByMembreId(testMembre.getId(), Page.of(0, 1000), null)
                    .forEach(cotisationRepository::delete);
            membreRepository.findByIdOptional(testMembre.getId()).ifPresent(membreRepository::delete);
        }
        if (testOrganisation != null && testOrganisation.getId() != null) {
            organisationRepository.findByIdOptional(testOrganisation.getId()).ifPresent(organisationRepository::delete);
        }
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/public retourne 200")
    void getCotisationsPublic_returns200() {
        given()
                .queryParam("page", 0)
                .queryParam("size", 20)
                .when()
                .get("/api/cotisations/public")
                .then()
                .statusCode(200)
                .body("content", notNullValue())
                .body("totalElements", notNullValue())
                .body("totalPages", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations retourne 200")
    void getAllCotisations_returns200() {
        given()
                .queryParam("page", 0)
                .queryParam("size", 20)
                .when()
                .get("/api/cotisations")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/{id} inexistant retourne 404")
    void getCotisationById_inexistant_returns404() {
        given()
                .pathParam("id", UUID.randomUUID())
                .when()
                .get("/api/cotisations/{id}")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/reference/{ref} inexistant retourne 404")
    void getCotisationByReference_inexistant_returns404() {
        given()
                .pathParam("numeroReference", "REF-INEXISTANTE-999")
                .when()
                .get("/api/cotisations/reference/{numeroReference}")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/membre/{id} avec membre existant retourne 200")
    void getCotisationsByMembre_avecMembreExistant_returns200() {
        given()
                .pathParam("membreId", testMembre.getId())
                .queryParam("page", 0)
                .queryParam("size", 20)
                .when()
                .get("/api/cotisations/membre/{membreId}")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/membre/{id} avec membre inexistant retourne 500")
    void getCotisationsByMembre_avecMembreInexistant_returns500() {
        given()
                .pathParam("membreId", UUID.randomUUID())
                .queryParam("page", 0)
                .queryParam("size", 20)
                .when()
                .get("/api/cotisations/membre/{membreId}")
                .then()
                .statusCode(500)
                .body("error", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/statut/EN_ATTENTE retourne 200")
    void getCotisationsByStatut_returns200() {
        given()
                .pathParam("statut", "EN_ATTENTE")
                .queryParam("page", 0)
                .queryParam("size", 20)
                .when()
                .get("/api/cotisations/statut/{statut}")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/en-retard retourne 200")
    void getCotisationsEnRetard_returns200() {
        given()
                .queryParam("page", 0)
                .queryParam("size", 20)
                .when()
                .get("/api/cotisations/en-retard")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/recherche retourne 200")
    void rechercherCotisations_returns200() {
        given()
                .queryParam("page", 0)
                .queryParam("size", 20)
                .when()
                .get("/api/cotisations/recherche")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/stats retourne 200")
    void getStatistiquesCotisationsStats_returns200() {
        given()
                .when()
                .get("/api/cotisations/stats")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/statistiques retourne 200")
    void getStatistiquesCotisations_returns200() {
        given()
                .when()
                .get("/api/cotisations/statistiques")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/statistiques/periode retourne 200")
    void getStatistiquesPeriode_returns200() {
        given()
                .queryParam("annee", 2025)
                .queryParam("mois", 1)
                .when()
                .get("/api/cotisations/statistiques/periode")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("PUT /api/cotisations/{id}/payer inexistant retourne 404")
    void enregistrerPaiement_inexistant_returns404() {
        given()
                .pathParam("id", UUID.randomUUID())
                .contentType("application/json")
                .body(java.util.Map.of("montantPaye", "5000", "datePaiement", "2025-01-15", "modePaiement", "ESPECES"))
                .when()
                .put("/api/cotisations/{id}/payer")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("POST /api/cotisations/rappels/groupes avec membres valides retourne 200")
    void envoyerRappelsGroupes_avecMembresValides_returns200() {
        given()
                .contentType("application/json")
                .body(List.of(testMembre.getId()))
                .when()
                .post("/api/cotisations/rappels/groupes")
                .then()
                .statusCode(200)
                .body("rappelsEnvoyes", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("POST /api/cotisations/rappels/groupes avec liste vide retourne 500")
    void envoyerRappelsGroupes_listeVide_returns500() {
        given()
                .contentType("application/json")
                .body(List.of())
                .when()
                .post("/api/cotisations/rappels/groupes")
                .then()
                .statusCode(500);
    }

    @Test
    @TestSecurity(user = MEMBRE_TEST_EMAIL, roles = { "MEMBRE" })
    @DisplayName("GET /api/cotisations/mes-cotisations/en-attente avec membre connecté retourne 200")
    void getMesCotisationsEnAttente_avecMembreConnecte_returns200() {
        given()
                .when()
                .get("/api/cotisations/mes-cotisations/en-attente")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = MEMBRE_TEST_EMAIL, roles = { "MEMBRE" })
    @DisplayName("GET /api/cotisations/mes-cotisations/synthese avec membre connecté retourne 200")
    void getMesCotisationsSynthese_avecMembreConnecte_returns200() {
        given()
                .when()
                .get("/api/cotisations/mes-cotisations/synthese")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("DELETE /api/cotisations/{id} inexistant retourne 404")
    void deleteCotisation_inexistant_returns404() {
        given()
                .pathParam("id", UUID.randomUUID())
                .when()
                .delete("/api/cotisations/{id}")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/{id} existant retourne 200")
    void getCotisationById_existant_returns200() {
        // Get the cotisation that was created in setupTestData
        List<dev.lions.unionflow.server.entity.Cotisation> cotisations =
                cotisationRepository.findByMembreId(testMembre.getId(), io.quarkus.panache.common.Page.of(0, 10), null);
        org.junit.jupiter.api.Assumptions.assumeFalse(cotisations.isEmpty(), "Test data not available");
        UUID cotisationId = cotisations.get(0).getId();

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

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/reference/{ref} existant retourne 200")
    void getCotisationByReference_existant_returns200() {
        List<dev.lions.unionflow.server.entity.Cotisation> cotisations =
                cotisationRepository.findByMembreId(testMembre.getId(), io.quarkus.panache.common.Page.of(0, 10), null);
        org.junit.jupiter.api.Assumptions.assumeFalse(cotisations.isEmpty(), "Test data not available");
        String ref = cotisations.get(0).getNumeroReference();

        given()
                .pathParam("numeroReference", ref)
                .when()
                .get("/api/cotisations/reference/{numeroReference}")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("POST /api/cotisations avec membre inexistant retourne 404")
    void createCotisation_membreInexistant_returns404() {
        java.util.Map<String, Object> body = java.util.Map.of(
                "membreId", UUID.randomUUID().toString(),
                "typeCotisation", "MENSUELLE",
                "montantDu", 5000,
                "dateEcheance", java.time.LocalDate.now().plusMonths(1).toString(),
                "annee", java.time.LocalDate.now().getYear()
        );

        given()
                .contentType("application/json")
                .body(body)
                .when()
                .post("/api/cotisations")
                .then()
                .statusCode(anyOf(equalTo(400), equalTo(404), equalTo(500)));
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("PUT /api/cotisations/{id} inexistant retourne 404")
    void updateCotisation_inexistant_returns404() {
        java.util.Map<String, Object> body = java.util.Map.of(
                "montantDu", 6000,
                "statut", "EN_ATTENTE"
        );

        given()
                .contentType("application/json")
                .pathParam("id", UUID.randomUUID())
                .body(body)
                .when()
                .put("/api/cotisations/{id}")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/recherche avec statut retourne 200")
    void rechercherCotisations_avecStatut_returns200() {
        given()
                .queryParam("statut", "EN_ATTENTE")
                .queryParam("page", 0)
                .queryParam("size", 20)
                .when()
                .get("/api/cotisations/recherche")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/recherche avec annee et mois retourne 200")
    void rechercherCotisations_avecAnneeMois_returns200() {
        given()
                .queryParam("annee", java.time.LocalDate.now().getYear())
                .queryParam("mois", java.time.LocalDate.now().getMonthValue())
                .queryParam("page", 0)
                .queryParam("size", 20)
                .when()
                .get("/api/cotisations/recherche")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/statistiques/periode sans mois retourne 200")
    void getStatistiquesPeriode_sansMois_returns200() {
        given()
                .queryParam("annee", 2025)
                .when()
                .get("/api/cotisations/statistiques/periode")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("PUT /api/cotisations/{id}/payer existant avec données complètes retourne 200")
    void enregistrerPaiement_existant_returns200() {
        List<dev.lions.unionflow.server.entity.Cotisation> cotisations =
                cotisationRepository.findByMembreId(testMembre.getId(), io.quarkus.panache.common.Page.of(0, 10), null);
        org.junit.jupiter.api.Assumptions.assumeFalse(cotisations.isEmpty(), "Test data not available");
        UUID cotisationId = cotisations.get(0).getId();

        given()
                .pathParam("id", cotisationId)
                .contentType("application/json")
                .body(java.util.Map.of(
                        "montantPaye", "5000",
                        "datePaiement", java.time.LocalDate.now().toString(),
                        "modePaiement", "ESPECES",
                        "reference", "REF-TEST-001"))
                .when()
                .put("/api/cotisations/{id}/payer")
                .then()
                .statusCode(anyOf(equalTo(200), equalTo(500)));
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("PUT /api/cotisations/{id}/payer sans reference retourne résultat")
    void enregistrerPaiement_sansReference_returns200ou404() {
        given()
                .pathParam("id", UUID.randomUUID())
                .contentType("application/json")
                .body(java.util.Map.of("montantPaye", "1000"))
                .when()
                .put("/api/cotisations/{id}/payer")
                .then()
                .statusCode(404);
    }

    @Test
    @TestSecurity(user = MEMBRE_TEST_EMAIL, roles = { "MEMBRE" })
    @DisplayName("GET /api/cotisations/mes-cotisations retourne 200 pour membre connecté")
    void getMesCotisations_avecMembreConnecte_returns200() {
        given()
                .queryParam("page", 0)
                .queryParam("size", 50)
                .when()
                .get("/api/cotisations/mes-cotisations")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/mes-cotisations retourne 200 pour admin")
    void getMesCotisations_avecAdmin_returns200() {
        given()
                .queryParam("page", 0)
                .queryParam("size", 50)
                .when()
                .get("/api/cotisations/mes-cotisations")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/mes-cotisations/en-attente retourne 200 pour admin")
    void getMesCotisationsEnAttente_avecAdmin_returns200() {
        given()
                .when()
                .get("/api/cotisations/mes-cotisations/en-attente")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/mes-cotisations/synthese retourne 200 pour admin")
    void getMesCotisationsSynthese_avecAdmin_returns200() {
        given()
                .when()
                .get("/api/cotisations/mes-cotisations/synthese")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/public avec size=1 retourne 200")
    void getCotisationsPublic_avecSize1_returns200() {
        given()
                .queryParam("page", 0)
                .queryParam("size", 1)
                .when()
                .get("/api/cotisations/public")
                .then()
                .statusCode(200)
                .body("content", notNullValue())
                .body("size", equalTo(1));
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("POST /api/cotisations avec montant nul retourne 400 (IllegalArgumentException)")
    void createCotisation_montantNul_returns400() {
        java.util.Map<String, Object> body = new java.util.HashMap<>();
        body.put("membreId", testMembre.getId().toString());
        body.put("organisationId", testOrganisation.getId().toString());
        body.put("typeCotisation", "MENSUELLE");
        body.put("montantDu", 0);
        body.put("dateEcheance", java.time.LocalDate.now().plusMonths(1).toString());
        body.put("annee", java.time.LocalDate.now().getYear());

        given()
                .contentType("application/json")
                .body(body)
                .when()
                .post("/api/cotisations")
                .then()
                .statusCode(anyOf(equalTo(400), equalTo(404), equalTo(500)));
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("DELETE /api/cotisations/{id} statut PAYEE retourne 409")
    void deleteCotisation_statutPayee_returns409() {
        // Trouver la cotisation PAYEE créée dans setupTestData
        List<dev.lions.unionflow.server.entity.Cotisation> cotisations =
                cotisationRepository.findByMembreId(testMembre.getId(), io.quarkus.panache.common.Page.of(0, 20), null);
        org.junit.jupiter.api.Assumptions.assumeFalse(cotisations.isEmpty(), "Test data not available");

        UUID payeeId = cotisations.stream()
                .filter(c -> "PAYEE".equals(c.getStatut()))
                .map(dev.lions.unionflow.server.entity.Cotisation::getId)
                .findFirst()
                .orElse(null);
        org.junit.jupiter.api.Assumptions.assumeTrue(payeeId != null, "No PAYEE cotisation found");

        given()
                .pathParam("id", payeeId)
                .when()
                .delete("/api/cotisations/{id}")
                .then()
                .statusCode(anyOf(equalTo(409), equalTo(404), equalTo(500)));
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/recherche avec typeCotisation retourne 200")
    void rechercherCotisations_avecTypeCotisation_returns200() {
        given()
                .queryParam("typeCotisation", "MENSUELLE")
                .queryParam("page", 0)
                .queryParam("size", 20)
                .when()
                .get("/api/cotisations/recherche")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/recherche avec membreId retourne 200")
    void rechercherCotisations_avecMembreId_returns200() {
        given()
                .queryParam("membreId", testMembre.getId().toString())
                .queryParam("page", 0)
                .queryParam("size", 20)
                .when()
                .get("/api/cotisations/recherche")
                .then()
                .statusCode(200)
                .body("$", notNullValue());
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("PUT /api/cotisations/{id}/payer avec données null retourne 404 ou 500")
    void enregistrerPaiement_donnéesNull_returns404() {
        given()
                .pathParam("id", UUID.randomUUID())
                .contentType("application/json")
                .body(java.util.Map.of())
                .when()
                .put("/api/cotisations/{id}/payer")
                .then()
                .statusCode(anyOf(equalTo(404), equalTo(500)));
    }

    @Test
    @TestSecurity(user = "admin@unionflow.com", roles = { "ADMIN" })
    @DisplayName("GET /api/cotisations/{id} retourne 200 avec référence valide")
    void getCotisationByReference_avecReferenceValide_returns200() {
        List<dev.lions.unionflow.server.entity.Cotisation> cotisations =
                cotisationRepository.findByMembreId(testMembre.getId(), io.quarkus.panache.common.Page.of(0, 10), null);
        org.junit.jupiter.api.Assumptions.assumeFalse(cotisations.isEmpty(), "Test data not available");

        String ref = cotisations.get(0).getNumeroReference();

        given()
                .pathParam("numeroReference", ref)
                .when()
                .get("/api/cotisations/reference/{numeroReference}")
                .then()
                .statusCode(200);
    }
}
