package dev.lions.unionflow.server.service;

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

import dev.lions.unionflow.server.api.dto.membre.MembreSearchCriteria;
import dev.lions.unionflow.server.api.dto.membre.MembreSearchResultDTO;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Sort;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import org.junit.jupiter.api.*;

/**
 * Tests pour la recherche avancée de membres
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-19
 */
@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class MembreServiceAdvancedSearchTest {

  @Inject MembreService membreService;
  @Inject MembreRepository membreRepository;
  @Inject OrganisationRepository organisationRepository;

  private Organisation testOrganisation;
  private List<Membre> testMembres;

  @BeforeEach
  @Transactional
  void setupTestData() {
    // Créer et persister une organisation de test
    testOrganisation =
        Organisation.builder()
            .nom("Organisation Test")
            .typeOrganisation("ASSOCIATION")
            .statut("ACTIF")
            .build();
    testOrganisation.setDateCreation(LocalDateTime.now());
    testOrganisation.setActif(true);
    organisationRepository.persist(testOrganisation);

    // Créer des membres de test avec différents profils
    testMembres =
        List.of(
            // Membre actif jeune
            createMembre("UF-2025-TEST001", "Dupont", "Marie", "marie.dupont@test.com", 
                "+221701234567", LocalDate.of(1995, 5, 15), LocalDate.of(2023, 1, 15), 
                "MEMBRE,SECRETAIRE", true),

            // Membre actif âgé
            createMembre("UF-2025-TEST002", "Martin", "Jean", "jean.martin@test.com", 
                "+221701234568", LocalDate.of(1970, 8, 20), LocalDate.of(2020, 3, 10), 
                "MEMBRE,PRESIDENT", true),

            // Membre inactif
            createMembre("UF-2025-TEST003", "Diallo", "Fatou", "fatou.diallo@test.com", 
                "+221701234569", LocalDate.of(1985, 12, 3), LocalDate.of(2021, 6, 5), 
                "MEMBRE", false),

            // Membre avec email spécifique
            createMembre("UF-2025-TEST004", "Sow", "Amadou", "amadou.sow@unionflow.com", 
                "+221701234570", LocalDate.of(1988, 3, 12), LocalDate.of(2022, 9, 20), 
                "MEMBRE,TRESORIER", true));

    // Persister tous les membres
    testMembres.forEach(membre -> membreRepository.persist(membre));
  }

  private Membre createMembre(String numero, String nom, String prenom, String email, 
      String telephone, LocalDate dateNaissance, LocalDate dateAdhesion, 
      String roles, boolean actif) {
    Membre membre = Membre.builder()
        .numeroMembre(numero)
        .nom(nom)
        .prenom(prenom)
        .email(email)
        .telephone(telephone)
        .dateNaissance(dateNaissance)
        .dateAdhesion(dateAdhesion)
        .roles(roles)
        .organisation(testOrganisation)
        .build();
    membre.setDateCreation(LocalDateTime.now());
    membre.setActif(actif);
    return membre;
  }

  @AfterEach
  @Transactional
  void cleanupTestData() {
    // Nettoyer les données de test
    if (testMembres != null) {
      testMembres.forEach(membre -> {
        if (membre.getId() != null) {
          membreRepository.delete(membre);
        }
      });
    }

    if (testOrganisation != null && testOrganisation.getId() != null) {
      organisationRepository.delete(testOrganisation);
    }
  }

  @Test
  @Order(1)
  @DisplayName("Doit effectuer une recherche par terme général")
  void testSearchByGeneralQuery() {
    // Given
    MembreSearchCriteria criteria = MembreSearchCriteria.builder().query("marie").build();

    // When
    MembreSearchResultDTO result =
        membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom"));

    // Then
    assertThat(result).isNotNull();
    assertThat(result.getTotalElements()).isEqualTo(1);
    assertThat(result.getMembres()).hasSize(1);
    assertThat(result.getMembres().get(0).getPrenom()).isEqualToIgnoringCase("Marie");
    assertThat(result.isFirst()).isTrue();
    assertThat(result.isLast()).isTrue();
  }

  @Test
  @Order(2)
  @DisplayName("Doit filtrer par statut actif")
  void testSearchByActiveStatus() {
    // Given
    MembreSearchCriteria criteria = MembreSearchCriteria.builder().statut("ACTIF").build();

    // When
    MembreSearchResultDTO result =
        membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom"));

    // Then
    assertThat(result).isNotNull();
    assertThat(result.getTotalElements()).isEqualTo(3); // 3 membres actifs
    assertThat(result.getMembres()).hasSize(3);
    assertThat(result.getMembres()).allMatch(membre -> "ACTIF".equals(membre.getStatut()));
  }

  @Test
  @Order(3)
  @DisplayName("Doit filtrer par tranche d'âge")
  void testSearchByAgeRange() {
    // Given
    MembreSearchCriteria criteria = MembreSearchCriteria.builder().ageMin(25).ageMax(35).build();

    // When
    MembreSearchResultDTO result =
        membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom"));

    // Then
    assertThat(result).isNotNull();
    assertThat(result.getTotalElements()).isGreaterThan(0);

    // Vérifier que tous les membres sont dans la tranche d'âge
    result
        .getMembres()
        .forEach(
            membre -> {
              if (membre.getDateNaissance() != null) {
                int age = LocalDate.now().getYear() - membre.getDateNaissance().getYear();
                assertThat(age).isBetween(25, 35);
              }
            });
  }

  @Test
  @Order(4)
  @DisplayName("Doit filtrer par période d'adhésion")
  void testSearchByAdhesionPeriod() {
    // Given
    MembreSearchCriteria criteria =
        MembreSearchCriteria.builder()
            .dateAdhesionMin(LocalDate.of(2022, 1, 1))
            .dateAdhesionMax(LocalDate.of(2023, 12, 31))
            .build();

    // When
    MembreSearchResultDTO result =
        membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("dateAdhesion"));

    // Then
    assertThat(result).isNotNull();
    assertThat(result.getTotalElements()).isGreaterThan(0);

    // Vérifier que toutes les dates d'adhésion sont dans la période
    result
        .getMembres()
        .forEach(
            membre -> {
              if (membre.getDateAdhesion() != null) {
                assertThat(membre.getDateAdhesion())
                    .isAfterOrEqualTo(LocalDate.of(2022, 1, 1))
                    .isBeforeOrEqualTo(LocalDate.of(2023, 12, 31));
              }
            });
  }

  @Test
  @Order(5)
  @DisplayName("Doit rechercher par email avec domaine spécifique")
  void testSearchByEmailDomain() {
    // Given
    MembreSearchCriteria criteria = MembreSearchCriteria.builder().email("@unionflow.com").build();

    // When
    MembreSearchResultDTO result =
        membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom"));

    // Then
    assertThat(result).isNotNull();
    assertThat(result.getTotalElements()).isEqualTo(1);
    assertThat(result.getMembres()).hasSize(1);
    assertThat(result.getMembres().get(0).getEmail()).contains("@unionflow.com");
  }

  @Test
  @Order(6)
  @DisplayName("Doit filtrer par rôles")
  void testSearchByRoles() {
    // Given
    MembreSearchCriteria criteria =
        MembreSearchCriteria.builder().roles(List.of("PRESIDENT", "SECRETAIRE")).build();

    // When
    MembreSearchResultDTO result =
        membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom"));

    // Then
    assertThat(result).isNotNull();
    assertThat(result.getTotalElements()).isGreaterThan(0);

    // Vérifier que tous les membres ont au moins un des rôles recherchés
    result
        .getMembres()
        .forEach(
            membre -> {
              assertThat(membre.getRole())
                  .satisfiesAnyOf(
                      role -> assertThat(role).contains("PRESIDENT"),
                      role -> assertThat(role).contains("SECRETAIRE"));
            });
  }

  @Test
  @Order(7)
  @DisplayName("Doit gérer la pagination correctement")
  void testPagination() {
    // Given
    MembreSearchCriteria criteria =
        MembreSearchCriteria.builder()
            .includeInactifs(true) // Inclure tous les membres
            .build();

    // When - Première page
    MembreSearchResultDTO firstPage =
        membreService.searchMembresAdvanced(criteria, Page.of(0, 2), Sort.by("nom"));

    // Then
    assertThat(firstPage).isNotNull();
    assertThat(firstPage.getCurrentPage()).isEqualTo(0);
    assertThat(firstPage.getPageSize()).isEqualTo(2);
    assertThat(firstPage.getMembres()).hasSizeLessThanOrEqualTo(2);
    assertThat(firstPage.isFirst()).isTrue();

    if (firstPage.getTotalElements() > 2) {
      assertThat(firstPage.isLast()).isFalse();
      assertThat(firstPage.isHasNext()).isTrue();
    }
  }

  @Test
  @Order(8)
  @DisplayName("Doit calculer les statistiques correctement")
  void testStatisticsCalculation() {
    // Given
    MembreSearchCriteria criteria =
        MembreSearchCriteria.builder()
            .includeInactifs(true) // Inclure tous les membres
            .build();

    // When
    MembreSearchResultDTO result =
        membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom"));

    // Then
    assertThat(result).isNotNull();
    assertThat(result.getStatistics()).isNotNull();

    MembreSearchResultDTO.SearchStatistics stats = result.getStatistics();
    assertThat(stats.getMembresActifs()).isEqualTo(3);
    assertThat(stats.getMembresInactifs()).isEqualTo(1);
    assertThat(stats.getAgeMoyen()).isGreaterThan(0);
    assertThat(stats.getAgeMin()).isGreaterThan(0);
    assertThat(stats.getAgeMax()).isGreaterThan(stats.getAgeMin());
    assertThat(stats.getAncienneteMoyenne()).isGreaterThanOrEqualTo(0);
  }

  @Test
  @Order(9)
  @DisplayName("Doit retourner un résultat vide pour critères impossibles")
  void testEmptyResultForImpossibleCriteria() {
    // Given
    MembreSearchCriteria criteria =
        MembreSearchCriteria.builder().query("membre_inexistant_xyz").build();

    // When
    MembreSearchResultDTO result =
        membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom"));

    // Then
    assertThat(result).isNotNull();
    assertThat(result.getTotalElements()).isEqualTo(0);
    assertThat(result.getMembres()).isEmpty();
    assertThat(result.isEmpty()).isTrue();
    assertThat(result.getTotalPages()).isEqualTo(0);
  }

  @Test
  @Order(10)
  @DisplayName("Doit valider la cohérence des critères")
  void testCriteriaValidation() {
    // Given - Critères incohérents
    MembreSearchCriteria invalidCriteria =
        MembreSearchCriteria.builder()
            .ageMin(50)
            .ageMax(30) // Âge max < âge min
            .build();

    // When & Then
    assertThat(invalidCriteria.isValid()).isFalse();
  }

  @Test
  @Order(11)
  @DisplayName("Doit avoir des performances acceptables (< 500ms)")
  void testSearchPerformance() {
    // Given
    MembreSearchCriteria criteria = MembreSearchCriteria.builder().includeInactifs(true).build();

    // When & Then - Mesurer le temps d'exécution
    long startTime = System.currentTimeMillis();

    MembreSearchResultDTO result =
        membreService.searchMembresAdvanced(criteria, Page.of(0, 20), Sort.by("nom"));

    long executionTime = System.currentTimeMillis() - startTime;

    // Vérifications
    assertThat(result).isNotNull();
    assertThat(executionTime).isLessThan(500L); // Moins de 500ms

    // Log pour monitoring
    System.out.printf(
        "Recherche avancée exécutée en %d ms pour %d résultats%n",
        executionTime, result.getTotalElements());
  }

  @Test
  @Order(12)
  @DisplayName("Doit gérer les critères avec caractères spéciaux")
  void testSearchWithSpecialCharacters() {
    // Given
    MembreSearchCriteria criteria =
        MembreSearchCriteria.builder().query("marie-josé").nom("o'connor").build();

    // When
    MembreSearchResultDTO result =
        membreService.searchMembresAdvanced(criteria, Page.of(0, 10), Sort.by("nom"));

    // Then
    assertThat(result).isNotNull();
    // La recherche ne doit pas échouer même avec des caractères spéciaux
    assertThat(result.getTotalElements()).isGreaterThanOrEqualTo(0);
  }
}
