package org.mte.numecoeval.expositiondonneesentrees.infrastructure.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mte.numecoeval.expositiondonneesentrees.domain.exception.ValidationException;
import org.mte.numecoeval.expositiondonneesentrees.generated.api.model.DureeUsage;
import org.mte.numecoeval.expositiondonneesentrees.infrastructure.adapters.ReferentielRestClient;
import org.mte.numecoeval.expositiondonneesentrees.infrastructure.jpa.entity.DonneesEntreesEntity;
import org.mte.numecoeval.expositiondonneesentrees.referentiels.generated.api.model.CritereDTO;
import org.mte.numecoeval.expositiondonneesentrees.referentiels.generated.api.model.EtapeDTO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.util.ReflectionTestUtils;

import java.util.Arrays;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.mte.numecoeval.expositiondonneesentrees.infrastructure.controller.CalculController.TYPE_CALCUL_SOUMISSION;

@ExtendWith({SpringExtension.class})
@ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class)
public class DonneesEntreesServiceTest {
    @InjectMocks
    DonneesEntreesService donneesEntreesService;
    @Mock
    ReferentielRestClient referentielRestClient;
    private static final ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());

    @Value("${regle-par-defaut-duree-usage}")
    private String defaultDureeUsage;

    @BeforeEach
    void init() throws JsonProcessingException {
        ReflectionTestUtils.setField(donneesEntreesService, "defaultDureeUsage", defaultDureeUsage);

        /* MOCK REFERENTIEL : Etapes */
        Mockito.lenient().when(referentielRestClient.getAllEtapes()).thenReturn(Arrays.asList(mapper.readValue("""
                [{ "code": "UTILISATION", "libelle": "Using" },
                { "code": "FABRICATION", "libelle": "Manufacturing" },
                { "code": "DISTRIBUTION", "libelle": "Transportation" }]
                """, EtapeDTO[].class)));

        /* MOCK REFERENTIEL : Criteres */
        Mockito.lenient().when(referentielRestClient.getAllCriteres()).thenReturn(Arrays.asList(mapper.readValue("""
                [{
                  "nomCritere": "Climate change",
                  "unite": "kg CO2 eq",
                  "description": "Greenhouse gases (GHG)"
                },
                {
                  "nomCritere": "Particulate matter and respiratory inorganics"
                },
                {
                  "nomCritere": "Ionising radiation"
                },
                {
                  "nomCritere": "Acidification"
                }]
                """, CritereDTO[].class)));
    }

    @Test
    void enrichiDonneesEntrees_with_dureeUsageNull() {
        DonneesEntreesEntity donneeEntreeEntity = new DonneesEntreesEntity(Long.parseLong("10000"), Long.parseLong("10"), Long.parseLong("20"), Long.parseLong("4"), Long.parseLong("4"), Long.parseLong("4"), null, null, null, null);
        DonneesEntreesEntity actual = ReflectionTestUtils.invokeMethod(donneesEntreesService, "enrichi", donneeEntreeEntity, null, null, null, null, TYPE_CALCUL_SOUMISSION);
        assertEquals("FIXE", actual.getDureeUsage());
    }

    @Test
    void enrichiDonneesEntrees_with_dureeUsageFixe() {
        DonneesEntreesEntity donneeEntreeEntity = new DonneesEntreesEntity(Long.parseLong("10000"), Long.parseLong("10"), Long.parseLong("20"), Long.parseLong("4"), Long.parseLong("4"), Long.parseLong("4"), null, null, null, null);
        DonneesEntreesEntity actual = ReflectionTestUtils.invokeMethod(donneesEntreesService, "enrichi", donneeEntreeEntity, DureeUsage.valueOf("FIXE"), null, null, null, TYPE_CALCUL_SOUMISSION);
        assertEquals("FIXE", actual.getDureeUsage());
    }

    @Test
    void enrichiDonneesEntrees_with_dureeUsageReel() {
        DonneesEntreesEntity donneeEntreeEntity = new DonneesEntreesEntity(Long.parseLong("10000"), Long.parseLong("10"), Long.parseLong("20"), Long.parseLong("4"), Long.parseLong("4"), Long.parseLong("4"), null, null, null, null);
        DonneesEntreesEntity actual = ReflectionTestUtils.invokeMethod(donneesEntreesService, "enrichi", donneeEntreeEntity, DureeUsage.valueOf("REEL"), null, null, null, TYPE_CALCUL_SOUMISSION);
        assertEquals("REEL", actual.getDureeUsage());
    }

    @Test
    void enrichiDonneesEntrees_with_etapesFiltree_shouldFilter() {
        DonneesEntreesEntity donneeEntreeEntity = new DonneesEntreesEntity(Long.parseLong("10000"), Long.parseLong("10"), Long.parseLong("20"), Long.parseLong("4"), Long.parseLong("4"), Long.parseLong("4"), null, null, null, null);
        DonneesEntreesEntity actual = ReflectionTestUtils.invokeMethod(donneesEntreesService, "enrichi", donneeEntreeEntity, DureeUsage.valueOf("FIXE"), List.of("UTILISATION", "FABRICATION"), List.of(), null, TYPE_CALCUL_SOUMISSION);
        assertEquals("UTILISATION##FABRICATION", actual.getEtapes());
    }

    @Test
    void enrichiDonneesEntrees_with_etapesFiltreeNull_shouldReturnNull() {
        DonneesEntreesEntity donneeEntreeEntity = new DonneesEntreesEntity(Long.parseLong("10000"), Long.parseLong("10"), Long.parseLong("20"), Long.parseLong("4"), Long.parseLong("4"), Long.parseLong("4"), null, null, null, null);
        DonneesEntreesEntity actual = ReflectionTestUtils.invokeMethod(donneesEntreesService, "enrichi", donneeEntreeEntity, DureeUsage.valueOf("FIXE"), null, List.of(), null, TYPE_CALCUL_SOUMISSION);
        Assertions.assertNull(actual.getEtapes());
    }

    @Test
    void enrichiDonneesEntrees_with_etapeFiltreeNotInReferentiel_shouldThrowError() {
        DonneesEntreesEntity donneeEntreeEntity = new DonneesEntreesEntity(Long.parseLong("10000"), Long.parseLong("10"), Long.parseLong("20"), Long.parseLong("4"), Long.parseLong("4"), Long.parseLong("4"), null, null, null, null);
        var exception = assertThrows(ValidationException.class, () -> ReflectionTestUtils.invokeMethod(donneesEntreesService, "enrichi", donneeEntreeEntity, DureeUsage.valueOf("FIXE"), List.of("FAB"), List.of(), null, TYPE_CALCUL_SOUMISSION));
        assertEquals("La liste d'étapes n'est pas valide, elle doit être comprise dans: [UTILISATION, FABRICATION, DISTRIBUTION]", exception.getErreur());
    }

    @Test
    void enrichiDonneesEntrees_with_criteresFiltree_shouldFilter() {
        DonneesEntreesEntity donneeEntreeEntity = new DonneesEntreesEntity(Long.parseLong("10000"), Long.parseLong("10"), Long.parseLong("20"), Long.parseLong("4"), Long.parseLong("4"), Long.parseLong("4"), null, null, null, null);
        DonneesEntreesEntity actual = ReflectionTestUtils.invokeMethod(donneesEntreesService, "enrichi", donneeEntreeEntity, DureeUsage.valueOf("FIXE"), List.of(), List.of("Acidification", "Ionising radiation", "Climate change"), null, TYPE_CALCUL_SOUMISSION);
        assertEquals("Acidification##Ionising radiation##Climate change", actual.getCriteres());
    }

    @Test
    void enrichiDonneesEntrees_with_criteresFiltreeNull_shouldReturnNull() {
        DonneesEntreesEntity donneeEntreeEntity = new DonneesEntreesEntity(Long.parseLong("10000"), Long.parseLong("10"), Long.parseLong("20"), Long.parseLong("4"), Long.parseLong("4"), Long.parseLong("4"), null, null, null, null);
        DonneesEntreesEntity actual = ReflectionTestUtils.invokeMethod(donneesEntreesService, "enrichi", donneeEntreeEntity, DureeUsage.valueOf("FIXE"), null, null, null, TYPE_CALCUL_SOUMISSION);
        Assertions.assertNull(actual.getCriteres());
    }

    @Test
    void enrichiDonneesEntrees_with_critereFiltreeNotInReferentiel_shouldThrowError() {
        DonneesEntreesEntity donneeEntreeEntity = new DonneesEntreesEntity(Long.parseLong("10000"), Long.parseLong("10"), Long.parseLong("20"), Long.parseLong("4"), Long.parseLong("4"), Long.parseLong("4"), null, null, null, null);
        var exception = assertThrows(ValidationException.class, () -> ReflectionTestUtils.invokeMethod(donneesEntreesService, "enrichi", donneeEntreeEntity, DureeUsage.valueOf("FIXE"), null, List.of("CLIMATE CHANGE"), null, TYPE_CALCUL_SOUMISSION));
        Assertions.assertEquals("La liste de critères n'est pas valide, elle doit être comprise dans: [Climate change, Particulate matter and respiratory inorganics, Ionising radiation, Acidification]", exception.getErreur());
    }

    @Test
    void enrichiDonneesEntrees_with_nomOrganisation() {
        DonneesEntreesEntity donneeEntreeEntity = new DonneesEntreesEntity(Long.parseLong("10000"), Long.parseLong("10"), Long.parseLong("20"), Long.parseLong("4"), Long.parseLong("4"), Long.parseLong("4"), null, null, null, null);
        DonneesEntreesEntity actual = ReflectionTestUtils.invokeMethod(donneesEntreesService, "enrichi", donneeEntreeEntity, DureeUsage.valueOf("FIXE"), List.of(), List.of(), "SSG", TYPE_CALCUL_SOUMISSION);
        assertEquals("SSG", actual.getNomOrganisation());
    }
}
