package org.mte.numecoeval.calculs.infrastructure.service.calcul;

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.mockito.junit.jupiter.MockitoExtension;
import org.mte.numecoeval.calculs.infrastructure.client.ReferentielClient;
import org.mte.numecoeval.calculs.infrastructure.repository.DonneesEntreeRepository;
import org.mte.numecoeval.calculs.infrastructure.service.enrichissement.EnrichissementOperationNonITService;
import org.mte.numecoeval.calculs.referentiels.generated.api.model.*;
import org.mte.numecoeval.topic.data.OperationNonITDTO;
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 static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.ArgumentMatchers.any;

@ExtendWith({MockitoExtension.class, SpringExtension.class})
@ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class)
class EnrichissementOperationNonITServiceTest {

    private static final ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());

    @InjectMocks
    EnrichissementOperationNonITService enrichissementOperationNonITService;

    @Mock
    ReferentielClient referentielClient;
    @Mock
    DonneesEntreeRepository donneesEntreeRepository;

    @Value("${numecoeval.hypotheses.operationNonIt}")
    private String hypothesesOperationNonIt;

    OperationNonITDTO operationNonITDTO = mapper.readValue("""
            {
              "id": 43701,
              "nomItemNonIT": "reseau-fixe-rennes",
              "quantite": 7.0,
              "type": "reseau-fixe-france",
              "dureeDeVie": 27.0,
              "localisation": "France",
              "nomEntite": "Entite test",
              "nomSourceDonnee":"RCP-SI",
              "nomCourtDatacenter": "dc1",
              "description": "voici une description",
              "consoElecAnnuelle": null,
              "nomLot": "lot1",
              "dateLot": "2024-04-24",
              "nomOrganisation": "org",
              "qualite":"BASSE"
            }
            """, OperationNonITDTO.class);

    EnrichissementOperationNonITServiceTest() throws JsonProcessingException {
    }

    @BeforeEach
    void initMocksReferentiel() throws JsonProcessingException {
        ReflectionTestUtils.setField(enrichissementOperationNonITService, "hypothesesOperationNonIt", hypothesesOperationNonIt);

        /* MOCK REFERENTIEL : Etapes */
        Mockito.lenient().when(referentielClient.getEtapes()).thenReturn(Arrays.asList(mapper.readValue("""
                [{ "code": "UTILISATION", "libelle": "Using" }]
                """, EtapeDTO[].class)));

        /* MOCK REFERENTIEL : Criteres */
        Mockito.lenient().when(referentielClient.getCriteres()).thenReturn(Arrays.asList(mapper.readValue("""
                [{
                  "nomCritere": "Climate change",
                  "unite": "kg CO2 eq",
                  "description": "Greenhouse gases (GHG)"
                }]
                """, CritereDTO[].class)));

        /* MOCK REFERENTIEL : Hypothese */
        Mockito.lenient().when(referentielClient.getHypothese("CAPACITE_LIGNE_FIXE_FR")).thenReturn(mapper.readValue("""
                {
                  "code": "CAPACITE_LIGNE_FIXE_FR",
                  "valeur": 2640,
                  "source": "Rapport ADEME"
                }
                """, HypotheseDTO.class));

        /* MOCK REFERENTIEL : TypeItem */
        Mockito.lenient().when(referentielClient.getTypeItem("reseau-fixe-france")).thenReturn(mapper.readValue("""
                {
                  "type": "reseau-fixe-france",
                  "categorie" : "RESEAU_FIXE",
                  "refHypothese" : "CAPACITE_LIGNE_FIXE_FR",
                  "refItemParDefaut" : "reseau-fixe-1"
                }
                """, TypeItemDTO.class));

        /* MOCK REFERENTIEL : FacteurCaracterisation */
        Mockito.lenient().when(referentielClient.getFacteurCaracterisationByCritereAndEtapeAndNom("Climate change", "UTILISATION", "reseau-fixe-1")).thenReturn(mapper.readValue("""
                {
                  "nom": "reseau-fixe-1",
                  "etape" : "UTILISATION",
                  "critere" : "Climate change",
                  "localisation": "France",
                  "consoElecMoyenne": 12.7,
                  "valeur" : "8.34"
                }
                """, FacteurCaracterisationDTO.class));

        /* MOCK REFERENTIEL : MixElectrique */
        Mockito.lenient().when(referentielClient.getMixElectriqueFromFacteurCaracterisation("Climate change", "France")).thenReturn(mapper.readValue("""
                {
                  "nom": "Electricity Mix/ Production mix/ Low voltage/ CL",
                  "etape" : "FABRICATION",
                  "critere" : "Climate change",
                  "categorie" : "electricity-mix",
                  "localisation": "France",
                  "valeur" : "0.0813225"
                }
                """, FacteurCaracterisationDTO.class));
    }

    void initMocksEtapesAndCriteres() throws JsonProcessingException {
        /* MOCK REFERENTIEL : Etapes */
        Mockito.lenient().when(referentielClient.getEtapes()).thenReturn(Arrays.asList(mapper.readValue("""
                [{ "code": "FABRICATION", "libelle": "Manufacturing" },
                 { "code": "DISTRIBUTION", "libelle": "Transportation" },
                 { "code": "UTILISATION", "libelle": "Using" },
                 { "code": "FIN_DE_VIE", "libelle": "End of Life" }]
                """, EtapeDTO[].class)));

        Mockito.lenient().when(referentielClient.getCriteres()).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 testServiceEnrichissementOperationNonIT_null() {
        assertNull(enrichissementOperationNonITService.serviceEnrichissementOperationNonIT(null));
    }

    @Test
    void testServiceEnrichissementOperationNonIT_via_typeItem() throws JsonProcessingException {

        var actual = enrichissementOperationNonITService.serviceEnrichissementOperationNonIT(operationNonITDTO);

        Assertions.assertEquals(1, actual.getEtapes().size());
        Assertions.assertEquals(1, actual.getCriteres().size());
        Assertions.assertEquals(1, actual.getHypotheses().size());

        Assertions.assertEquals(2, actual.getFacteurCaracterisations().size());

        Assertions.assertEquals("reseau-fixe-1", actual.getFacteurCaracterisations().get(0).getNom());
        Assertions.assertEquals(12.7, actual.getFacteurCaracterisations().get(0).getConsoElecMoyenne());
        Assertions.assertEquals(8.34, actual.getFacteurCaracterisations().get(0).getValeur());

        Assertions.assertEquals("France", actual.getFacteurCaracterisations().get(1).getLocalisation());
        Assertions.assertEquals(0.0813225, actual.getFacteurCaracterisations().get(1).getValeur());
        Assertions.assertEquals("BASSE", actual.getOperationNonIT().getQualite());

    }

    @Test
    void testServiceEnrichissementEquipementPhysique_filterEtapes() throws JsonProcessingException {
        initMocksEtapesAndCriteres();
        Mockito.lenient().when(donneesEntreeRepository.findEtapes(any())).thenReturn("UTILISATION##FABRICATION");

        var actual = enrichissementOperationNonITService.serviceEnrichissementOperationNonIT(operationNonITDTO);

        Assertions.assertEquals(2, actual.getEtapes().size());
    }

    @Test
    void testServiceEnrichissementEquipementPhysique_doesNotFilterEtapesWhenDataNull() throws JsonProcessingException {
        initMocksEtapesAndCriteres();
        Mockito.lenient().when(donneesEntreeRepository.findEtapes(any())).thenReturn(null);

        var actual = enrichissementOperationNonITService.serviceEnrichissementOperationNonIT(operationNonITDTO);

        Assertions.assertEquals(4, actual.getEtapes().size());
    }

    @Test
    void testServiceEnrichissementEquipementPhysique_filterCriteres() throws JsonProcessingException {
        initMocksEtapesAndCriteres();
        Mockito.lenient().when(donneesEntreeRepository.findCriteres(any())).thenReturn("Climate change##Ionising radiation##Acidification");

        var actual = enrichissementOperationNonITService.serviceEnrichissementOperationNonIT(operationNonITDTO);

        Assertions.assertEquals(3, actual.getCriteres().size());
    }

    @Test
    void testServiceEnrichissementEquipementPhysique_doesNotFilterCriteresWhenDataNull() throws JsonProcessingException {
        initMocksEtapesAndCriteres();
        Mockito.lenient().when(donneesEntreeRepository.findCriteres(any())).thenReturn(null);

        var actual = enrichissementOperationNonITService.serviceEnrichissementOperationNonIT(operationNonITDTO);

        Assertions.assertEquals(4, actual.getCriteres().size());
    }
}
