package dev.lions.unionflow.server.repository;

import dev.lions.unionflow.server.entity.Evenement;
import dev.lions.unionflow.server.entity.FeedbackEvenement;
import dev.lions.unionflow.server.entity.Membre;
import io.quarkus.test.TestTransaction;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

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

@QuarkusTest
@DisplayName("FeedbackEvenementRepository — tests des méthodes de requête")
class FeedbackEvenementRepositoryTest {

    @Inject
    FeedbackEvenementRepository feedbackRepository;

    @Inject
    EntityManager em;

    // ─── Helpers ─────────────────────────────────────────────────────────────

    private Membre persistMembre() {
        Membre m = new Membre();
        m.setNumeroMembre("FB-MEM-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase());
        m.setPrenom("Fatoumata");
        m.setNom("Feedback");
        m.setEmail("fb." + UUID.randomUUID() + "@test.com");
        m.setDateNaissance(LocalDate.of(1992, 3, 10));
        m.setActif(true);
        em.persist(m);
        em.flush();
        return m;
    }

    private Evenement persistEvenement() {
        Evenement e = new Evenement();
        e.setTitre("Evénement Test " + UUID.randomUUID().toString().substring(0, 8));
        e.setDateDebut(LocalDateTime.now().plusDays(1));
        e.setStatut("PLANIFIE");
        e.setInscriptionRequise(false);
        e.setVisiblePublic(true);
        e.setActif(true);
        em.persist(e);
        em.flush();
        return e;
    }

    private FeedbackEvenement persistFeedback(Membre membre, Evenement evenement, int note, String statut) {
        FeedbackEvenement fb = FeedbackEvenement.builder()
                .membre(membre)
                .evenement(evenement)
                .note(note)
                .commentaire("Commentaire test")
                .dateFeedback(LocalDateTime.now())
                .moderationStatut(statut)
                .build();
        fb.setActif(true);
        em.persist(fb);
        em.flush();
        return fb;
    }

    // ─── findByMembreAndEvenement ─────────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findByMembreAndEvenement retourne le feedback pour le couple membre/événement")
    void findByMembreAndEvenement_existant_retourneFeedback() {
        Membre membre = persistMembre();
        Evenement evenement = persistEvenement();
        FeedbackEvenement fb = persistFeedback(membre, evenement, 4, "PUBLIE");

        Optional<FeedbackEvenement> result = feedbackRepository.findByMembreAndEvenement(
                membre.getId(), evenement.getId());

        assertThat(result).isPresent();
        assertThat(result.get().getId()).isEqualTo(fb.getId());
        assertThat(result.get().getNote()).isEqualTo(4);
    }

    @Test
    @TestTransaction
    @DisplayName("findByMembreAndEvenement retourne empty pour combinaison inexistante")
    void findByMembreAndEvenement_inexistant_retourneEmpty() {
        Optional<FeedbackEvenement> result = feedbackRepository.findByMembreAndEvenement(
                UUID.randomUUID(), UUID.randomUUID());

        assertThat(result).isEmpty();
    }

    // ─── findPubliesByEvenement ───────────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findPubliesByEvenement retourne uniquement les feedbacks publiés")
    void findPubliesByEvenement_retourneSeulementPublies() {
        Membre m1 = persistMembre();
        Membre m2 = persistMembre();
        Evenement evenement = persistEvenement();
        persistFeedback(m1, evenement, 5, "PUBLIE");
        persistFeedback(m2, evenement, 3, "EN_ATTENTE");

        List<FeedbackEvenement> result = feedbackRepository.findPubliesByEvenement(evenement.getId());

        assertThat(result).isNotNull();
        assertThat(result).hasSize(1);
        assertThat(result.get(0).getModerationStatut()).isEqualTo("PUBLIE");
    }

    @Test
    @TestTransaction
    @DisplayName("findPubliesByEvenement retourne liste vide pour événement sans feedback publié")
    void findPubliesByEvenement_aucunPublie_retourneListe() {
        Evenement evenement = persistEvenement();
        Membre m = persistMembre();
        persistFeedback(m, evenement, 2, "EN_ATTENTE");

        List<FeedbackEvenement> result = feedbackRepository.findPubliesByEvenement(evenement.getId());

        assertThat(result).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findPubliesByEvenement retourne liste vide pour événement sans feedback")
    void findPubliesByEvenement_aucunFeedback_retourneListeVide() {
        Evenement evenement = persistEvenement();

        List<FeedbackEvenement> result = feedbackRepository.findPubliesByEvenement(evenement.getId());

        assertThat(result).isEmpty();
    }

    // ─── findAllByEvenement ───────────────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findAllByEvenement retourne tous les feedbacks actifs (tous statuts)")
    void findAllByEvenement_retourneTousLesFeedbacks() {
        Membre m1 = persistMembre();
        Membre m2 = persistMembre();
        Membre m3 = persistMembre();
        Evenement evenement = persistEvenement();
        persistFeedback(m1, evenement, 5, "PUBLIE");
        persistFeedback(m2, evenement, 3, "EN_ATTENTE");
        persistFeedback(m3, evenement, 2, "REJETE");

        List<FeedbackEvenement> result = feedbackRepository.findAllByEvenement(evenement.getId());

        assertThat(result).hasSize(3);
    }

    @Test
    @TestTransaction
    @DisplayName("findAllByEvenement retourne liste vide pour événement sans feedback")
    void findAllByEvenement_aucunFeedback_retourneListeVide() {
        Evenement evenement = persistEvenement();

        List<FeedbackEvenement> result = feedbackRepository.findAllByEvenement(evenement.getId());

        assertThat(result).isEmpty();
    }

    // ─── calculateAverageNote ─────────────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("calculateAverageNote retourne la moyenne des notes publiées")
    void calculateAverageNote_avecFeedbacksPublies_retourneMoyenne() {
        Membre m1 = persistMembre();
        Membre m2 = persistMembre();
        Evenement evenement = persistEvenement();
        persistFeedback(m1, evenement, 4, "PUBLIE");
        persistFeedback(m2, evenement, 2, "PUBLIE");

        Double avg = feedbackRepository.calculateAverageNote(evenement.getId());

        assertThat(avg).isEqualTo(3.0);
    }

    @Test
    @TestTransaction
    @DisplayName("calculateAverageNote retourne 0.0 pour événement sans feedback publié")
    void calculateAverageNote_aucunFeedbackPublie_retourneZero() {
        Evenement evenement = persistEvenement();

        Double avg = feedbackRepository.calculateAverageNote(evenement.getId());

        assertThat(avg).isEqualTo(0.0);
    }

    @Test
    @TestTransaction
    @DisplayName("calculateAverageNote ignore les feedbacks en attente de modération")
    void calculateAverageNote_ignoreFeedbacksEnAttente() {
        Membre m1 = persistMembre();
        Membre m2 = persistMembre();
        Evenement evenement = persistEvenement();
        persistFeedback(m1, evenement, 5, "PUBLIE");
        persistFeedback(m2, evenement, 1, "EN_ATTENTE"); // doit être ignoré

        Double avg = feedbackRepository.calculateAverageNote(evenement.getId());

        assertThat(avg).isEqualTo(5.0); // seul le feedback PUBLIE compte
    }

    // ─── countPubliesByEvenement ──────────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("countPubliesByEvenement compte correctement les feedbacks publiés")
    void countPubliesByEvenement_avecFeedbacks_compteCorrectement() {
        Membre m1 = persistMembre();
        Membre m2 = persistMembre();
        Membre m3 = persistMembre();
        Evenement evenement = persistEvenement();
        persistFeedback(m1, evenement, 5, "PUBLIE");
        persistFeedback(m2, evenement, 4, "PUBLIE");
        persistFeedback(m3, evenement, 3, "EN_ATTENTE");

        long count = feedbackRepository.countPubliesByEvenement(evenement.getId());

        assertThat(count).isEqualTo(2);
    }

    @Test
    @TestTransaction
    @DisplayName("countPubliesByEvenement retourne 0 pour événement sans feedback publié")
    void countPubliesByEvenement_aucunPublie_retourneZero() {
        Evenement evenement = persistEvenement();

        long count = feedbackRepository.countPubliesByEvenement(evenement.getId());

        assertThat(count).isEqualTo(0);
    }

    // ─── findEnAttente ────────────────────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findEnAttente retourne les feedbacks en attente de modération")
    void findEnAttente_retourneFeedbacksEnAttente() {
        Membre m1 = persistMembre();
        Membre m2 = persistMembre();
        Evenement evenement = persistEvenement();
        persistFeedback(m1, evenement, 5, "EN_ATTENTE");
        persistFeedback(m2, evenement, 3, "PUBLIE"); // ne doit pas apparaître

        List<FeedbackEvenement> result = feedbackRepository.findEnAttente();

        assertThat(result).isNotNull();
        assertThat(result).isNotEmpty();
        result.forEach(fb -> assertThat(fb.getModerationStatut()).isEqualTo("EN_ATTENTE"));
    }

    @Test
    @TestTransaction
    @DisplayName("findEnAttente retourne liste vide si aucun feedback en attente")
    void findEnAttente_aucunEnAttente_retourneListeVide() {
        // Dans un test isolé avec DB vide ou feedbacks tous publiés
        // On vérifie uniquement que la méthode fonctionne sans erreur
        List<FeedbackEvenement> result = feedbackRepository.findEnAttente();

        assertThat(result).isNotNull();
    }

    // ─── Tests des méthodes métier sur l'entité FeedbackEvenement ────────────

    @Test
    @TestTransaction
    @DisplayName("mettreEnAttente change le statut à EN_ATTENTE")
    void mettreEnAttente_changStatutEnAttente() {
        Membre m = persistMembre();
        Evenement e = persistEvenement();
        FeedbackEvenement fb = persistFeedback(m, e, 4, "PUBLIE");

        fb.mettreEnAttente("Contenu inapproprié");

        assertThat(fb.getModerationStatut()).isEqualTo("EN_ATTENTE");
        assertThat(fb.getRaisonModeration()).isEqualTo("Contenu inapproprié");
    }

    @Test
    @TestTransaction
    @DisplayName("publier change le statut à PUBLIE et efface la raison")
    void publier_changStatutPublie() {
        Membre m = persistMembre();
        Evenement e = persistEvenement();
        FeedbackEvenement fb = persistFeedback(m, e, 3, "EN_ATTENTE");
        fb.setRaisonModeration("ancienne raison");

        fb.publier();

        assertThat(fb.getModerationStatut()).isEqualTo("PUBLIE");
        assertThat(fb.getRaisonModeration()).isNull();
    }

    @Test
    @TestTransaction
    @DisplayName("rejeter change le statut à REJETE avec une raison")
    void rejeter_changStatutRejete() {
        Membre m = persistMembre();
        Evenement e = persistEvenement();
        FeedbackEvenement fb = persistFeedback(m, e, 1, "EN_ATTENTE");

        fb.rejeter("Non conforme aux règles");

        assertThat(fb.getModerationStatut()).isEqualTo("REJETE");
        assertThat(fb.getRaisonModeration()).isEqualTo("Non conforme aux règles");
    }

    @Test
    @TestTransaction
    @DisplayName("isPublie retourne true pour statut PUBLIE")
    void isPublie_statutPublie_retourneTrue() {
        Membre m = persistMembre();
        Evenement e = persistEvenement();
        FeedbackEvenement fb = persistFeedback(m, e, 5, "PUBLIE");

        assertThat(fb.isPublie()).isTrue();
    }

    @Test
    @TestTransaction
    @DisplayName("isPublie retourne false pour statut EN_ATTENTE")
    void isPublie_statutEnAttente_retourneFalse() {
        Membre m = persistMembre();
        Evenement e = persistEvenement();
        FeedbackEvenement fb = persistFeedback(m, e, 5, "EN_ATTENTE");

        assertThat(fb.isPublie()).isFalse();
    }
}
