package dev.lions.unionflow.server.service;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

import dev.lions.unionflow.server.entity.Evenement;
import dev.lions.unionflow.server.entity.Evenement.StatutEvenement;
import dev.lions.unionflow.server.entity.Evenement.TypeEvenement;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.repository.EvenementRepository;
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 java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.junit.jupiter.api.*;
import org.mockito.Mock;

/**
 * Tests unitaires pour EvenementService
 *
 * <p>Tests complets du service de gestion des événements avec validation des règles métier et
 * intégration Keycloak.
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-01-15
 */
@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@DisplayName("Tests unitaires - Service Événements")
class EvenementServiceTest {

  @Inject EvenementService evenementService;

  @Mock EvenementRepository evenementRepository;

  @Mock MembreRepository membreRepository;

  @Mock OrganisationRepository organisationRepository;

  @Mock KeycloakService keycloakService;

  private Evenement evenementTest;
  private Organisation organisationTest;
  private Membre membreTest;

  @BeforeEach
  void setUp() {
    // Données de test
    organisationTest =
        Organisation.builder()
            .nom("Union Test")
            .typeOrganisation("ASSOCIATION")
            .statut("ACTIVE")
            .email("test@union.com")
            .actif(true)
            .build();
    organisationTest.id = 1L;

    membreTest =
        Membre.builder()
            .numeroMembre("UF2025-TEST01")
            .prenom("Jean")
            .nom("Dupont")
            .email("jean.dupont@test.com")
            .actif(true)
            .build();
    membreTest.id = 1L;

    evenementTest =
        Evenement.builder()
            .titre("Assemblée Générale 2025")
            .description("Assemblée générale annuelle de l'union")
            .dateDebut(LocalDateTime.now().plusDays(30))
            .dateFin(LocalDateTime.now().plusDays(30).plusHours(3))
            .lieu("Salle de conférence")
            .typeEvenement(TypeEvenement.ASSEMBLEE_GENERALE)
            .statut(StatutEvenement.PLANIFIE)
            .capaciteMax(100)
            .prix(BigDecimal.valueOf(25.00))
            .inscriptionRequise(true)
            .visiblePublic(true)
            .actif(true)
            .organisation(organisationTest)
            .organisateur(membreTest)
            .build();
    evenementTest.id = 1L;
  }

  @Test
  @Order(1)
  @DisplayName("Création d'événement - Succès")
  void testCreerEvenement_Succes() {
    // Given
    when(keycloakService.getCurrentUserEmail()).thenReturn("jean.dupont@test.com");
    when(evenementRepository.findByTitre(anyString())).thenReturn(Optional.empty());
    doNothing().when(evenementRepository).persist(any(Evenement.class));

    // When
    Evenement resultat = evenementService.creerEvenement(evenementTest);

    // Then
    assertNotNull(resultat);
    assertEquals("Assemblée Générale 2025", resultat.getTitre());
    assertEquals(StatutEvenement.PLANIFIE, resultat.getStatut());
    assertTrue(resultat.getActif());
    assertEquals("jean.dupont@test.com", resultat.getCreePar());

    verify(evenementRepository).persist(any(Evenement.class));
  }

  @Test
  @Order(2)
  @DisplayName("Création d'événement - Titre obligatoire")
  void testCreerEvenement_TitreObligatoire() {
    // Given
    evenementTest.setTitre(null);

    // When & Then
    IllegalArgumentException exception =
        assertThrows(
            IllegalArgumentException.class, () -> evenementService.creerEvenement(evenementTest));

    assertEquals("Le titre de l'événement est obligatoire", exception.getMessage());
    verify(evenementRepository, never()).persist(any(Evenement.class));
  }

  @Test
  @Order(3)
  @DisplayName("Création d'événement - Date de début obligatoire")
  void testCreerEvenement_DateDebutObligatoire() {
    // Given
    evenementTest.setDateDebut(null);

    // When & Then
    IllegalArgumentException exception =
        assertThrows(
            IllegalArgumentException.class, () -> evenementService.creerEvenement(evenementTest));

    assertEquals("La date de début est obligatoire", exception.getMessage());
  }

  @Test
  @Order(4)
  @DisplayName("Création d'événement - Date de début dans le passé")
  void testCreerEvenement_DateDebutPassee() {
    // Given
    evenementTest.setDateDebut(LocalDateTime.now().minusDays(1));

    // When & Then
    IllegalArgumentException exception =
        assertThrows(
            IllegalArgumentException.class, () -> evenementService.creerEvenement(evenementTest));

    assertEquals("La date de début ne peut pas être dans le passé", exception.getMessage());
  }

  @Test
  @Order(5)
  @DisplayName("Création d'événement - Date de fin antérieure à date de début")
  void testCreerEvenement_DateFinInvalide() {
    // Given
    evenementTest.setDateFin(evenementTest.getDateDebut().minusHours(1));

    // When & Then
    IllegalArgumentException exception =
        assertThrows(
            IllegalArgumentException.class, () -> evenementService.creerEvenement(evenementTest));

    assertEquals(
        "La date de fin ne peut pas être antérieure à la date de début", exception.getMessage());
  }

  @Test
  @Order(6)
  @DisplayName("Mise à jour d'événement - Succès")
  void testMettreAJourEvenement_Succes() {
    // Given
    when(evenementRepository.findByIdOptional(1L)).thenReturn(Optional.of(evenementTest));
    when(keycloakService.hasRole("ADMIN")).thenReturn(true);
    when(keycloakService.getCurrentUserEmail()).thenReturn("admin@test.com");
    doNothing().when(evenementRepository).persist(any(Evenement.class));

    Evenement evenementMisAJour =
        Evenement.builder()
            .titre("Assemblée Générale 2025 - Modifiée")
            .description("Description mise à jour")
            .dateDebut(LocalDateTime.now().plusDays(35))
            .dateFin(LocalDateTime.now().plusDays(35).plusHours(4))
            .lieu("Nouvelle salle")
            .typeEvenement(TypeEvenement.ASSEMBLEE_GENERALE)
            .capaciteMax(150)
            .prix(BigDecimal.valueOf(30.00))
            .inscriptionRequise(true)
            .visiblePublic(true)
            .build();

    // When
    Evenement resultat = evenementService.mettreAJourEvenement(1L, evenementMisAJour);

    // Then
    assertNotNull(resultat);
    assertEquals("Assemblée Générale 2025 - Modifiée", resultat.getTitre());
    assertEquals("Description mise à jour", resultat.getDescription());
    assertEquals("Nouvelle salle", resultat.getLieu());
    assertEquals(150, resultat.getCapaciteMax());
    assertEquals(BigDecimal.valueOf(30.00), resultat.getPrix());
    assertEquals("admin@test.com", resultat.getModifiePar());

    verify(evenementRepository).persist(any(Evenement.class));
  }

  @Test
  @Order(7)
  @DisplayName("Mise à jour d'événement - Événement non trouvé")
  void testMettreAJourEvenement_NonTrouve() {
    // Given
    when(evenementRepository.findByIdOptional(999L)).thenReturn(Optional.empty());

    // When & Then
    IllegalArgumentException exception =
        assertThrows(
            IllegalArgumentException.class,
            () -> evenementService.mettreAJourEvenement(999L, evenementTest));

    assertEquals("Événement non trouvé avec l'ID: 999", exception.getMessage());
  }

  @Test
  @Order(8)
  @DisplayName("Suppression d'événement - Succès")
  void testSupprimerEvenement_Succes() {
    // Given
    when(evenementRepository.findByIdOptional(1L)).thenReturn(Optional.of(evenementTest));
    when(keycloakService.hasRole("ADMIN")).thenReturn(true);
    when(keycloakService.getCurrentUserEmail()).thenReturn("admin@test.com");
    when(evenementTest.getNombreInscrits()).thenReturn(0);
    doNothing().when(evenementRepository).persist(any(Evenement.class));

    // When
    assertDoesNotThrow(() -> evenementService.supprimerEvenement(1L));

    // Then
    assertFalse(evenementTest.getActif());
    assertEquals("admin@test.com", evenementTest.getModifiePar());
    verify(evenementRepository).persist(any(Evenement.class));
  }

  @Test
  @Order(9)
  @DisplayName("Recherche d'événements - Succès")
  void testRechercherEvenements_Succes() {
    // Given
    List<Evenement> evenementsAttendus = List.of(evenementTest);
    when(evenementRepository.findByTitreOrDescription(
            anyString(), any(Page.class), any(Sort.class)))
        .thenReturn(evenementsAttendus);

    // When
    List<Evenement> resultat =
        evenementService.rechercherEvenements("Assemblée", Page.of(0, 10), Sort.by("dateDebut"));

    // Then
    assertNotNull(resultat);
    assertEquals(1, resultat.size());
    assertEquals("Assemblée Générale 2025", resultat.get(0).getTitre());

    verify(evenementRepository)
        .findByTitreOrDescription(eq("Assemblée"), any(Page.class), any(Sort.class));
  }

  @Test
  @Order(10)
  @DisplayName("Changement de statut - Succès")
  void testChangerStatut_Succes() {
    // Given
    when(evenementRepository.findByIdOptional(1L)).thenReturn(Optional.of(evenementTest));
    when(keycloakService.hasRole("ADMIN")).thenReturn(true);
    when(keycloakService.getCurrentUserEmail()).thenReturn("admin@test.com");
    doNothing().when(evenementRepository).persist(any(Evenement.class));

    // When
    Evenement resultat = evenementService.changerStatut(1L, StatutEvenement.CONFIRME);

    // Then
    assertNotNull(resultat);
    assertEquals(StatutEvenement.CONFIRME, resultat.getStatut());
    assertEquals("admin@test.com", resultat.getModifiePar());

    verify(evenementRepository).persist(any(Evenement.class));
  }

  @Test
  @Order(11)
  @DisplayName("Statistiques des événements")
  void testObtenirStatistiques() {
    // Given
    Map<String, Long> statsBase =
        Map.of(
            "total", 100L,
            "actifs", 80L,
            "aVenir", 30L,
            "enCours", 5L,
            "passes", 45L,
            "publics", 70L,
            "avecInscription", 25L);
    when(evenementRepository.getStatistiques()).thenReturn(statsBase);

    // When
    Map<String, Object> resultat = evenementService.obtenirStatistiques();

    // Then
    assertNotNull(resultat);
    assertEquals(100L, resultat.get("total"));
    assertEquals(80L, resultat.get("actifs"));
    assertEquals(30L, resultat.get("aVenir"));
    assertEquals(80.0, resultat.get("tauxActivite"));
    assertEquals(37.5, resultat.get("tauxEvenementsAVenir"));
    assertEquals(6.25, resultat.get("tauxEvenementsEnCours"));
    assertNotNull(resultat.get("timestamp"));

    verify(evenementRepository).getStatistiques();
  }

  @Test
  @Order(12)
  @DisplayName("Lister événements actifs avec pagination")
  void testListerEvenementsActifs() {
    // Given
    List<Evenement> evenementsAttendus = List.of(evenementTest);
    when(evenementRepository.findAllActifs(any(Page.class), any(Sort.class)))
        .thenReturn(evenementsAttendus);

    // When
    List<Evenement> resultat =
        evenementService.listerEvenementsActifs(Page.of(0, 20), Sort.by("dateDebut"));

    // Then
    assertNotNull(resultat);
    assertEquals(1, resultat.size());
    assertEquals("Assemblée Générale 2025", resultat.get(0).getTitre());

    verify(evenementRepository).findAllActifs(any(Page.class), any(Sort.class));
  }

  @Test
  @Order(13)
  @DisplayName("Validation des règles métier - Prix négatif")
  void testValidation_PrixNegatif() {
    // Given
    evenementTest.setPrix(BigDecimal.valueOf(-10.00));

    // When & Then
    IllegalArgumentException exception =
        assertThrows(
            IllegalArgumentException.class, () -> evenementService.creerEvenement(evenementTest));

    assertEquals("Le prix ne peut pas être négatif", exception.getMessage());
  }

  @Test
  @Order(14)
  @DisplayName("Validation des règles métier - Capacité négative")
  void testValidation_CapaciteNegative() {
    // Given
    evenementTest.setCapaciteMax(-5);

    // When & Then
    IllegalArgumentException exception =
        assertThrows(
            IllegalArgumentException.class, () -> evenementService.creerEvenement(evenementTest));

    assertEquals("La capacité maximale ne peut pas être négative", exception.getMessage());
  }

  @Test
  @Order(15)
  @DisplayName("Permissions - Utilisateur non autorisé")
  void testPermissions_UtilisateurNonAutorise() {
    // Given
    when(evenementRepository.findByIdOptional(1L)).thenReturn(Optional.of(evenementTest));
    when(keycloakService.hasRole(anyString())).thenReturn(false);
    when(keycloakService.getCurrentUserEmail()).thenReturn("autre@test.com");

    // When & Then
    SecurityException exception =
        assertThrows(
            SecurityException.class,
            () -> evenementService.mettreAJourEvenement(1L, evenementTest));

    assertEquals(
        "Vous n'avez pas les permissions pour modifier cet événement", exception.getMessage());
  }
}
