package dev.lions.unionflow.server.repository;

import dev.lions.unionflow.server.api.enums.membre.StatutMembre;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.MembreOrganisation;
import dev.lions.unionflow.server.entity.Organisation;
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.Optional;
import java.util.UUID;

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

@QuarkusTest
@DisplayName("MembreOrganisationRepository — tests de la méthode findByMembreIdAndOrganisationId")
class MembreOrganisationRepositoryTest {

    @Inject
    MembreOrganisationRepository membreOrgRepository;

    @Inject
    EntityManager em;

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

    private Membre persistMembre() {
        Membre m = new Membre();
        m.setNumeroMembre("MO-MEM-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase());
        m.setPrenom("Koffi");
        m.setNom("OrgTest");
        m.setEmail("mo." + UUID.randomUUID() + "@test.com");
        m.setDateNaissance(LocalDate.of(1988, 4, 20));
        m.setActif(true);
        em.persist(m);
        em.flush();
        return m;
    }

    private Organisation persistOrganisation() {
        Organisation org = new Organisation();
        org.setNom("Org MO Test " + UUID.randomUUID().toString().substring(0, 8));
        org.setTypeOrganisation("ASSOCIATION");
        org.setStatut("ACTIVE");
        org.setEmail("orgmo." + UUID.randomUUID() + "@test.com");
        org.setDateCreation(LocalDateTime.now());
        org.setActif(true);
        em.persist(org);
        em.flush();
        return org;
    }

    private MembreOrganisation persistLien(Membre membre, Organisation organisation) {
        MembreOrganisation mo = MembreOrganisation.builder()
                .membre(membre)
                .organisation(organisation)
                .statutMembre(StatutMembre.ACTIF)
                .dateAdhesion(LocalDate.now())
                .build();
        mo.setActif(true);
        em.persist(mo);
        em.flush();
        return mo;
    }

    // ─── Tests ───────────────────────────────────────────────────────────────

    @Test
    @TestTransaction
    @DisplayName("findByMembreIdAndOrganisationId retourne le lien existant")
    void findByMembreIdAndOrganisationId_lienExistant_retourneLien() {
        Membre membre = persistMembre();
        Organisation org = persistOrganisation();
        MembreOrganisation mo = persistLien(membre, org);

        Optional<MembreOrganisation> result = membreOrgRepository.findByMembreIdAndOrganisationId(
                membre.getId(), org.getId());

        assertThat(result).isPresent();
        assertThat(result.get().getId()).isEqualTo(mo.getId());
    }

    @Test
    @TestTransaction
    @DisplayName("findByMembreIdAndOrganisationId retourne empty si le lien n'existe pas")
    void findByMembreIdAndOrganisationId_lienInexistant_retourneEmpty() {
        Optional<MembreOrganisation> result = membreOrgRepository.findByMembreIdAndOrganisationId(
                UUID.randomUUID(), UUID.randomUUID());

        assertThat(result).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findByMembreIdAndOrganisationId ne confond pas les membres")
    void findByMembreIdAndOrganisationId_autresMembres_retourneEmpty() {
        Membre membre1 = persistMembre();
        Membre membre2 = persistMembre();
        Organisation org = persistOrganisation();
        persistLien(membre1, org);

        // On cherche avec membre2 qui n'est pas dans cette organisation
        Optional<MembreOrganisation> result = membreOrgRepository.findByMembreIdAndOrganisationId(
                membre2.getId(), org.getId());

        assertThat(result).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findByMembreIdAndOrganisationId ne confond pas les organisations")
    void findByMembreIdAndOrganisationId_autresOrgs_retourneEmpty() {
        Membre membre = persistMembre();
        Organisation org1 = persistOrganisation();
        Organisation org2 = persistOrganisation();
        persistLien(membre, org1);

        // On cherche dans org2 où le membre n'est pas
        Optional<MembreOrganisation> result = membreOrgRepository.findByMembreIdAndOrganisationId(
                membre.getId(), org2.getId());

        assertThat(result).isEmpty();
    }

    @Test
    @TestTransaction
    @DisplayName("findById retourne null pour UUID inexistant")
    void findById_inexistant_retourneNull() {
        MembreOrganisation result = membreOrgRepository.findById(UUID.randomUUID());

        assertThat(result).isNull();
    }

    @Test
    @TestTransaction
    @DisplayName("persist et findById fonctionnent correctement")
    void persistAndFindById_retourneLienPersisteCorrectement() {
        Membre membre = persistMembre();
        Organisation org = persistOrganisation();
        MembreOrganisation mo = persistLien(membre, org);

        MembreOrganisation found = membreOrgRepository.findById(mo.getId());

        assertThat(found).isNotNull();
        assertThat(found.getStatutMembre()).isEqualTo(StatutMembre.ACTIF);
        assertThat(found.getDateAdhesion()).isEqualTo(LocalDate.now());
    }

    @Test
    @TestTransaction
    @DisplayName("isActif retourne true pour statut ACTIF et actif=true")
    void isActif_statutActifEtActif_retourneTrue() {
        Membre membre = persistMembre();
        Organisation org = persistOrganisation();
        MembreOrganisation mo = persistLien(membre, org);
        mo.setStatutMembre(StatutMembre.ACTIF);
        mo.setActif(true);

        assertThat(mo.isActif()).isTrue();
    }

    @Test
    @TestTransaction
    @DisplayName("isActif retourne false pour statut EN_ATTENTE_VALIDATION")
    void isActif_statutEnAttente_retourneFalse() {
        Membre membre = persistMembre();
        Organisation org = persistOrganisation();
        MembreOrganisation mo = persistLien(membre, org);
        mo.setStatutMembre(StatutMembre.EN_ATTENTE_VALIDATION);

        assertThat(mo.isActif()).isFalse();
    }

    @Test
    @TestTransaction
    @DisplayName("peutDemanderAide retourne true pour statut ACTIF")
    void peutDemanderAide_statutActif_retourneTrue() {
        Membre membre = persistMembre();
        Organisation org = persistOrganisation();
        MembreOrganisation mo = persistLien(membre, org);
        mo.setStatutMembre(StatutMembre.ACTIF);

        assertThat(mo.peutDemanderAide()).isTrue();
    }

    @Test
    @TestTransaction
    @DisplayName("peutDemanderAide retourne false pour statut EN_ATTENTE_VALIDATION")
    void peutDemanderAide_statutEnAttente_retourneFalse() {
        Membre membre = persistMembre();
        Organisation org = persistOrganisation();
        MembreOrganisation mo = persistLien(membre, org);
        mo.setStatutMembre(StatutMembre.EN_ATTENTE_VALIDATION);

        assertThat(mo.peutDemanderAide()).isFalse();
    }

    // =========================================================================
    // findAllByOrganisationId — L36-38 (non couvert précédemment)
    // =========================================================================

    @Test
    @TestTransaction
    @DisplayName("findAllByOrganisationId retourne la liste des membres actifs pour une organisation")
    void findAllByOrganisationId_membresActifs_retourneListe() {
        Membre membre = persistMembre();
        Organisation org = persistOrganisation();
        MembreOrganisation mo = persistLien(membre, org);

        java.util.List<MembreOrganisation> result = membreOrgRepository.findAllByOrganisationId(org.getId());

        assertThat(result).isNotNull();
        assertThat(result).isNotEmpty();
        assertThat(result.stream().anyMatch(m -> m.getId().equals(mo.getId()))).isTrue();
    }

    @Test
    @TestTransaction
    @DisplayName("findAllByOrganisationId retourne liste vide pour organisation inexistante")
    void findAllByOrganisationId_organisationInexistante_retourneListeVide() {
        java.util.List<MembreOrganisation> result = membreOrgRepository.findAllByOrganisationId(UUID.randomUUID());

        assertThat(result).isNotNull();
        assertThat(result).isEmpty();
    }
}
