package dev.lions.unionflow.server.service;

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

import dev.lions.unionflow.server.api.dto.suggestion.request.CreateSuggestionRequest;
import dev.lions.unionflow.server.api.dto.suggestion.response.SuggestionResponse;
import dev.lions.unionflow.server.entity.Suggestion;
import dev.lions.unionflow.server.entity.SuggestionVote;
import dev.lions.unionflow.server.repository.SuggestionRepository;
import dev.lions.unionflow.server.repository.SuggestionVoteRepository;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.NotFoundException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.junit.jupiter.api.*;

/**
 * Tests unitaires pour SuggestionService
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2025-12-18
 */
@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class SuggestionServiceTest {

  @Inject
  SuggestionService suggestionService;
  @Inject
  SuggestionRepository suggestionRepository;
  @Inject
  SuggestionVoteRepository suggestionVoteRepository;

  private Suggestion testSuggestion;
  private UUID utilisateurId1;
  private UUID utilisateurId2;

  @BeforeEach
  @Transactional
  void setupTestData() {
    utilisateurId1 = UUID.randomUUID();
    utilisateurId2 = UUID.randomUUID();

    // Créer une suggestion de test
    testSuggestion = Suggestion.builder()
        .utilisateurId(utilisateurId1)
        .utilisateurNom("Test User")
        .titre("Suggestion de Test")
        .description("Description de test")
        .statut("NOUVELLE")
        .nbVotes(0)
        .nbCommentaires(0)
        .nbVues(0)
        .build();
    testSuggestion.setDateCreation(LocalDateTime.now());
    testSuggestion.setDateSoumission(LocalDateTime.now());
    testSuggestion.setActif(true);
    suggestionRepository.persist(testSuggestion);
  }

  @AfterEach
  @Transactional
  void cleanupTestData() {
    // Supprimer tous les votes
    if (testSuggestion != null && testSuggestion.getId() != null) {
      List<SuggestionVote> votes = suggestionVoteRepository.listerVotesParSuggestion(testSuggestion.getId());
      votes.forEach(vote -> suggestionVoteRepository.delete(vote));
    }

    // Supprimer la suggestion
    if (testSuggestion != null && testSuggestion.getId() != null) {
      Suggestion suggestionToDelete = suggestionRepository.findById(testSuggestion.getId());
      if (suggestionToDelete != null) {
        suggestionRepository.delete(suggestionToDelete);
      }
    }
  }

  @Test
  @Order(1)
  @DisplayName("Devrait lister toutes les suggestions")
  void testListerSuggestions() {
    // When
    List<SuggestionResponse> suggestions = suggestionService.listerSuggestions();

    // Then
    assertThat(suggestions).isNotNull();
    assertThat(suggestions).isNotEmpty();
    // Vérifier que notre suggestion de test est dans la liste
    assertThat(suggestions)
        .anyMatch(s -> s.getId().equals(testSuggestion.getId()));
  }

  @Test
  @Order(2)
  @DisplayName("Devrait créer une nouvelle suggestion")
  void testCreerSuggestion() {
    // Given
    CreateSuggestionRequest request = CreateSuggestionRequest.builder()
        .utilisateurId(utilisateurId2)
        .utilisateurNom("Nouvel Utilisateur")
        .titre("Nouvelle Suggestion")
        .description("Description de la nouvelle suggestion")
        .categorie("FEATURE")
        .prioriteEstimee("HAUTE")
        .build();

    // When
    SuggestionResponse created = suggestionService.creerSuggestion(request);

    // Then
    assertThat(created).isNotNull();
    assertThat(created.getId()).isNotNull();
    assertThat(created.getTitre()).isEqualTo("Nouvelle Suggestion");
    assertThat(created.getStatut()).isEqualTo("NOUVELLE");
    assertThat(created.getNbVotes()).isEqualTo(0);
    assertThat(created.getNbCommentaires()).isEqualTo(0);
    assertThat(created.getDateSoumission()).isNotNull();

    // Cleanup
    Suggestion createdEntity = suggestionRepository.findById(created.getId());
    if (createdEntity != null) {
      suggestionRepository.delete(createdEntity);
    }
  }

  @Test
  @Order(3)
  @DisplayName("Devrait permettre à un utilisateur de voter pour une suggestion")
  void testVoterPourSuggestion() {
    // Given
    UUID suggestionId = testSuggestion.getId();
    int nbVotesInitial = testSuggestion.getNbVotes() != null ? testSuggestion.getNbVotes() : 0;

    // When
    suggestionService.voterPourSuggestion(suggestionId, utilisateurId2);

    // Then
    // Vérifier que le vote a été créé
    assertThat(suggestionVoteRepository.aDejaVote(suggestionId, utilisateurId2)).isTrue();

    // Vérifier que le compteur de votes a été mis à jour
    Suggestion updatedSuggestion = suggestionRepository.findById(suggestionId);
    assertThat(updatedSuggestion.getNbVotes()).isEqualTo(nbVotesInitial + 1);
  }

  @Test
  @Order(4)
  @DisplayName("Ne devrait pas permettre à un utilisateur de voter deux fois")
  void testNePasPermettreVoteMultiple() {
    // Given
    UUID suggestionId = testSuggestion.getId();

    // Premier vote
    suggestionService.voterPourSuggestion(suggestionId, utilisateurId2);

    // When/Then - Tentative de vote multiple
    assertThatThrownBy(
        () -> suggestionService.voterPourSuggestion(suggestionId, utilisateurId2))
        .isInstanceOf(IllegalStateException.class)
        .hasMessageContaining("déjà voté");
  }

  @Test
  @Order(5)
  @DisplayName("Devrait lever une exception si la suggestion n'existe pas")
  void testVoterPourSuggestionInexistante() {
    // Given
    UUID suggestionInexistante = UUID.randomUUID();

    // When/Then
    assertThatThrownBy(
        () -> suggestionService.voterPourSuggestion(suggestionInexistante, utilisateurId2))
        .isInstanceOf(NotFoundException.class)
        .hasMessageContaining("non trouvée");
  }

  @Test
  @Order(6)
  @DisplayName("Devrait synchroniser le compteur de votes avec la base de données")
  void testSynchronisationCompteurVotes() {
    // Given
    UUID suggestionId = testSuggestion.getId();

    // Créer plusieurs votes directement dans la base
    SuggestionVote vote1 = SuggestionVote.builder()
        .suggestionId(suggestionId)
        .utilisateurId(utilisateurId1)
        .dateVote(LocalDateTime.now())
        .build();
    vote1.setActif(true);
    suggestionVoteRepository.persist(vote1);

    SuggestionVote vote2 = SuggestionVote.builder()
        .suggestionId(suggestionId)
        .utilisateurId(utilisateurId2)
        .dateVote(LocalDateTime.now())
        .build();
    vote2.setActif(true);
    suggestionVoteRepository.persist(vote2);

    // Mettre à jour le compteur manuellement (simulation d'un état désynchronisé)
    testSuggestion.setNbVotes(0);
    suggestionRepository.update(testSuggestion);

    // When - Voter via le service (qui doit synchroniser)
    UUID utilisateurId3 = UUID.randomUUID();
    suggestionService.voterPourSuggestion(suggestionId, utilisateurId3);

    // Then - Le compteur doit être synchronisé avec la base (2 votes existants + 1
    // nouveau = 3)
    Suggestion updatedSuggestion = suggestionRepository.findById(suggestionId);
    assertThat(updatedSuggestion.getNbVotes()).isEqualTo(3);
  }

  @Test
  @Order(8)
  @DisplayName("Devrait obtenir les statistiques des suggestions")
  void testObtenirStatistiques() {
    // When
    Map<String, Object> stats = suggestionService.obtenirStatistiques();

    // Then
    assertThat(stats).isNotNull();
    assertThat(stats).containsKey("totalSuggestions");
    assertThat(stats).containsKey("suggestionsImplementees");
    assertThat(stats).containsKey("totalVotes");
    assertThat(stats).containsKey("contributeursActifs");

    assertThat(stats.get("totalSuggestions")).isInstanceOf(Long.class);
    assertThat((Long) stats.get("totalSuggestions")).isGreaterThanOrEqualTo(1);
  }

  @Test
  @Order(7)
  @Transactional
  @DisplayName("voterPourSuggestion lève NotFoundException si suggestion inactive")
  void testVoterPourSuggestionInactive() {
    // Suggestion inactive → voter lève NotFoundException
    Suggestion inactiveSuggestion = Suggestion.builder()
        .utilisateurId(utilisateurId1)
        .utilisateurNom("Test User Inactive")
        .titre("Suggestion Inactive " + UUID.randomUUID())
        .description("Description inactive")
        .statut("NOUVELLE")
        .nbVotes(0)
        .nbCommentaires(0)
        .nbVues(0)
        .build();
    inactiveSuggestion.setDateCreation(LocalDateTime.now());
    inactiveSuggestion.setDateSoumission(LocalDateTime.now());
    inactiveSuggestion.setActif(false);
    suggestionRepository.persist(inactiveSuggestion);

    UUID inactiveSuggestionId = inactiveSuggestion.getId();

    // When/Then — suggestion trouvée mais inactive → NotFoundException
    assertThatThrownBy(
        () -> suggestionService.voterPourSuggestion(inactiveSuggestionId, utilisateurId2))
        .isInstanceOf(NotFoundException.class)
        .hasMessageContaining("non trouvée");

    // Nettoyage
    suggestionRepository.delete(inactiveSuggestion);
  }

  @Test
  @Order(9)
  @DisplayName("toDTO avec suggestion null retourne null (branche ligne 112 via réflexion)")
  void toDTO_nullSuggestion_returnsNull() throws Exception {
    java.lang.reflect.Method method = SuggestionService.class.getDeclaredMethod(
            "toDTO", dev.lions.unionflow.server.entity.Suggestion.class);
    method.setAccessible(true);
    Object result = method.invoke(suggestionService, (Object) null);
    assertThat(result).isNull();
  }

  @Test
  @Order(10)
  @DisplayName("toEntity avec request null retourne null (branche ligne 136 via réflexion)")
  void toEntity_nullRequest_returnsNull() throws Exception {
    java.lang.reflect.Method method = SuggestionService.class.getDeclaredMethod(
            "toEntity", dev.lions.unionflow.server.api.dto.suggestion.request.CreateSuggestionRequest.class);
    method.setAccessible(true);
    Object result = method.invoke(suggestionService, (Object) null);
    assertThat(result).isNull();
  }

  @Test
  @Order(11)
  @Transactional
  @DisplayName("obtenirStatistiques avec suggestion sans votes (nbVotes null) calcule totalVotes = 0")
  void obtenirStatistiques_avecSuggestionNbVotesNull_totalVotesZero() {
    // Créer une suggestion avec nbVotes = null pour couvrir la branche (lambda ligne 101: nbVotes != null ? x : 0)
    Suggestion suggestionSansVotes = Suggestion.builder()
            .utilisateurId(UUID.randomUUID())
            .utilisateurNom("Test Null Votes")
            .titre("Suggestion Sans Votes " + UUID.randomUUID())
            .description("Description")
            .statut("NOUVELLE")
            .nbVotes(null) // ← null pour déclencher la branche : 0
            .nbCommentaires(0)
            .nbVues(0)
            .build();
    suggestionSansVotes.setActif(true);
    suggestionRepository.persist(suggestionSansVotes);

    Map<String, Object> stats = suggestionService.obtenirStatistiques();

    // La lambda doit retourner 0 pour les nbVotes null sans exception
    assertThat(stats).containsKey("totalVotes");
    assertThat((Integer) stats.get("totalVotes")).isGreaterThanOrEqualTo(0);

    // Nettoyage
    suggestionRepository.delete(suggestionSansVotes);
  }
}
