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.EnrichissementEquipementPhysiqueService;
import org.mte.numecoeval.calculs.referentiels.generated.api.model.*;
import org.mte.numecoeval.topic.data.EquipementPhysiqueDTO;
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.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;

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

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

    @InjectMocks
    EnrichissementEquipementPhysiqueService enrichissementEquipementPhysiqueService;

    @Mock
    ReferentielClient referentielClient;

    @Mock
    DonneesEntreeRepository donneesEntreeRepository;

    @Value("${numecoeval.hypotheses.equipementPhysique}")
    private String hypothesesEquipementPhysique;

    EquipementPhysiqueDTO equipementPhysiqueDTO = mapper.readValue("""
            {
              "id": 43702,
              "nomEquipementPhysique": "physical-eq-srv-1001",
              "modele": "blade-server--28",
              "type": "Server",
              "statut": "In use",
              "paysDUtilisation": null,
              "dateAchat": "2016-06-17",
              "dateRetrait": "2023-06-16",
              "nomCourtDatacenter": "default",
              "quantite": 7.0,
              "consoElecAnnuelle": null,
              "serveur": true,
              "nomLot": "lot1",
              "dateLot": "2023-10-26",
              "nomOrganisation": "org",
              "nbEquipementsVirtuels": 1,
              "nbTotalVCPU": 4,
              "dataCenter": {
                "id": 5026,
                "nomCourtDatacenter": "default",
                "nomLongDatacenter": "Default",
                "pue": 1.75,
                "localisation": "France",
                "dateLot": "2023-10-26",
                "nomOrganisation": "org"
              },
              "qualite":"HAUTE"
            }
            """, EquipementPhysiqueDTO.class);

    EnrichissementEquipementPhysiqueServiceTest() throws JsonProcessingException {
    }

    @BeforeEach
    void initMocksReferentiel() throws JsonProcessingException {
        ReflectionTestUtils.setField(enrichissementEquipementPhysiqueService, "hypothesesEquipementPhysique", hypothesesEquipementPhysique);

        /* 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)));

        /* MOCK REFERENTIEL : Criteres */
        Mockito.lenient().when(referentielClient.getCriteres()).thenReturn(Arrays.asList(mapper.readValue("""
                [
                  { "nomCritere": "Climate change" },
                  { "nomCritere": "Particulate matter and respiratory inorganics" },
                  { "nomCritere": "Ionising radiation" },
                  { "nomCritere": "Acidification" }
                ]
                """, CritereDTO[].class)));

        /* MOCK REFERENTIEL : Hypothese */
        Mockito.lenient().when(referentielClient.getHypothese(any(), any())).thenReturn(mapper.readValue("""
                {
                  "code": "hyp code",
                  "valeur": "val",
                  "source": "test"
                }
                """, HypotheseDTO.class));


        var mixFr = new FacteurCaracterisationDTO();
        mixFr.setLocalisation("FR");
        Mockito.lenient().when(referentielClient.getMixElectriqueFromFacteurCaracterisation(eq("Climate change"), eq("France"), any())).thenReturn(mixFr);
        Mockito.lenient().when(referentielClient.getMixElectriqueFromFacteurCaracterisation(eq("Climate change"), eq(null), any())).thenReturn(null);
    }

    @Test
    void testServiceEnrichissementEquipementPhysique_null() {
        Assertions.assertNull(enrichissementEquipementPhysiqueService.serviceEnrichissementEquipementPhysique(null));
    }

    @Test
    void testServiceEnrichissementEquipementPhysique_correspondanceRefEquipement() throws JsonProcessingException {

        /* MOCK REFERENTIEL : CorrespondanceRefEquipement */
        Mockito.when(referentielClient.getCorrespondanceRefEquipement(eq("blade-server--28"), any())).thenReturn(mapper.readValue("""
                {
                  "modeleEquipementSource": "srv",
                  "refEquipementCible": "cible"
                }
                """, CorrespondanceRefEquipementDTO.class));

        /* MOCK REFERENTIEL : FacteurCaracterisation */
        var fcDTO = new FacteurCaracterisationDTO();
        fcDTO.setValeur(1.1);

        Mockito.when(referentielClient.getFacteurCaracterisationByCritereAndEtapeAndNomAndNomOrganisation(any(), any(), eq("cible"), any())).thenReturn(fcDTO);

        var actual = enrichissementEquipementPhysiqueService.serviceEnrichissementEquipementPhysique(equipementPhysiqueDTO);

        Assertions.assertEquals(4, actual.getEtapes().size());
        Assertions.assertEquals(4, actual.getCriteres().size());
        Assertions.assertEquals(4, actual.getHypotheses().size());
        Assertions.assertEquals(17, actual.getFacteurCaracterisations().size());
        Assertions.assertEquals(1.1, actual.getFacteurCaracterisations().getFirst().getValeur());
        Assertions.assertEquals("HAUTE", actual.getEquipementPhysique().getQualite());
    }

    @Test
    void testServiceEnrichissementEquipementPhysique_typeEquipement() throws JsonProcessingException {

        /* MOCK REFERENTIEL : CorrespondanceRefEquipement */
        Mockito.when(referentielClient.getTypeItem(eq("Server"), any())).thenReturn(mapper.readValue("""
                {
                  "refItemParDefaut": "default-ref-server"
                }
                """, TypeItemDTO.class));

        /* MOCK REFERENTIEL : FacteurCaracterisation */
        var fcDTO = new FacteurCaracterisationDTO();
        fcDTO.setValeur(2.2);

        Mockito.when(referentielClient.getFacteurCaracterisationByCritereAndEtapeAndNomAndNomOrganisation(any(), any(), eq("default-ref-server"), any())).thenReturn(fcDTO);

        var actual = enrichissementEquipementPhysiqueService.serviceEnrichissementEquipementPhysique(equipementPhysiqueDTO);

        Assertions.assertEquals(4, actual.getEtapes().size());
        Assertions.assertEquals(4, actual.getCriteres().size());
        Assertions.assertEquals(4, actual.getHypotheses().size());
        Assertions.assertEquals(17, actual.getFacteurCaracterisations().size());
        Assertions.assertEquals(2.2, actual.getFacteurCaracterisations().getFirst().getValeur());
        Assertions.assertEquals("HAUTE", actual.getEquipementPhysique().getQualite());
    }

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

        var actual = enrichissementEquipementPhysiqueService.serviceEnrichissementEquipementPhysique(equipementPhysiqueDTO);

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

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

        var actual = enrichissementEquipementPhysiqueService.serviceEnrichissementEquipementPhysique(equipementPhysiqueDTO);

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

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

        var actual = enrichissementEquipementPhysiqueService.serviceEnrichissementEquipementPhysique(equipementPhysiqueDTO);

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

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

        var actual = enrichissementEquipementPhysiqueService.serviceEnrichissementEquipementPhysique(equipementPhysiqueDTO);

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