package dev.lions.unionflow.server.service.collectefonds;

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

import dev.lions.unionflow.server.api.dto.collectefonds.CampagneCollecteResponse;
import dev.lions.unionflow.server.api.dto.collectefonds.ContributionCollecteDTO;
import dev.lions.unionflow.server.api.enums.collectefonds.StatutCampagneCollecte;
import dev.lions.unionflow.server.entity.Membre;
import dev.lions.unionflow.server.entity.collectefonds.CampagneCollecte;
import dev.lions.unionflow.server.entity.collectefonds.ContributionCollecte;
import dev.lions.unionflow.server.mapper.collectefonds.CampagneCollecteMapper;
import dev.lions.unionflow.server.mapper.collectefonds.ContributionCollecteMapper;
import dev.lions.unionflow.server.repository.MembreRepository;
import dev.lions.unionflow.server.repository.collectefonds.CampagneCollecteRepository;
import dev.lions.unionflow.server.repository.collectefonds.ContributionCollecteRepository;
import io.quarkus.hibernate.orm.panache.PanacheQuery;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import jakarta.ws.rs.NotFoundException;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@QuarkusTest
class CampagneCollecteServiceTest {

    @Inject
    CampagneCollecteService service;

    @InjectMock
    CampagneCollecteRepository repository;

    @InjectMock
    ContributionCollecteRepository contributionRepository;

    @InjectMock
    MembreRepository membreRepository;

    @InjectMock
    ContributionCollecteMapper contributionMapper;

    @InjectMock
    CampagneCollecteMapper campagneMapper;

    @Test
    @DisplayName("contribuer met à jour les montants de la campagne")
    void contribuer_updatesAmounts() {
        UUID campagneId = UUID.randomUUID();
        CampagneCollecte campagne = new CampagneCollecte();
        campagne.setId(campagneId);
        campagne.setStatut(StatutCampagneCollecte.EN_COURS);
        campagne.setMontantCollecteActuel(BigDecimal.ZERO);
        campagne.setNombreDonateurs(0);

        ContributionCollecteDTO dto = new ContributionCollecteDTO();
        dto.setMontantSoutien(new BigDecimal("1000"));

        ContributionCollecte entity = new ContributionCollecte();
        entity.setMontantSoutien(new BigDecimal("1000"));

        when(repository.findByIdOptional(campagneId)).thenReturn(Optional.of(campagne));
        when(contributionMapper.toEntity(dto)).thenReturn(entity);
        when(contributionMapper.toDto(entity)).thenReturn(dto);

        service.contribuer(campagneId, dto);

        assertThat(campagne.getMontantCollecteActuel()).isEqualByComparingTo("1000");
        assertThat(campagne.getNombreDonateurs()).isEqualTo(1);
        verify(contributionRepository).persist(any(ContributionCollecte.class));
    }

    @Test
    @DisplayName("getCampagneById retourne le DTO quand la campagne existe")
    void getCampagneById_found_returnsDto() {
        UUID id = UUID.randomUUID();
        CampagneCollecte entity = new CampagneCollecte();
        CampagneCollecteResponse dto = new CampagneCollecteResponse();

        when(repository.findByIdOptional(id)).thenReturn(Optional.of(entity));
        when(campagneMapper.toDto(entity)).thenReturn(dto);

        CampagneCollecteResponse result = service.getCampagneById(id);

        assertThat(result).isEqualTo(dto);
    }

    @Test
    @DisplayName("getCampagneById lance NotFoundException si la campagne n'existe pas")
    void getCampagneById_notFound_throws() {
        UUID id = UUID.randomUUID();
        when(repository.findByIdOptional(id)).thenReturn(Optional.empty());

        assertThatThrownBy(() -> service.getCampagneById(id))
                .isInstanceOf(NotFoundException.class);
    }

    @Test
    @SuppressWarnings("unchecked")
    @DisplayName("getCampagnesByOrganisation retourne la liste des campagnes")
    void getCampagnesByOrganisation_returnsList() {
        UUID orgId = UUID.randomUUID();
        CampagneCollecte entity = new CampagneCollecte();
        CampagneCollecteResponse dto = new CampagneCollecteResponse();

        PanacheQuery<CampagneCollecte> mockQuery = mock(PanacheQuery.class);
        when(repository.find(anyString(), (Object[]) any())).thenReturn(mockQuery);
        when(mockQuery.stream()).thenReturn(List.of(entity).stream());
        when(campagneMapper.toDto(entity)).thenReturn(dto);

        List<CampagneCollecteResponse> result = service.getCampagnesByOrganisation(orgId);

        assertThat(result).hasSize(1).containsExactly(dto);
    }

    @Test
    @DisplayName("contribuer lance NotFoundException si campagne n'existe pas")
    void contribuer_campagneNotFound_throwsNotFound() {
        UUID campagneId = UUID.randomUUID();
        ContributionCollecteDTO dto = new ContributionCollecteDTO();
        dto.setMontantSoutien(new BigDecimal("500"));

        when(repository.findByIdOptional(campagneId)).thenReturn(Optional.empty());

        assertThatThrownBy(() -> service.contribuer(campagneId, dto))
                .isInstanceOf(NotFoundException.class);
    }

    @Test
    @DisplayName("contribuer lance NotFoundException si le membre donateur n'existe pas")
    void contribuer_membreNotFound_throwsNotFound() {
        UUID campagneId = UUID.randomUUID();
        UUID membreId = UUID.randomUUID();

        CampagneCollecte campagne = new CampagneCollecte();
        campagne.setId(campagneId);
        campagne.setStatut(StatutCampagneCollecte.EN_COURS);
        campagne.setMontantCollecteActuel(BigDecimal.ZERO);
        campagne.setNombreDonateurs(0);

        ContributionCollecteDTO dto = new ContributionCollecteDTO();
        dto.setMontantSoutien(new BigDecimal("500"));
        dto.setMembreDonateurId(membreId.toString());

        ContributionCollecte entity = new ContributionCollecte();
        entity.setMontantSoutien(new BigDecimal("500"));

        when(repository.findByIdOptional(campagneId)).thenReturn(Optional.of(campagne));
        when(contributionMapper.toEntity(dto)).thenReturn(entity);
        when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.empty());

        assertThatThrownBy(() -> service.contribuer(campagneId, dto))
                .isInstanceOf(NotFoundException.class);
    }

    @Test
    @DisplayName("contribuer avec campagne non EN_COURS lance IllegalStateException")
    void contribuer_campagneNonEnCours_throws() {
        UUID campagneId = UUID.randomUUID();
        CampagneCollecte campagne = new CampagneCollecte();
        campagne.setStatut(StatutCampagneCollecte.EXPIREE);

        ContributionCollecteDTO dto = new ContributionCollecteDTO();
        dto.setMontantSoutien(new BigDecimal("500"));

        when(repository.findByIdOptional(campagneId)).thenReturn(Optional.of(campagne));

        assertThatThrownBy(() -> service.contribuer(campagneId, dto))
                .isInstanceOf(IllegalStateException.class);
    }

    @Test
    @DisplayName("contribuer avec membre donateur trouvé définit le membre sur la contribution")
    void contribuer_avecMembreDonateurTrouve_setMembreDonateur() {
        UUID campagneId = UUID.randomUUID();
        UUID membreId = UUID.randomUUID();

        CampagneCollecte campagne = new CampagneCollecte();
        campagne.setId(campagneId);
        campagne.setStatut(StatutCampagneCollecte.EN_COURS);
        campagne.setMontantCollecteActuel(BigDecimal.ZERO);
        campagne.setNombreDonateurs(0);

        ContributionCollecteDTO dto = new ContributionCollecteDTO();
        dto.setMontantSoutien(new BigDecimal("2000"));
        dto.setMembreDonateurId(membreId.toString());

        ContributionCollecte entity = new ContributionCollecte();
        entity.setMontantSoutien(new BigDecimal("2000"));

        Membre membre = new Membre();
        membre.setId(membreId);

        when(repository.findByIdOptional(campagneId)).thenReturn(Optional.of(campagne));
        when(contributionMapper.toEntity(dto)).thenReturn(entity);
        when(membreRepository.findByIdOptional(membreId)).thenReturn(Optional.of(membre));
        when(contributionMapper.toDto(entity)).thenReturn(dto);

        service.contribuer(campagneId, dto);

        assertThat(entity.getMembreDonateur()).isEqualTo(membre);
        assertThat(campagne.getMontantCollecteActuel()).isEqualByComparingTo("2000");
        verify(contributionRepository).persist(any(ContributionCollecte.class));
    }
}
