package org.mte.numecoeval.referentiel.infrastructure.restapi.facade;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mte.numecoeval.referentiel.domain.exception.ReferentielException;
import org.mte.numecoeval.referentiel.domain.model.Hypothese;
import org.mte.numecoeval.referentiel.domain.model.id.HypotheseId;
import org.mte.numecoeval.referentiel.domain.ports.output.ReferentielPersistencePort;
import org.mte.numecoeval.referentiel.factory.TestDataFactory;
import org.mte.numecoeval.referentiel.infrastructure.mapper.HypotheseMapper;
import org.mte.numecoeval.referentiel.infrastructure.mapper.HypotheseMapperImpl;
import org.springframework.test.util.ReflectionTestUtils;

import java.util.Arrays;
import java.util.Collection;

import static org.junit.jupiter.api.Assertions.*;

class HypotheseFacadeTest {

    @InjectMocks
    HypotheseFacade facadeToTest;

    @Mock
    private ReferentielPersistencePort<Hypothese, HypotheseId> persistencePort;

    private HypotheseMapper mapper = new HypotheseMapperImpl();

    @BeforeEach
    void setup() {
        MockitoAnnotations.openMocks(this);
        ReflectionTestUtils.setField(facadeToTest, "mapper", mapper);
    }

    @Test
    void get_shouldReturnMatchingDTO() throws ReferentielException {
        var wantedDTOId = TestDataFactory.HypotheseFactory.idDTO("code");
        var expectedDomainID = TestDataFactory.HypotheseFactory.idDomain("code");
        var expectedDomain = TestDataFactory.HypotheseFactory.domain("code", "1.1", "Test");

        Mockito.when(persistencePort.get(expectedDomainID)).thenReturn(expectedDomain);

        var result = assertDoesNotThrow(() -> facadeToTest.get(wantedDTOId));

        assertEquals(expectedDomain.getCode(), result.getCode());
        assertEquals(expectedDomain.getValeur(), result.getValeur());
        assertEquals(expectedDomain.getSource(), result.getSource());
    }

    @Test
    void getAll_ShouldAllReturnMatchingDTO() {
        var expectedDomains = Arrays.asList(
                TestDataFactory.HypotheseFactory.domain("code", "2.0", "Test"),
                TestDataFactory.HypotheseFactory.domain("code2", "3.0", "Test")
        );

        Mockito.when(persistencePort.getAll()).thenReturn(expectedDomains);

        var result = assertDoesNotThrow( () -> facadeToTest.getAll() );

        assertEquals(expectedDomains.size(), result.size());

        expectedDomains.forEach( expectedDomain -> {
            var matchingDTO = result.stream()
                    .filter(critereDTO -> expectedDomain.getCode().equals(critereDTO.getCode()))
                    .findAny();

            assertTrue(matchingDTO.isPresent(), "Il n'existe pas de DTO correspondant au domain");
            var resultDTO = matchingDTO.get();
            assertEquals(expectedDomain.getCode(), resultDTO.getCode());
            assertEquals(expectedDomain.getValeur(), resultDTO.getValeur());
            assertEquals(expectedDomain.getSource(), resultDTO.getSource());

        });
    }

    @Test
    void purgeAndAddAll_ShouldCallPurgeThenSaveAll() throws ReferentielException {
        var dtosToSave = Arrays.asList(
                TestDataFactory.HypotheseFactory.dto("hyp", "1.0", "Test"),
                TestDataFactory.HypotheseFactory.dto("hyp2", "m3", "Autre Test")
        );
        ArgumentCaptor<Collection<Hypothese>> valueCapture = ArgumentCaptor.forClass(Collection.class);

        assertDoesNotThrow(() -> facadeToTest.purgeAndAddAll(dtosToSave));

        Mockito.verify(persistencePort, Mockito.times(1)).purge();
        Mockito.verify(persistencePort, Mockito.times(1)).saveAll(valueCapture.capture());

        var expectedDomains = valueCapture.getValue();
        assertNotNull(expectedDomains);
        assertEquals(dtosToSave.size(), expectedDomains.size());
        dtosToSave.forEach(dto -> {
            var matchingCriteres = expectedDomains.stream()
                    .filter(domain -> dto.getCode().equals(domain.getCode()))
                    .findAny();
            assertTrue(matchingCriteres.isPresent());
            var critere = matchingCriteres.get();
            assertEquals(dto.getCode(), critere.getCode());
            assertEquals(dto.getSource(), critere.getSource());
            assertEquals(dto.getValeur(), critere.getValeur());
        });
    }
}
