package org.mte.numecoeval.calculs.domain.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockitoAnnotations;
import org.mte.numecoeval.calculs.domain.data.demande.DemandeCalculImpactReseau;
import org.mte.numecoeval.calculs.domain.data.entree.EquipementPhysique;
import org.mte.numecoeval.calculs.domain.data.indicateurs.ImpactReseau;
import org.mte.numecoeval.calculs.domain.data.referentiel.ReferentielCritere;
import org.mte.numecoeval.calculs.domain.data.referentiel.ReferentielEtapeACV;
import org.mte.numecoeval.calculs.domain.data.referentiel.ReferentielImpactReseau;
import org.mte.numecoeval.calculs.domain.port.input.service.CalculImpactReseauService;
import org.mte.numecoeval.calculs.domain.port.input.service.impl.CalculImpactReseauServiceImpl;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collections;

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

class CalculImpactReseauServiceTest {

    private final String REF_RESEAU = "impactReseauMobileMoyen";

    private CalculImpactReseauService calculImpactService;

    ObjectMapper objectMapper = new ObjectMapper();

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
        calculImpactService = new CalculImpactReseauServiceImpl(objectMapper);
    }

    @Test
    void shouldReturnCorrectCalculImpact_whenValidInputs() {
        // Given
        LocalDateTime dateCalcul = LocalDateTime.now();
        ReferentielEtapeACV etapeACV = ReferentielEtapeACV.builder()
                .code("UTILISATION")
                .build();
        ReferentielCritere critere = ReferentielCritere.builder()
                .nomCritere("Changement Climatique")
                .unite("kg CO_{2} eq")
                .build();
        EquipementPhysique equipementPhysique = EquipementPhysique.builder()
                .dateLot(LocalDate.of(2022,1,1))
                .nomOrganisation("Test")
                .nomLot("Test|20220101")
                .nomSourceDonnee("Source_Test")
                .nomEquipementPhysique("Equipement 1")
                .goTelecharge(3.5f)
                .build();
        ReferentielImpactReseau referentielImpactReseau = ReferentielImpactReseau.builder()
                .etapeACV(etapeACV.getCode())
                .critere(critere.getNomCritere())
                .refReseau(REF_RESEAU)
                .impactReseauMobileMoyen(4d)
                .build();
        var demandeCalcul = DemandeCalculImpactReseau.builder()
                .dateCalcul(dateCalcul)
                .equipementPhysique(equipementPhysique)
                .etape(etapeACV)
                .critere(critere)
                .impactsReseau(Collections.singletonList(referentielImpactReseau))
                .build();
        Double expected = 4d * 3.5f;

        //When
        var impactReseau = assertDoesNotThrow(() -> calculImpactService.calculerImpactReseau(demandeCalcul));

        //Then
        assertNotNull(impactReseau.getImpactUnitaire());
        assertEquals(expected, impactReseau.getImpactUnitaire());
        assertContentIndicateur(demandeCalcul, impactReseau);
        assertEquals("OK", impactReseau.getStatutIndicateur());
        assertEquals(
                "{\"critere\":\"Changement Climatique\",\"etapeACV\":\"UTILISATION\",\"equipementPhysique\":\"Equipement 1\",\"impactReseauMobileMoyen\":\"4.0\",\"goTelecharge\":\"3.5\",\"formule\":\"impactReseau = 'equipementPhysique.goTelecharge (3.5) x ref_ImpactReseau(Changement Climatique, UTILISATION, impactReseauMobileMoyen).valeur(4.0)'\"}",
                impactReseau.getTrace()
        );
    }

    @Test
    void shouldReturnError_whenInvalidInputs() {
        //Given
        ReferentielEtapeACV etapeACV = ReferentielEtapeACV.builder()
                .code("UTILISATION")
                .build();
        ReferentielCritere critere = ReferentielCritere.builder()
                .nomCritere("Changement Climatique")
                .unite("kg CO_{2} eq")
                .build();
        EquipementPhysique equipementPhysique = EquipementPhysique.builder()
                .dateLot(LocalDate.of(2022,1,1))
                .nomOrganisation("Test")
                .nomLot("Test|20220101")
                .nomSourceDonnee("Source_Test")
                .nomEquipementPhysique("Equipement 1")
                .goTelecharge(null)
                .build();
        var demandeCalcul = DemandeCalculImpactReseau.builder()
                .dateCalcul(LocalDateTime.now())
                .equipementPhysique(equipementPhysique)
                .etape(etapeACV)
                .critere(critere)
                .impactsReseau(Collections.emptyList())
                .build();

        //When
        var impactReseau = calculImpactService.calculerImpactReseau(demandeCalcul);

        //Then
        assertContentIndicateur(demandeCalcul, impactReseau);
        assertEquals("ERREUR", impactReseau.getStatutIndicateur());
        assertNull(impactReseau.getImpactUnitaire());
        assertEquals("{\"erreur\":\"ErrCalcFonc : Erreur de calcul impact réseau: Etape: UTILISATION, Critere: Changement Climatique, Equipement Physique: Equipement 1 : la valeur en_EqP(Equipement).goTelecharge est nulle\"}", impactReseau.getTrace());
    }

    @Test
    void shouldReturnError_whenInvalidReference() {
        //Given
        EquipementPhysique equipementPhysique = EquipementPhysique.builder()
                .dateLot(LocalDate.of(2022,1,1))
                .nomOrganisation("Test")
                .nomLot("Test|20220101")
                .nomSourceDonnee("Source_Test")
                .nomEquipementPhysique("Equipement 1")
                .goTelecharge(4.0f)
                .build();
        ReferentielEtapeACV etapeACV = ReferentielEtapeACV.builder()
                .code("UTILISATION")
                .build();
        ReferentielCritere critere = ReferentielCritere.builder()
                .nomCritere("Changement Climatique")
                .unite("kg CO_{2} eq")
                .build();
        ReferentielImpactReseau referentielImpactReseau = ReferentielImpactReseau.builder()
                .etapeACV(etapeACV.getCode())
                .critere(critere.getNomCritere())
                .refReseau(REF_RESEAU)
                .build();
        var demandeCalcul = DemandeCalculImpactReseau.builder()
                .dateCalcul(LocalDateTime.now())
                .equipementPhysique(equipementPhysique)
                .etape(etapeACV)
                .critere(critere)
                .impactsReseau(Collections.singletonList(referentielImpactReseau))
                .build();

        //When
        var impactReseau = calculImpactService.calculerImpactReseau(demandeCalcul);

        //Then
        assertContentIndicateur(demandeCalcul, impactReseau);
        assertEquals("ERREUR", impactReseau.getStatutIndicateur());
        assertNull(impactReseau.getImpactUnitaire());
        assertEquals("{\"erreur\":\"ErrCalcFonc : Erreur de calcul impact réseau: Etape: UTILISATION, Critere: Changement Climatique, RefReseau: impactReseauMobileMoyen, Equipement Physique: Equipement 1 : L'impactReseauMobileMoyen de la référence est null.\"}", impactReseau.getTrace());
    }

    @Test
    void shouldReturnError_whenReferenceNotFound() {
        //Given
        EquipementPhysique equipementPhysique = EquipementPhysique.builder()
                .dateLot(LocalDate.of(2022,1,1))
                .nomOrganisation("Test")
                .nomLot("Test|20220101")
                .nomSourceDonnee("Source_Test")
                .nomEquipementPhysique("Equipement 1")
                .goTelecharge(4.0f)
                .dateLot(LocalDate.of(2023,1,1))
                .build();
        ReferentielEtapeACV etapeACV = ReferentielEtapeACV.builder()
                .code("UTILISATION")
                .build();
        ReferentielCritere critere = ReferentielCritere.builder()
                .nomCritere("Changement Climatique")
                .unite("kg CO_{2} eq")
                .build();
        ReferentielImpactReseau referentielImpactReseau = ReferentielImpactReseau.builder()
                .etapeACV(etapeACV.getCode())
                .critere(critere.getNomCritere())
                .refReseau(REF_RESEAU)
                .build();
        var demandeCalcul = DemandeCalculImpactReseau.builder()
                .dateCalcul(LocalDateTime.now())
                .equipementPhysique(equipementPhysique)
                .etape(etapeACV)
                .critere(critere)
                .impactsReseau(Collections.singletonList(referentielImpactReseau))
                .build();

        //When
        var impactReseau = calculImpactService.calculerImpactReseau(demandeCalcul);

        //Then
        assertContentIndicateur(demandeCalcul, impactReseau);
        assertEquals("ERREUR", impactReseau.getStatutIndicateur());
        assertNull(impactReseau.getImpactUnitaire());
        assertEquals("{\"erreur\":\"ErrCalcFonc : Erreur de calcul impact réseau: Etape: UTILISATION, Critere: Changement Climatique, RefReseau: impactReseauMobileMoyen, Equipement Physique: Equipement 1 : L'impactReseauMobileMoyen de la référence est null.\"}", impactReseau.getTrace());
    }

    private static void assertContentIndicateur(DemandeCalculImpactReseau demandeCalcul, ImpactReseau impactReseau) {
        assertEquals(demandeCalcul.getDateCalcul(), impactReseau.getDateCalcul());

        assertEquals(demandeCalcul.getEtape().getCode(), impactReseau.getEtapeACV());
        assertEquals(demandeCalcul.getCritere().getNomCritere(), impactReseau.getCritere());
        assertEquals(demandeCalcul.getCritere().getNomCritere(), impactReseau.getCritere());

        assertEquals(demandeCalcul.getEquipementPhysique().getNomLot(), impactReseau.getNomLot());
        assertEquals(demandeCalcul.getEquipementPhysique().getDateLot(), impactReseau.getDateLot());
        assertEquals(demandeCalcul.getEquipementPhysique().getNomEntite(), impactReseau.getNomEntite());
        assertEquals(demandeCalcul.getEquipementPhysique().getNomOrganisation(), impactReseau.getNomOrganisation());
        assertEquals(demandeCalcul.getEquipementPhysique().getNomEquipementPhysique(), impactReseau.getNomEquipement());
        assertEquals(demandeCalcul.getEquipementPhysique().getNomSourceDonnee(), impactReseau.getNomSourceDonnee());

        assertEquals("1.0", impactReseau.getVersionCalcul());
    }

}