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

import org.junit.jupiter.api.Assertions;
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.ImpactMessagerie;
import org.mte.numecoeval.referentiel.domain.ports.output.ReferentielPersistencePort;
import org.mte.numecoeval.referentiel.factory.TestDataFactory;
import org.mte.numecoeval.referentiel.infrastructure.mapper.ImpactMessagerieMapper;
import org.mte.numecoeval.referentiel.infrastructure.mapper.ImpactMessagerieMapperImpl;
import org.springframework.test.util.ReflectionTestUtils;

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

import static org.junit.jupiter.api.Assertions.*;
import static org.mte.numecoeval.referentiel.factory.TestDataFactory.DEFAULT_CRITERE;
import static org.mte.numecoeval.referentiel.factory.TestDataFactory.DEFAULT_UNITE;

class ImpactMessagerieFacadeTest {

    @InjectMocks
    ImpactMessagerieFacade facadeToTest;

    @Mock
    private ReferentielPersistencePort<ImpactMessagerie, String> persistencePort;

    private ImpactMessagerieMapper mapper = new ImpactMessagerieMapperImpl();

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

    @Test
    void get_shouldReturnMatchingDTO() throws ReferentielException {
        var wantedDTOId = DEFAULT_CRITERE;
        var expectedDomainID = DEFAULT_CRITERE;
        var expectedDomain = TestDataFactory.ImpactMessagerieFactory.domain(
                TestDataFactory.CritereFactory.domain(DEFAULT_CRITERE, DEFAULT_UNITE, DEFAULT_CRITERE),
                2.0, 0.0005, "test"
        );

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

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

        assertEquals(expectedDomain.getCritere(), result.getCritere());
        assertEquals(expectedDomain.getConstanteCoefficientDirecteur(), result.getConstanteCoefficientDirecteur());
        assertEquals(expectedDomain.getConstanteOrdonneeOrigine(), result.getConstanteOrdonneeOrigine());
        assertEquals(expectedDomain.getSource(), result.getSource());
    }

    @Test
    void purgeAndAddAll_ShouldCallPurgeThenSaveAll() throws ReferentielException {
        var dtosToSave = Arrays.asList(
                TestDataFactory.ImpactMessagerieFactory.dto(
                        TestDataFactory.CritereFactory.dto(DEFAULT_CRITERE, DEFAULT_UNITE, DEFAULT_CRITERE),
                        2.0, 0.0005, "test"
                ),
                TestDataFactory.ImpactMessagerieFactory.dto(
                        TestDataFactory.CritereFactory.dto("Acidification", "m3", "Autre Test"),
                        0.005051, 0.0000000523, "test"
                )
        );
        ArgumentCaptor<Collection<ImpactMessagerie>> 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 matchingDomain = expectedDomains.stream()
                    .filter(domain -> dto.getCritere().equals(domain.getCritere()))
                    .findAny();
            assertTrue(matchingDomain.isPresent());
            var domain = matchingDomain.get();
            assertEquals(dto.getCritere(), domain.getCritere());
            assertEquals(dto.getConstanteCoefficientDirecteur(), domain.getConstanteCoefficientDirecteur());
            assertEquals(dto.getConstanteOrdonneeOrigine(), domain.getConstanteOrdonneeOrigine());
            assertEquals(dto.getSource(), domain.getSource());
        });
    }

    @Test
    void getAllImpactMessagerie_ShouldReturnAllDataAvailable() throws ReferentielException {
        var domainsAvailable = Arrays.asList(
                TestDataFactory.ImpactMessagerieFactory.domain(
                        TestDataFactory.CritereFactory.domain(DEFAULT_CRITERE, DEFAULT_UNITE, DEFAULT_CRITERE),
                        2.0, 0.0005, "test"
                ),
                TestDataFactory.ImpactMessagerieFactory.domain(
                        TestDataFactory.CritereFactory.domain("Acidification", "m3", "Autre Test"),
                        0.005051, 0.0000000523, "test"
                )
        );
        Mockito.when(persistencePort.getAll()).thenReturn(domainsAvailable);

        var expectedDTOs = Assertions.assertDoesNotThrow(() -> facadeToTest.getAllImpactMessagerie());

        Mockito.verify(persistencePort, Mockito.times(1)).getAll();

        domainsAvailable.forEach(domain -> {
            var matchingDTO = expectedDTOs.stream()
                    .filter(dto -> domain.getCritere().equals(dto.getCritere()))
                    .findAny();
            assertTrue(matchingDTO.isPresent());
            var dto = matchingDTO.get();
            assertEquals(domain.getCritere(), dto.getCritere());
            assertEquals(domain.getConstanteCoefficientDirecteur(), dto.getConstanteCoefficientDirecteur());
            assertEquals(domain.getConstanteOrdonneeOrigine(), dto.getConstanteOrdonneeOrigine());
            assertEquals(domain.getSource(), dto.getSource());
        });
    }
}
