package dev.lions.unionflow.server.resource;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;

import dev.lions.unionflow.server.api.dto.finance.CotisationDTO;
import dev.lions.unionflow.server.entity.Cotisation;
import dev.lions.unionflow.server.entity.Membre;
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.http.ContentType;
import jakarta.transaction.Transactional;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;

/**
 * Tests d'intégration pour CotisationResource Teste tous les endpoints REST de l'API cotisations
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-15
 */
@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@DisplayName("Tests d'intégration - API Cotisations")
class CotisationResourceTest {

  private static Long membreTestId;
  private static Long cotisationTestId;
  private static String numeroReferenceTest;

  @BeforeEach
  @Transactional
  void setUp() {
    // Nettoyage et création des données de test
    Cotisation.deleteAll();
    Membre.deleteAll();

    // Création d'un membre de test
    Membre membreTest = new Membre();
    membreTest.setNumeroMembre("MBR-TEST-001");
    membreTest.setNom("Dupont");
    membreTest.setPrenom("Jean");
    membreTest.setEmail("jean.dupont@test.com");
    membreTest.setTelephone("+225070123456");
    membreTest.setDateNaissance(LocalDate.of(1985, 5, 15));
    membreTest.setActif(true);
    membreTest.persist();

    membreTestId = membreTest.id;
  }

  @Test
  @org.junit.jupiter.api.Order(1)
  @DisplayName("POST /api/cotisations - Création d'une cotisation")
  void testCreateCotisation() {
    CotisationDTO nouvelleCotisation = new CotisationDTO();
    nouvelleCotisation.setMembreId(UUID.fromString(membreTestId.toString()));
    nouvelleCotisation.setTypeCotisation("MENSUELLE");
    nouvelleCotisation.setMontantDu(new BigDecimal("25000.00"));
    nouvelleCotisation.setDateEcheance(LocalDate.now().plusDays(30));
    nouvelleCotisation.setDescription("Cotisation mensuelle janvier 2025");
    nouvelleCotisation.setPeriode("Janvier 2025");
    nouvelleCotisation.setAnnee(2025);
    nouvelleCotisation.setMois(1);

    given()
        .contentType(ContentType.JSON)
        .body(nouvelleCotisation)
        .when()
        .post("/api/cotisations")
        .then()
        .statusCode(201)
        .body("numeroReference", notNullValue())
        .body("membreId", equalTo(membreTestId.toString()))
        .body("typeCotisation", equalTo("MENSUELLE"))
        .body("montantDu", equalTo(25000.00f))
        .body("montantPaye", equalTo(0.0f))
        .body("statut", equalTo("EN_ATTENTE"))
        .body("codeDevise", equalTo("XOF"))
        .body("annee", equalTo(2025))
        .body("mois", equalTo(1));
  }

  @Test
  @org.junit.jupiter.api.Order(2)
  @DisplayName("GET /api/cotisations - Liste des cotisations")
  void testGetAllCotisations() {
    given()
        .queryParam("page", 0)
        .queryParam("size", 10)
        .when()
        .get("/api/cotisations")
        .then()
        .statusCode(200)
        .body("size()", greaterThanOrEqualTo(0));
  }

  @Test
  @org.junit.jupiter.api.Order(3)
  @DisplayName("GET /api/cotisations/{id} - Récupération par ID")
  void testGetCotisationById() {
    // Créer d'abord une cotisation
    CotisationDTO cotisation = createTestCotisation();
    cotisationTestId = Long.valueOf(cotisation.getId().toString());

    given()
        .pathParam("id", cotisationTestId)
        .when()
        .get("/api/cotisations/{id}")
        .then()
        .statusCode(200)
        .body("id", equalTo(cotisationTestId.toString()))
        .body("typeCotisation", equalTo("MENSUELLE"));
  }

  @Test
  @org.junit.jupiter.api.Order(4)
  @DisplayName("GET /api/cotisations/reference/{numeroReference} - Récupération par référence")
  void testGetCotisationByReference() {
    // Utiliser la cotisation créée précédemment
    if (numeroReferenceTest == null) {
      CotisationDTO cotisation = createTestCotisation();
      numeroReferenceTest = cotisation.getNumeroReference();
    }

    given()
        .pathParam("numeroReference", numeroReferenceTest)
        .when()
        .get("/api/cotisations/reference/{numeroReference}")
        .then()
        .statusCode(200)
        .body("numeroReference", equalTo(numeroReferenceTest))
        .body("typeCotisation", equalTo("MENSUELLE"));
  }

  @Test
  @org.junit.jupiter.api.Order(5)
  @DisplayName("PUT /api/cotisations/{id} - Mise à jour d'une cotisation")
  void testUpdateCotisation() {
    // Créer une cotisation si nécessaire
    if (cotisationTestId == null) {
      CotisationDTO cotisation = createTestCotisation();
      cotisationTestId = Long.valueOf(cotisation.getId().toString());
    }

    CotisationDTO cotisationMiseAJour = new CotisationDTO();
    cotisationMiseAJour.setTypeCotisation("TRIMESTRIELLE");
    cotisationMiseAJour.setMontantDu(new BigDecimal("75000.00"));
    cotisationMiseAJour.setDescription("Cotisation trimestrielle Q1 2025");
    cotisationMiseAJour.setObservations("Mise à jour du type de cotisation");

    given()
        .contentType(ContentType.JSON)
        .pathParam("id", cotisationTestId)
        .body(cotisationMiseAJour)
        .when()
        .put("/api/cotisations/{id}")
        .then()
        .statusCode(200)
        .body("typeCotisation", equalTo("TRIMESTRIELLE"))
        .body("montantDu", equalTo(75000.00f))
        .body("observations", equalTo("Mise à jour du type de cotisation"));
  }

  @Test
  @org.junit.jupiter.api.Order(6)
  @DisplayName("GET /api/cotisations/membre/{membreId} - Cotisations d'un membre")
  void testGetCotisationsByMembre() {
    given()
        .pathParam("membreId", membreTestId)
        .queryParam("page", 0)
        .queryParam("size", 10)
        .when()
        .get("/api/cotisations/membre/{membreId}")
        .then()
        .statusCode(200)
        .body("size()", greaterThanOrEqualTo(0));
  }

  @Test
  @org.junit.jupiter.api.Order(7)
  @DisplayName("GET /api/cotisations/statut/{statut} - Cotisations par statut")
  void testGetCotisationsByStatut() {
    given()
        .pathParam("statut", "EN_ATTENTE")
        .queryParam("page", 0)
        .queryParam("size", 10)
        .when()
        .get("/api/cotisations/statut/{statut}")
        .then()
        .statusCode(200)
        .body("size()", greaterThanOrEqualTo(0));
  }

  @Test
  @org.junit.jupiter.api.Order(8)
  @DisplayName("GET /api/cotisations/en-retard - Cotisations en retard")
  void testGetCotisationsEnRetard() {
    given()
        .queryParam("page", 0)
        .queryParam("size", 10)
        .when()
        .get("/api/cotisations/en-retard")
        .then()
        .statusCode(200)
        .body("size()", greaterThanOrEqualTo(0));
  }

  @Test
  @org.junit.jupiter.api.Order(9)
  @DisplayName("GET /api/cotisations/recherche - Recherche avancée")
  void testRechercherCotisations() {
    given()
        .queryParam("membreId", membreTestId)
        .queryParam("statut", "EN_ATTENTE")
        .queryParam("annee", 2025)
        .queryParam("page", 0)
        .queryParam("size", 10)
        .when()
        .get("/api/cotisations/recherche")
        .then()
        .statusCode(200)
        .body("size()", greaterThanOrEqualTo(0));
  }

  @Test
  @org.junit.jupiter.api.Order(10)
  @DisplayName("GET /api/cotisations/stats - Statistiques des cotisations")
  void testGetStatistiquesCotisations() {
    given()
        .when()
        .get("/api/cotisations/stats")
        .then()
        .statusCode(200)
        .body("totalCotisations", notNullValue())
        .body("cotisationsPayees", notNullValue())
        .body("cotisationsEnRetard", notNullValue())
        .body("tauxPaiement", notNullValue());
  }

  @Test
  @org.junit.jupiter.api.Order(11)
  @DisplayName("DELETE /api/cotisations/{id} - Suppression d'une cotisation")
  void testDeleteCotisation() {
    // Créer une cotisation si nécessaire
    if (cotisationTestId == null) {
      CotisationDTO cotisation = createTestCotisation();
      cotisationTestId = Long.valueOf(cotisation.getId().toString());
    }

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

    // Vérifier que la cotisation est marquée comme annulée
    given()
        .pathParam("id", cotisationTestId)
        .when()
        .get("/api/cotisations/{id}")
        .then()
        .statusCode(200)
        .body("statut", equalTo("ANNULEE"));
  }

  @Test
  @DisplayName("GET /api/cotisations/{id} - Cotisation inexistante")
  void testGetCotisationByIdNotFound() {
    given()
        .pathParam("id", 99999L)
        .when()
        .get("/api/cotisations/{id}")
        .then()
        .statusCode(404)
        .body("error", equalTo("Cotisation non trouvée"));
  }

  @Test
  @DisplayName("POST /api/cotisations - Données invalides")
  void testCreateCotisationInvalidData() {
    CotisationDTO cotisationInvalide = new CotisationDTO();
    // Données manquantes ou invalides
    cotisationInvalide.setTypeCotisation("");
    cotisationInvalide.setMontantDu(new BigDecimal("-100"));

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

  /** Méthode utilitaire pour créer une cotisation de test */
  private CotisationDTO createTestCotisation() {
    CotisationDTO cotisation = new CotisationDTO();
    cotisation.setMembreId(UUID.fromString(membreTestId.toString()));
    cotisation.setTypeCotisation("MENSUELLE");
    cotisation.setMontantDu(new BigDecimal("25000.00"));
    cotisation.setDateEcheance(LocalDate.now().plusDays(30));
    cotisation.setDescription("Cotisation de test");
    cotisation.setPeriode("Test 2025");
    cotisation.setAnnee(2025);
    cotisation.setMois(1);

    return given()
        .contentType(ContentType.JSON)
        .body(cotisation)
        .when()
        .post("/api/cotisations")
        .then()
        .statusCode(201)
        .extract()
        .as(CotisationDTO.class);
  }
}
