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

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.vote.CampagneVoteRequest;
import dev.lions.unionflow.server.api.dto.vote.CampagneVoteResponse;
import dev.lions.unionflow.server.api.dto.vote.CandidatDTO;
import dev.lions.unionflow.server.api.enums.vote.StatutVote;
import dev.lions.unionflow.server.entity.Organisation;
import dev.lions.unionflow.server.entity.vote.CampagneVote;
import dev.lions.unionflow.server.entity.vote.Candidat;
import dev.lions.unionflow.server.mapper.vote.CampagneVoteMapper;
import dev.lions.unionflow.server.mapper.vote.CandidatMapper;
import dev.lions.unionflow.server.repository.OrganisationRepository;
import dev.lions.unionflow.server.repository.vote.CampagneVoteRepository;
import dev.lions.unionflow.server.repository.vote.CandidatRepository;
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.util.List;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@QuarkusTest
class CampagneVoteServiceTest {

    @Inject
    CampagneVoteService service;

    @InjectMock
    CampagneVoteRepository repository;

    @InjectMock
    CandidatRepository candidatRepository;

    @InjectMock
    OrganisationRepository organisationRepository;

    @InjectMock
    CampagneVoteMapper mapper;

    @InjectMock
    CandidatMapper candidatMapper;

    @Test
    @DisplayName("creerCampagne initialise le statut à BROUILLON")
    void creerCampagne_success() {
        UUID orgId = UUID.randomUUID();
        CampagneVoteRequest request = new CampagneVoteRequest();
        request.setOrganisationId(orgId.toString());

        Organisation org = new Organisation();
        org.setId(orgId);

        CampagneVote entity = new CampagneVote();
        CampagneVoteResponse responseDto = new CampagneVoteResponse();

        when(organisationRepository.findByIdOptional(orgId)).thenReturn(Optional.of(org));
        when(mapper.toEntity(request)).thenReturn(entity);
        when(mapper.toDto(entity)).thenReturn(responseDto);

        service.creerCampagne(request);

        assertThat(entity.getStatut()).isEqualTo(StatutVote.BROUILLON);
        assertThat(entity.getOrganisation()).isEqualTo(org);
        verify(repository).persist(any(CampagneVote.class));
    }

    @Test
    @DisplayName("creerCampagne lance NotFoundException si l'organisation n'existe pas")
    void creerCampagne_orgNotFound_throwsNotFound() {
        UUID orgId = UUID.randomUUID();
        CampagneVoteRequest request = new CampagneVoteRequest();
        request.setOrganisationId(orgId.toString());

        when(organisationRepository.findByIdOptional(orgId)).thenReturn(Optional.empty());

        assertThatThrownBy(() -> service.creerCampagne(request))
                .isInstanceOf(NotFoundException.class);
    }

    @Test
    @DisplayName("ajouterCandidat lance NotFoundException si la campagne n'existe pas")
    void ajouterCandidat_campagneNotFound_throwsNotFound() {
        UUID campagneId = UUID.randomUUID();
        CandidatDTO dto = new CandidatDTO();

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

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

    @Test
    @DisplayName("ajouterCandidat lie le candidat à la campagne")
    void ajouterCandidat_success() {
        UUID campagneId = UUID.randomUUID();
        CampagneVote campagne = new CampagneVote();
        campagne.setId(campagneId);

        CandidatDTO dto = new CandidatDTO();
        Candidat entity = new Candidat();

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

        service.ajouterCandidat(campagneId, dto);

        assertThat(entity.getCampagneVote()).isEqualTo(campagne);
        verify(candidatRepository).persist(any(Candidat.class));
    }

    @Test
    @DisplayName("ajouterCandidat avec membreIdAssocie non null le définit sur le candidat")
    void ajouterCandidat_withMembreIdAssocie_setsMembreId() {
        UUID campagneId = UUID.randomUUID();
        CampagneVote campagne = new CampagneVote();
        campagne.setId(campagneId);

        String membreId = UUID.randomUUID().toString();
        CandidatDTO dto = new CandidatDTO();
        dto.setMembreIdAssocie(membreId);

        Candidat entity = new Candidat();

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

        service.ajouterCandidat(campagneId, dto);

        // Le membreIdAssocie doit être défini sur l'entité
        assertThat(entity.getMembreIdAssocie()).isEqualTo(membreId);
        verify(candidatRepository).persist(any(Candidat.class));
    }

    @Test
    @DisplayName("getCampagneById retourne la réponse quand la campagne existe")
    void getCampagneById_found_returnsResponse() {
        UUID id = UUID.randomUUID();
        CampagneVote campagne = new CampagneVote();
        campagne.setId(id);
        CampagneVoteResponse responseDto = new CampagneVoteResponse();
        responseDto.setId(id);

        when(repository.findByIdOptional(id)).thenReturn(Optional.of(campagne));
        when(mapper.toDto(campagne)).thenReturn(responseDto);

        CampagneVoteResponse result = service.getCampagneById(id);

        assertThat(result).isNotNull();
        assertThat(result.getId()).isEqualTo(id);
    }

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

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

    @Test
    @DisplayName("getCampagnesByOrganisation retourne les campagnes de l'organisation")
    @SuppressWarnings("unchecked")
    void getCampagnesByOrganisation_returnsList() {
        UUID orgId = UUID.randomUUID();
        CampagneVote campagne = new CampagneVote();
        campagne.setId(UUID.randomUUID());
        CampagneVoteResponse responseDto = new CampagneVoteResponse();

        PanacheQuery<CampagneVote> mockQuery = mock(PanacheQuery.class);
        when(repository.find(anyString(), (Object) any())).thenReturn(mockQuery);
        when(mockQuery.stream()).thenReturn(List.of(campagne).stream());
        when(mapper.toDto(campagne)).thenReturn(responseDto);

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

        assertThat(result).isNotNull();
        assertThat(result).hasSize(1);
    }

    @Test
    @DisplayName("changerStatut met à jour le statut de la campagne")
    void changerStatut_success_updatesStatut() {
        UUID id = UUID.randomUUID();
        CampagneVote campagne = new CampagneVote();
        campagne.setId(id);
        campagne.setStatut(StatutVote.BROUILLON);
        CampagneVoteResponse responseDto = new CampagneVoteResponse();
        responseDto.setId(id);

        when(repository.findByIdOptional(id)).thenReturn(Optional.of(campagne));
        when(mapper.toDto(campagne)).thenReturn(responseDto);

        CampagneVoteResponse result = service.changerStatut(id, StatutVote.OUVERT);

        assertThat(campagne.getStatut()).isEqualTo(StatutVote.OUVERT);
        assertThat(result).isNotNull();
    }

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

        assertThatThrownBy(() -> service.changerStatut(id, StatutVote.OUVERT))
                .isInstanceOf(NotFoundException.class);
    }
}
