package dev.lions.unionflow.server.repository;

import dev.lions.unionflow.server.entity.Organisation;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Sort;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

/**
 * Tests unitaires purs pour OrganisationRepository.
 *
 * <p>Ces tests couvrent :
 * <ul>
 *   <li>{@code buildOrderBy} avec Sort non-null mais colonnes vides (branche {@code isEmpty()==true})</li>
 *   <li>{@code findByOrganisationParente} et {@code findOrganisationsRacines} avec EntityManager mocké</li>
 * </ul>
 *
 * <p>On instancie OrganisationRepository directement (sans CDI/Quarkus) et on injecte
 * un EntityManager mocké par assignation directe du champ hérité.
 */
@DisplayName("OrganisationRepository — tests unitaires (buildOrderBy + JPQL hiérarchique)")
class OrganisationRepositoryUnitTest {

    /**
     * Sous-classe concrète non gérée par CDI.
     */
    static class TestOrganisationRepository extends OrganisationRepository {
        TestOrganisationRepository(EntityManager em) {
            super();
            this.entityManager = em;
        }
    }

    private EntityManager em;
    private TestOrganisationRepository repo;

    @BeforeEach
    void setUp() {
        em = mock(EntityManager.class);
        repo = new TestOrganisationRepository(em);
    }

    @SuppressWarnings("unchecked")
    private TypedQuery<Organisation> mockOrgQuery(List<Organisation> results) {
        TypedQuery<Organisation> q = mock(TypedQuery.class);
        when(q.setParameter(anyString(), any())).thenReturn(q);
        when(q.setFirstResult(anyInt())).thenReturn(q);
        when(q.setMaxResults(anyInt())).thenReturn(q);
        when(q.getResultList()).thenReturn(results);
        return q;
    }

    // ─── buildOrderBy via findByStatut ────────────────────────────────────────

    @Test
    @DisplayName("buildOrderBy: sort null → ORDER BY absent (branche sort==null)")
    void buildOrderBy_sortNull_noOrderBy() {
        TypedQuery<Organisation> q = mockOrgQuery(List.of());
        when(em.createQuery(anyString(), eq(Organisation.class))).thenReturn(q);

        Page page = new Page(0, 10);
        List<Organisation> result = repo.findByStatut("ACTIVE", page, null);
        assertThat(result).isNotNull();
    }

    @Test
    @DisplayName("buildOrderBy: sort avec une colonne ASC → ORDER BY o.nom ASC")
    void buildOrderBy_singleColumnAsc() {
        TypedQuery<Organisation> q = mockOrgQuery(List.of());
        when(em.createQuery(anyString(), eq(Organisation.class))).thenReturn(q);

        Page page = new Page(0, 10);
        Sort sort = Sort.ascending("nom");
        List<Organisation> result = repo.findByStatut("ACTIVE", page, sort);
        assertThat(result).isNotNull();
    }

    @Test
    @DisplayName("buildOrderBy: sort avec une colonne DESC → ORDER BY o.nom DESC")
    void buildOrderBy_singleColumnDesc() {
        TypedQuery<Organisation> q = mockOrgQuery(List.of());
        when(em.createQuery(anyString(), eq(Organisation.class))).thenReturn(q);

        Page page = new Page(0, 10);
        Sort sort = Sort.descending("nom");
        List<Organisation> result = repo.findByStatut("ACTIVE", page, sort);
        assertThat(result).isNotNull();
    }

    @Test
    @DisplayName("buildOrderBy: sort avec plusieurs colonnes → couvre la branche i>0")
    void buildOrderBy_multipleColumns_coversIPlusBranch() {
        TypedQuery<Organisation> q = mockOrgQuery(List.of());
        when(em.createQuery(anyString(), eq(Organisation.class))).thenReturn(q);

        Page page = new Page(0, 10);
        Sort sort = Sort.by("nom", Sort.Direction.Ascending).and("statut", Sort.Direction.Descending);
        List<Organisation> result = repo.findByStatut("ACTIVE", page, sort);
        assertThat(result).isNotNull();
    }

    @Test
    @DisplayName("buildOrderBy: sort non-null avec colonnes vides → retourne 'o.id' (branche isEmpty==true)")
    void buildOrderBy_emptySort_returnsDefaultOrderById() throws Exception {
        TypedQuery<Organisation> q = mockOrgQuery(List.of());
        when(em.createQuery(anyString(), eq(Organisation.class))).thenReturn(q);

        // Créer un Sort non-null avec liste de colonnes vide via réflexion
        Sort emptySort;
        try {
            java.lang.reflect.Constructor<Sort> ctor = Sort.class.getDeclaredConstructor();
            ctor.setAccessible(true);
            emptySort = ctor.newInstance();
        } catch (NoSuchMethodException e) {
            // Sort n'a pas de constructeur no-arg accessible : test skip
            return;
        }

        Page page = new Page(0, 10);
        List<Organisation> result = repo.findByStatut("ACTIVE", page, emptySort);
        assertThat(result).isNotNull();
    }

    // ─── findByOrganisationParente avec EntityManager mocké ──────────────────

    @Test
    @DisplayName("findByOrganisationParente: sort null → exécute la requête sans ORDER BY")
    void findByOrganisationParente_sortNull_executesQuery() {
        TypedQuery<Organisation> q = mockOrgQuery(List.of());
        when(em.createQuery(anyString(), eq(Organisation.class))).thenReturn(q);

        Page page = new Page(0, 10);
        List<Organisation> result = repo.findByOrganisationParente(UUID.randomUUID(), page, null);
        assertThat(result).isNotNull();
    }

    @Test
    @DisplayName("findByOrganisationParente: sort non-null → exécute la requête avec ORDER BY")
    void findByOrganisationParente_sortNonNull_executesQueryWithOrderBy() {
        TypedQuery<Organisation> q = mockOrgQuery(List.of());
        when(em.createQuery(anyString(), eq(Organisation.class))).thenReturn(q);

        Page page = new Page(0, 10);
        Sort sort = Sort.ascending("nom");
        List<Organisation> result = repo.findByOrganisationParente(UUID.randomUUID(), page, sort);
        assertThat(result).isNotNull();
    }

    @Test
    @DisplayName("findByOrganisationParente: retourne la liste fournie par la query")
    void findByOrganisationParente_returnsQueryResult() {
        Organisation org = new Organisation();
        org.setNom("Org Fille");
        TypedQuery<Organisation> q = mockOrgQuery(List.of(org));
        when(em.createQuery(anyString(), eq(Organisation.class))).thenReturn(q);

        Page page = new Page(0, 10);
        List<Organisation> result = repo.findByOrganisationParente(UUID.randomUUID(), page, null);
        assertThat(result).hasSize(1);
        assertThat(result.get(0).getNom()).isEqualTo("Org Fille");
    }

    // ─── findOrganisationsRacines avec EntityManager mocké ───────────────────

    @Test
    @DisplayName("findOrganisationsRacines: sort null → exécute la requête sans ORDER BY")
    void findOrganisationsRacines_sortNull_executesQuery() {
        TypedQuery<Organisation> q = mockOrgQuery(List.of());
        when(em.createQuery(anyString(), eq(Organisation.class))).thenReturn(q);

        Page page = new Page(0, 10);
        List<Organisation> result = repo.findOrganisationsRacines(page, null);
        assertThat(result).isNotNull();
    }

    @Test
    @DisplayName("findOrganisationsRacines: sort non-null → exécute la requête avec ORDER BY")
    void findOrganisationsRacines_sortNonNull_executesQueryWithOrderBy() {
        TypedQuery<Organisation> q = mockOrgQuery(List.of());
        when(em.createQuery(anyString(), eq(Organisation.class))).thenReturn(q);

        Page page = new Page(0, 10);
        Sort sort = Sort.ascending("nom");
        List<Organisation> result = repo.findOrganisationsRacines(page, sort);
        assertThat(result).isNotNull();
    }

    @Test
    @DisplayName("findOrganisationsRacines: retourne les organisations racines fournies par la query")
    void findOrganisationsRacines_returnsQueryResult() {
        Organisation racine1 = new Organisation();
        racine1.setNom("Racine 1");
        Organisation racine2 = new Organisation();
        racine2.setNom("Racine 2");
        TypedQuery<Organisation> q = mockOrgQuery(List.of(racine1, racine2));
        when(em.createQuery(anyString(), eq(Organisation.class))).thenReturn(q);

        Page page = new Page(0, 10);
        List<Organisation> result = repo.findOrganisationsRacines(page, null);
        assertThat(result).hasSize(2);
    }

    @Test
    @DisplayName("findOrganisationsRacines: pagination correctement appliquée")
    void findOrganisationsRacines_paginationApplied() {
        TypedQuery<Organisation> q = mockOrgQuery(List.of());
        when(em.createQuery(anyString(), eq(Organisation.class))).thenReturn(q);

        Page page = new Page(2, 5); // page index=2, size=5
        repo.findOrganisationsRacines(page, null);

        // Vérifie que setFirstResult et setMaxResults ont été appelés
        verify(q).setFirstResult(10); // 2 * 5 = 10
        verify(q).setMaxResults(5);
    }

    @Test
    @DisplayName("findByOrganisationParente: pagination correctement appliquée")
    void findByOrganisationParente_paginationApplied() {
        TypedQuery<Organisation> q = mockOrgQuery(List.of());
        when(em.createQuery(anyString(), eq(Organisation.class))).thenReturn(q);

        Page page = new Page(1, 20); // page index=1, size=20
        repo.findByOrganisationParente(UUID.randomUUID(), page, null);

        // Vérifie que setFirstResult et setMaxResults ont été appelés
        verify(q).setFirstResult(20); // 1 * 20 = 20
        verify(q).setMaxResults(20);
    }
}
