package dev.lions.unionflow.server.repository.mutuelle.parts;

import dev.lions.unionflow.server.entity.mutuelle.parts.ComptePartsSociales;
import io.quarkus.hibernate.orm.panache.PanacheQuery;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

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

@DisplayName("ComptePartsSocialesRepository — tests unitaires")
class ComptePartsSocialesRepositoryTest {

    static class TestableComptePartsSocialesRepository extends ComptePartsSocialesRepository {
        // instanciation sans CDI
    }

    private ComptePartsSocialesRepository repo;

    @BeforeEach
    void setUp() {
        repo = spy(new TestableComptePartsSocialesRepository());
    }

    @Test
    @DisplayName("classExists : instanciation possible sans CDI")
    void classExists() {
        assertThat(new TestableComptePartsSocialesRepository()).isNotNull();
    }

    // ─── findByNumeroCompte ───────────────────────────────────────────────────

    @Test
    @DisplayName("findByNumeroCompte : retourne Optional.empty() si numéro inconnu")
    @SuppressWarnings("unchecked")
    void findByNumeroCompte_noResult_returnsEmpty() {
        PanacheQuery<ComptePartsSociales> query = mock(PanacheQuery.class);
        when(query.firstResultOptional()).thenReturn(Optional.empty());
        doReturn(query).when(repo).find(anyString(), anyString());

        Optional<ComptePartsSociales> result = repo.findByNumeroCompte("CPS-UNKNOWN");

        assertThat(result).isEmpty();
    }

    @Test
    @DisplayName("findByNumeroCompte : retourne le compte correspondant au numéro")
    @SuppressWarnings("unchecked")
    void findByNumeroCompte_withResult_returnsCompte() {
        ComptePartsSociales compte = ComptePartsSociales.builder()
                .numeroCompte("CPS-2025-001")
                .valeurNominale(new BigDecimal("5000"))
                .build();
        PanacheQuery<ComptePartsSociales> query = mock(PanacheQuery.class);
        when(query.firstResultOptional()).thenReturn(Optional.of(compte));
        doReturn(query).when(repo).find(anyString(), anyString());

        Optional<ComptePartsSociales> result = repo.findByNumeroCompte("CPS-2025-001");

        assertThat(result).isPresent();
        assertThat(result.get()).isSameAs(compte);
    }

    // ─── findByMembre ─────────────────────────────────────────────────────────

    @Test
    @DisplayName("findByMembre : retourne liste vide si aucun compte pour ce membre")
    void findByMembre_noResult_returnsEmpty() {
        doReturn(List.of()).when(repo).list(anyString(), any(UUID.class));

        List<ComptePartsSociales> result = repo.findByMembre(UUID.randomUUID());

        assertThat(result).isEmpty();
    }

    @Test
    @DisplayName("findByMembre : retourne les comptes actifs du membre")
    void findByMembre_withResult_returnsList() {
        ComptePartsSociales c1 = ComptePartsSociales.builder()
                .numeroCompte("CPS-M1-001")
                .valeurNominale(new BigDecimal("5000"))
                .build();
        ComptePartsSociales c2 = ComptePartsSociales.builder()
                .numeroCompte("CPS-M1-002")
                .valeurNominale(new BigDecimal("5000"))
                .build();
        doReturn(List.of(c1, c2)).when(repo).list(anyString(), any(UUID.class));

        List<ComptePartsSociales> result = repo.findByMembre(UUID.randomUUID());

        assertThat(result).hasSize(2).containsExactly(c1, c2);
    }

    // ─── findByOrganisation ───────────────────────────────────────────────────

    @Test
    @DisplayName("findByOrganisation : retourne liste vide si aucun compte pour cette organisation")
    void findByOrganisation_noResult_returnsEmpty() {
        doReturn(List.of()).when(repo).list(anyString(), any(UUID.class));

        List<ComptePartsSociales> result = repo.findByOrganisation(UUID.randomUUID());

        assertThat(result).isEmpty();
    }

    @Test
    @DisplayName("findByOrganisation : retourne les comptes actifs de l'organisation")
    void findByOrganisation_withResult_returnsList() {
        ComptePartsSociales compte = ComptePartsSociales.builder()
                .numeroCompte("CPS-ORG-001")
                .valeurNominale(new BigDecimal("5000"))
                .build();
        doReturn(List.of(compte)).when(repo).list(anyString(), any(UUID.class));

        List<ComptePartsSociales> result = repo.findByOrganisation(UUID.randomUUID());

        assertThat(result).hasSize(1).contains(compte);
    }

    // ─── findByMembreAndOrg ───────────────────────────────────────────────────

    @Test
    @DisplayName("findByMembreAndOrg : retourne Optional.empty() si aucun compte")
    @SuppressWarnings("unchecked")
    void findByMembreAndOrg_noResult_returnsEmpty() {
        PanacheQuery<ComptePartsSociales> query = mock(PanacheQuery.class);
        when(query.firstResultOptional()).thenReturn(Optional.empty());
        doReturn(query).when(repo).find(anyString(), any(UUID.class), any(UUID.class));

        Optional<ComptePartsSociales> result =
                repo.findByMembreAndOrg(UUID.randomUUID(), UUID.randomUUID());

        assertThat(result).isEmpty();
    }

    @Test
    @DisplayName("findByMembreAndOrg : retourne le compte du membre dans l'organisation")
    @SuppressWarnings("unchecked")
    void findByMembreAndOrg_withResult_returnsCompte() {
        ComptePartsSociales compte = ComptePartsSociales.builder()
                .numeroCompte("CPS-MO-001")
                .valeurNominale(new BigDecimal("5000"))
                .build();
        PanacheQuery<ComptePartsSociales> query = mock(PanacheQuery.class);
        when(query.firstResultOptional()).thenReturn(Optional.of(compte));
        doReturn(query).when(repo).find(anyString(), any(UUID.class), any(UUID.class));

        Optional<ComptePartsSociales> result =
                repo.findByMembreAndOrg(UUID.randomUUID(), UUID.randomUUID());

        assertThat(result).isPresent().contains(compte);
    }

    // ─── countByOrganisation ──────────────────────────────────────────────────

    @Test
    @DisplayName("countByOrganisation : retourne 0 si aucun compte")
    void countByOrganisation_noCompte_returnsZero() {
        doReturn(0L).when(repo).count(anyString(), any(UUID.class));

        long result = repo.countByOrganisation(UUID.randomUUID());

        assertThat(result).isEqualTo(0L);
    }

    @Test
    @DisplayName("countByOrganisation : retourne le nombre de comptes actifs de l'organisation")
    void countByOrganisation_withComptes_returnsCount() {
        doReturn(7L).when(repo).count(anyString(), any(UUID.class));

        long result = repo.countByOrganisation(UUID.randomUUID());

        assertThat(result).isEqualTo(7L);
    }
}
