package org.mte.numecoeval.referentiel.infrastructure.jpa;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullSource;
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.MixElectrique;
import org.mte.numecoeval.referentiel.domain.model.id.MixElectriqueId;
import org.mte.numecoeval.referentiel.infrastructure.jpa.adapter.MixElectriqueJpaAdapter;
import org.mte.numecoeval.referentiel.infrastructure.jpa.entity.FacteurCaracterisationEntity;
import org.mte.numecoeval.referentiel.infrastructure.jpa.repository.FacteurCaracterisationRepository;
import org.mte.numecoeval.referentiel.infrastructure.mapper.FacteurCaracterisationMapper;
import org.mte.numecoeval.referentiel.infrastructure.mapper.FacteurCaracterisationMapperImpl;
import org.mte.numecoeval.referentiel.infrastructure.mapper.MixElectriqueMapper;
import org.mte.numecoeval.referentiel.infrastructure.mapper.MixElectriqueMapperImpl;
import org.mte.numecoeval.referentiel.utils.Constants;
import org.springframework.test.util.ReflectionTestUtils;

import java.util.Collections;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;

class MixElectriqueJpaAdapterTest {

    @InjectMocks
    private MixElectriqueJpaAdapter jpaAdapter;
    @Mock
    FacteurCaracterisationRepository repository;
    FacteurCaracterisationMapper facteurCaracterisationMapper = new FacteurCaracterisationMapperImpl();
    MixElectriqueMapper mapper = new MixElectriqueMapperImpl();
    private static final String PAYS = "France";
    private static final String CRITERE = "Changement climatique";

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

    private FacteurCaracterisationEntity facteurCaracterisationEntity() {
        return new FacteurCaracterisationEntity()
                .setNom(Constants.MIXELEC_NOM_LOW_VOLTAGE + "FR")
                .setNiveau(Constants.MIXELEC_NIVEAU)
                .setEtape(Constants.MIXELEC_ETAPEACV)
                .setCategorie(Constants.MIXELEC_CATEGORIE)
                .setCritere(CRITERE)
                .setLocalisation(PAYS)
                .setSource("Test")
                .setValeur(0.120);
    }

    private MixElectrique mixelectrique() {
        return new MixElectrique()
                .setCritere(CRITERE)
                .setPays(PAYS)
                .setSource("Test")
                .setRaccourcisAnglais("FR")
                .setValeur(0.120);
    }

    @Test
    void get_shouldReturnDomain() {
        var expectedEntity = facteurCaracterisationEntity();
        var wantedId = new MixElectriqueId()
                .setPays(PAYS)
                .setCritere(CRITERE);

        Mockito.when(repository.findByNiveauAndCategorieAndLocalisationAndCritere(Constants.MIXELEC_NIVEAU, Constants.MIXELEC_CATEGORIE, wantedId.getPays(), wantedId.getCritere())).thenReturn(List.of(expectedEntity));
        var actualDomain = assertDoesNotThrow(() -> jpaAdapter.get(wantedId));

        assertNotNull(actualDomain.getCritere());
        Assertions.assertEquals(expectedEntity.getCritere(), actualDomain.getCritere());
        Assertions.assertEquals(PAYS, actualDomain.getPays());
        Assertions.assertNull(actualDomain.getRaccourcisAnglais());
    }

    @Test
    void get_shouldThrowException() {
        var wantedId = new MixElectriqueId()
                .setPays("NonExistant")
                .setCritere("Inexistant");
        Mockito.when(repository.findByNiveauAndCategorieAndLocalisationAndCritere(Constants.MIXELEC_NIVEAU, Constants.MIXELEC_CATEGORIE, wantedId.getPays(), wantedId.getCritere())).thenReturn(Collections.emptyList());

        ReferentielException expectedException = assertThrows(ReferentielException.class, () -> jpaAdapter.get(wantedId));

        assertEquals("Mix Electrique non trouvé pour l'id MixElectriqueId(pays=NonExistant, critere=Inexistant)", expectedException.getMessage());
    }

    @Test
    void get_whenNull_shouldThrowException() {
        ReferentielException expectedException = assertThrows(ReferentielException.class, () -> jpaAdapter.get(null));

        assertEquals("Mix Electrique non trouvé pour l'id null", expectedException.getMessage());
    }

    @Test
    void purge_shouldCallDeleteAll() {
        jpaAdapter.purge();

        Mockito.verify(repository, Mockito.times(1)).deleteByNiveauAndCategorie(any(), any());
    }

    @Test
    void getAll_shouldCallfindAll() {
        var expectedEntity = facteurCaracterisationEntity();
        Mockito.when(repository.findByNiveauAndCategorie(any(), any())).thenReturn(Collections.singletonList(expectedEntity));

        var result = jpaAdapter.getAll();

        Mockito.verify(repository, Mockito.times(1)).findByNiveauAndCategorie(any(), any());
        assertNotNull(result);
        assertEquals(1, result.size());

        var actualDomain = result.get(0);
        assertNotNull(actualDomain.getCritere());
        assertNull(actualDomain.getRaccourcisAnglais());
        assertEquals(PAYS, actualDomain.getPays());
    }


    @Test
    void saveAll_shouldCallsaveAll() {
        var domainToSave = mixelectrique();
        var entitiesToSave = facteurCaracterisationMapper.toEntities(mapper.toFacteurCaracterisations(Collections.singletonList(domainToSave)));

        assertDoesNotThrow(() -> jpaAdapter.saveAll(Collections.singletonList(domainToSave)));
        Mockito.verify(repository).saveAll(entitiesToSave);
        Mockito.verify(repository, Mockito.times(1)).saveAll(entitiesToSave);
    }

    @Test
    void save_shouldSaveAndReturnDomain() {
        var domain = mixelectrique();
        var actualEntity = facteurCaracterisationMapper.toEntity(mapper.toFacteurCaracterisation(domain));
        var expectedEntity = facteurCaracterisationEntity();

        assertEquals(expectedEntity.getNom(), actualEntity.getNom());
        assertEquals(expectedEntity.getLocalisation(), actualEntity.getLocalisation());
        assertEquals(expectedEntity.getEtape(), actualEntity.getEtape());
        assertEquals(expectedEntity.getNiveau(), actualEntity.getNiveau());
        assertEquals(expectedEntity.getCategorie(), actualEntity.getCategorie());

        Mockito.when(repository.save(actualEntity)).thenReturn(actualEntity);

        assertDoesNotThrow(() -> jpaAdapter.save(domain));
        Mockito.verify(repository).save(actualEntity);
    }

    @ParameterizedTest
    @NullSource
    void save_shouldSaveAndReturnNull(MixElectrique nullValue) {
        var expectedDomain = assertDoesNotThrow(() -> jpaAdapter.save(nullValue));
        assertNull(expectedDomain);
    }

}
