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.CorrespondanceRefEquipement;
import org.mte.numecoeval.referentiel.domain.ports.output.ReferentielPersistencePort;
import org.mte.numecoeval.referentiel.factory.TestDataFactory;
import org.mte.numecoeval.referentiel.infrastructure.mapper.CorrespondanceRefEquipementMapper;
import org.mte.numecoeval.referentiel.infrastructure.mapper.CorrespondanceRefEquipementMapperImpl;
import org.springframework.test.util.ReflectionTestUtils;

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

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

class CorrespondanceRefEquipementFacadeTest {

    @InjectMocks
    CorrespondanceRefEquipementFacade facadeToTest;

    @Mock
    private ReferentielPersistencePort<CorrespondanceRefEquipement, String> persistencePort;

    private CorrespondanceRefEquipementMapper mapper = new CorrespondanceRefEquipementMapperImpl();

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

    @Test
    void get_shouldReturnMatchingDTO() throws ReferentielException {
        var wantedDTOId = "modele";
        var expectedDomainID = "modele";
        var expectedDomain = TestDataFactory.CorrespondanceRefEquipementFactory.domain(
                "modele", "refCible");

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

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

        assertEquals(expectedDomain.getModeleEquipementSource(), result.getModeleEquipementSource());
        assertEquals(expectedDomain.getRefEquipementCible(), result.getRefEquipementCible());
    }

    @Test
    void get_withNonMachingDTOShouldReturnNull() throws ReferentielException {
        var wantedDTOId = "modele";
        var expectedDomainID = "modele";

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

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

        assertNull(result);
    }

    @Test
    void getAll_shouldReturnMatchingAllDTOs() throws ReferentielException {
        var expectedDomains = Arrays.asList(
                TestDataFactory.CorrespondanceRefEquipementFactory.domain("modele", "refCible"),
                TestDataFactory.CorrespondanceRefEquipementFactory.domain("modele2", "refCible")
                );

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

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

        assertEquals(2, result.size());
    }

    @Test
    void purgeAndAddAll_ShouldCallPurgeThenSaveAll() throws ReferentielException {
        var dtosToSave = Arrays.asList(
                TestDataFactory.CorrespondanceRefEquipementFactory.dto(
                        "modele01",  "refCible"
                ),
                TestDataFactory.CorrespondanceRefEquipementFactory.dto(
                        "modele02",  "refCible"
                )
        );
        ArgumentCaptor<Collection<CorrespondanceRefEquipement>> 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.getModeleEquipementSource().equals(domain.getModeleEquipementSource()))
                    .findAny();
            assertTrue(matchingDomain.isPresent());
            var domain = matchingDomain.get();

            assertEquals(dto.getModeleEquipementSource(), domain.getModeleEquipementSource());
            assertEquals(dto.getRefEquipementCible(), domain.getRefEquipementCible());
        });
    }
}
