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

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mte.numecoeval.calculs.domain.data.demande.DemandeCalculImpactApplication;
import org.mte.numecoeval.calculs.domain.data.entree.Application;
import org.mte.numecoeval.calculs.domain.data.indicateurs.ImpactApplication;
import org.mte.numecoeval.calculs.domain.data.indicateurs.ImpactEquipementVirtuel;
import org.mte.numecoeval.calculs.domain.port.input.service.impl.CalculImpactApplicationServiceImpl;

import java.time.LocalDate;
import java.time.LocalDateTime;

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

@ExtendWith(MockitoExtension.class)
class CalculImpactApplicationServiceImplTest {

    CalculImpactApplicationServiceImpl calculImpactApplicatifService;

    @BeforeEach
    void setUp() {
        calculImpactApplicatifService = new CalculImpactApplicationServiceImpl();
    }

    @Test
    void whenImpactEquipementVirtuelIsInError_ShouldReturnImpactError() {
        String applicationName = "MS-Word";
        String environnement = "Production";
        LocalDate dateLot = LocalDate.of(2023, 4, 1);
        Application application = Application.builder()
                .dateLot(dateLot)
                .nomOrganisation("Test")
                .nomLot("Test|20220101")
                .nomSourceDonnee("Source_Test")
                .nomApplication(applicationName)
                .typeEnvironnement(environnement)
                .domaine("Bureautique")
                .sousDomaine("Licence")
                .nomEquipementPhysique("nomEquipementPhysique")
                .nomEquipementVirtuel("nomEquipementVirtuel")
                .build();
        ImpactEquipementVirtuel impactEquipementVirtuel = ImpactEquipementVirtuel.builder()
                .dateLot(dateLot)
                .etapeACV("Utilisation")
                .critere("Critere")
                .unite("kgCO2eq")
                .dateCalcul(LocalDateTime.now().minusSeconds(10))
                .statutIndicateur("ERREUR")
                .impactUnitaire(61.744d)
                .nomEquipement(application.getNomEquipementPhysique())
                .nomEquipementVirtuel(application.getNomEquipementVirtuel())
                .trace("{\"erreur\":\"Erreur lors du calcul d'impact équipement virtuel\"}")
                .build();
        DemandeCalculImpactApplication demanceCalcul = DemandeCalculImpactApplication.builder()
                .application(application)
                .dateCalcul(LocalDateTime.now())
                .nbApplications(1)
                .impactEquipementVirtuel(impactEquipementVirtuel)
                .build();

        // When
        var impactApplication = calculImpactApplicatifService.calculImpactApplicatif(demanceCalcul);

        assertContentIndicateur(demanceCalcul, impactApplication);


        assertNull(impactApplication.getImpactUnitaire());
        assertNull(impactApplication.getConsoElecMoyenne());

        assertEquals("ERREUR", impactApplication.getStatutIndicateur());
        assertEquals("{\"erreur\":\"ErrCalcFonc : L'indicateur d'impact équipement virtuel associé est au statut : ERREUR\"}", impactApplication.getTrace());
    }

    @Test
    void whenImpactEquipementVirtuelIsNull_ShouldReturnImpactError() {
        String applicationName = "MS-Word";
        String environnement = "Production";
        LocalDate dateLot = LocalDate.of(2023, 4, 1);
        Application application = Application.builder()
                .dateLot(dateLot)
                .nomOrganisation("Test")
                .nomLot("Test|20220101")
                .nomSourceDonnee("Source_Test")
                .nomApplication(applicationName)
                .typeEnvironnement(environnement)
                .domaine("Bureautique")
                .sousDomaine("Licence")
                .nomEquipementPhysique("nomEquipementPhysique")
                .nomEquipementVirtuel("nomEquipementVirtuel")
                .build();
        ImpactEquipementVirtuel impactEquipementVirtuel = ImpactEquipementVirtuel.builder()
                .dateLot(dateLot)
                .etapeACV("Utilisation")
                .critere("Critere")
                .unite("kgCO2eq")
                .dateCalcul(LocalDateTime.now().minusSeconds(10))
                .statutIndicateur("OK")
                .impactUnitaire(null)
                .nomEquipement(application.getNomEquipementPhysique())
                .nomEquipementVirtuel(application.getNomEquipementVirtuel())
                .trace("{\"erreur\":\"Erreur lors du calcul d'impact équipement virtuel\"}")
                .build();
        DemandeCalculImpactApplication demanceCalcul = DemandeCalculImpactApplication.builder()
                .application(application)
                .dateCalcul(LocalDateTime.now())
                .nbApplications(1)
                .impactEquipementVirtuel(impactEquipementVirtuel)
                .build();

        // When
        var impactApplication = calculImpactApplicatifService.calculImpactApplicatif(demanceCalcul);

        assertContentIndicateur(demanceCalcul, impactApplication);


        assertNull(impactApplication.getImpactUnitaire());
        assertNull(impactApplication.getConsoElecMoyenne());

        assertEquals("ERREUR", impactApplication.getStatutIndicateur());
        assertEquals("{\"erreur\":\"ErrCalcFonc : L'indicateur d'impact équipement virtuel associé est null.\"}", impactApplication.getTrace());
    }

    @ParameterizedTest
    @ValueSource(ints = 0)
    void whenNbApplicationIs0_ShouldApplyRatio1AndReturnImpactOK(Integer nbApplications) {
        String applicationName = "MS-Word";
        String environnement = "Production";
        LocalDate dateLot = LocalDate.of(2023, 4, 1);
        Application application = Application.builder()
                .dateLot(dateLot)
                .nomOrganisation("Test")
                .nomLot("Test|20220101")
                .nomSourceDonnee("Source_Test")
                .nomApplication(applicationName)
                .typeEnvironnement(environnement)
                .domaine("Bureautique")
                .sousDomaine("Licence")
                .nomEquipementPhysique("nomEquipementPhysique")
                .nomEquipementVirtuel("nomEquipementVirtuel")
                .build();
        ImpactEquipementVirtuel impactEquipementVirtuel = ImpactEquipementVirtuel.builder()
                .dateLot(dateLot)
                .etapeACV("Utilisation")
                .critere("Critere")
                .unite("kgCO2eq")
                .dateCalcul(LocalDateTime.now().minusSeconds(10))
                .statutIndicateur("OK")
                .impactUnitaire(61.744d)
                .nomEquipement(application.getNomEquipementPhysique())
                .nomEquipementVirtuel(application.getNomEquipementVirtuel())
                .build();
        DemandeCalculImpactApplication demanceCalcul = DemandeCalculImpactApplication.builder()
                .application(application)
                .dateCalcul(LocalDateTime.now())
                .nbApplications(nbApplications)
                .impactEquipementVirtuel(impactEquipementVirtuel)
                .build();

        // When
        var impactApplication = calculImpactApplicatifService.calculImpactApplicatif(demanceCalcul);

        assertContentIndicateur(demanceCalcul, impactApplication);

        assertEquals(demanceCalcul.getImpactEquipementVirtuel().getImpactUnitaire(), impactApplication.getImpactUnitaire());
        assertEquals(demanceCalcul.getImpactEquipementVirtuel().getConsoElecMoyenne(), impactApplication.getConsoElecMoyenne());

        assertEquals("OK", impactApplication.getStatutIndicateur());
        assertEquals("{\"formule\":\"ImpactApplication = ImpactEquipementVirtuel(61.744) / nbApplications(1)\",\"nbApplications\":0}", impactApplication.getTrace());
    }

    @ParameterizedTest
    @NullSource
    void whenNbApplicationIsNull_ShouldApplyRatio1AndReturnImpactOK(Integer nbApplications) {
        String applicationName = "MS-Word";
        String environnement = "Production";
        LocalDate dateLot = LocalDate.of(2023, 4, 1);
        Application application = Application.builder()
                .dateLot(dateLot)
                .nomOrganisation("Test")
                .nomLot("Test|20220101")
                .nomSourceDonnee("Source_Test")
                .nomApplication(applicationName)
                .typeEnvironnement(environnement)
                .domaine("Bureautique")
                .sousDomaine("Licence")
                .nomEquipementPhysique("nomEquipementPhysique")
                .nomEquipementVirtuel("nomEquipementVirtuel")
                .build();
        ImpactEquipementVirtuel impactEquipementVirtuel = ImpactEquipementVirtuel.builder()
                .dateLot(dateLot)
                .etapeACV("Utilisation")
                .critere("Critere")
                .unite("kgCO2eq")
                .dateCalcul(LocalDateTime.now().minusSeconds(10))
                .statutIndicateur("OK")
                .impactUnitaire(61.744d)
                .nomEquipement(application.getNomEquipementPhysique())
                .nomEquipementVirtuel(application.getNomEquipementVirtuel())
                .build();
        DemandeCalculImpactApplication demanceCalcul = DemandeCalculImpactApplication.builder()
                .application(application)
                .dateCalcul(LocalDateTime.now())
                .nbApplications(nbApplications)
                .impactEquipementVirtuel(impactEquipementVirtuel)
                .build();

        // When
        var impactApplication = calculImpactApplicatifService.calculImpactApplicatif(demanceCalcul);

        assertContentIndicateur(demanceCalcul, impactApplication);

        assertEquals(demanceCalcul.getImpactEquipementVirtuel().getImpactUnitaire(), impactApplication.getImpactUnitaire());
        assertEquals(demanceCalcul.getImpactEquipementVirtuel().getConsoElecMoyenne(), impactApplication.getConsoElecMoyenne());

        assertEquals("OK", impactApplication.getStatutIndicateur());
        assertEquals("{\"formule\":\"ImpactApplication = ImpactEquipementVirtuel(61.744) / nbApplications(1)\"}", impactApplication.getTrace());
    }

    @Test
    void whenImpactEquipementVirtuelIsOK_ShouldReturnImpactOK() {
        String applicationName = "MS-Word";
        String environnement = "Production";
        LocalDate dateLot = LocalDate.of(2023, 4, 1);
        Application application = Application.builder()
                .dateLot(dateLot)
                .nomOrganisation("Test")
                .nomLot("Test|20220101")
                .nomSourceDonnee("Source_Test")
                .nomApplication(applicationName)
                .typeEnvironnement(environnement)
                .domaine("Bureautique")
                .sousDomaine("Licence")
                .nomEquipementPhysique("nomEquipementPhysique")
                .nomEquipementVirtuel("nomEquipementVirtuel")
                .build();
        ImpactEquipementVirtuel impactEquipementVirtuel = ImpactEquipementVirtuel.builder()
                .dateLot(dateLot)
                .etapeACV("Utilisation")
                .critere("Critere")
                .unite("kgCO2eq")
                .dateCalcul(LocalDateTime.now().minusSeconds(10))
                .statutIndicateur("OK")
                .impactUnitaire(61.744d)
                .nomEquipement(application.getNomEquipementPhysique())
                .nomEquipementVirtuel(application.getNomEquipementVirtuel())
                .build();
        DemandeCalculImpactApplication demanceCalcul = DemandeCalculImpactApplication.builder()
                .application(application)
                .dateCalcul(LocalDateTime.now())
                .nbApplications(1)
                .impactEquipementVirtuel(impactEquipementVirtuel)
                .build();

        // When
        var impactApplication = calculImpactApplicatifService.calculImpactApplicatif(demanceCalcul);

        assertContentIndicateur(demanceCalcul, impactApplication);

        assertEquals(demanceCalcul.getImpactEquipementVirtuel().getImpactUnitaire(), impactApplication.getImpactUnitaire());
        assertEquals(demanceCalcul.getImpactEquipementVirtuel().getConsoElecMoyenne(), impactApplication.getConsoElecMoyenne());

        assertEquals("OK", impactApplication.getStatutIndicateur());
        assertEquals("{\"formule\":\"ImpactApplication = ImpactEquipementVirtuel(61.744) / nbApplications(1)\",\"nbApplications\":1}", impactApplication.getTrace());
    }

    @Test
    void taiga1025_whenNbApplicationsIsMoreThan1_ThenImpactShouldBeDividedAndImpactShouldBeOK() {
        String applicationName = "MS-Word";
        String environnement = "Production";
        LocalDate dateLot = LocalDate.of(2023, 4, 1);
        Application application = Application.builder()
                .dateLot(dateLot)
                .nomOrganisation("Test")
                .nomLot("Test|20220101")
                .nomSourceDonnee("Source_Test")
                .nomApplication(applicationName)
                .typeEnvironnement(environnement)
                .domaine("Bureautique")
                .sousDomaine("Licence")
                .nomEquipementPhysique("nomEquipementPhysique")
                .nomEquipementVirtuel("nomEquipementVirtuel")
                .build();
        ImpactEquipementVirtuel impactEquipementVirtuel = ImpactEquipementVirtuel.builder()
                .dateLot(dateLot)
                .etapeACV("Utilisation")
                .critere("Critere")
                .unite("kgCO2eq")
                .dateCalcul(LocalDateTime.now().minusSeconds(10))
                .statutIndicateur("OK")
                .impactUnitaire(61.744d)
                .consoElecMoyenne(61.744d)
                .nomEquipement(application.getNomEquipementPhysique())
                .nomEquipementVirtuel(application.getNomEquipementVirtuel())
                .trace("{\"erreur\":\"Erreur lors du calcul d'impact équipement virtuel\"}")
                .build();
        DemandeCalculImpactApplication demanceCalcul = DemandeCalculImpactApplication.builder()
                .application(application)
                .dateCalcul(LocalDateTime.now())
                .nbApplications(10)
                .impactEquipementVirtuel(impactEquipementVirtuel)
                .build();

        // When
        var impactApplication = calculImpactApplicatifService.calculImpactApplicatif(demanceCalcul);

        assertContentIndicateur(demanceCalcul, impactApplication);

        assertEquals(demanceCalcul.getImpactEquipementVirtuel().getImpactUnitaire() / demanceCalcul.getNbApplications(), impactApplication.getImpactUnitaire());
        assertEquals(demanceCalcul.getImpactEquipementVirtuel().getConsoElecMoyenne() / demanceCalcul.getNbApplications(), impactApplication.getConsoElecMoyenne());

        assertEquals("OK", impactApplication.getStatutIndicateur());
        assertEquals("{\"formule\":\"ImpactApplication = ImpactEquipementVirtuel(61.744) / nbApplications(10)\",\"nbApplications\":10}", impactApplication.getTrace());
    }

    private static void assertContentIndicateur(DemandeCalculImpactApplication demanceCalcul, ImpactApplication impactApplication) {
        assertNotNull(impactApplication);
        assertEquals(demanceCalcul.getApplication().getNomApplication(), impactApplication.getNomApplication());
        assertEquals(demanceCalcul.getApplication().getTypeEnvironnement(), impactApplication.getTypeEnvironnement());
        assertEquals(demanceCalcul.getApplication().getDomaine(), impactApplication.getDomaine());
        assertEquals(demanceCalcul.getApplication().getSousDomaine(), impactApplication.getSousDomaine());

        assertEquals(demanceCalcul.getApplication().getNomLot(), impactApplication.getNomLot());
        assertEquals(demanceCalcul.getApplication().getDateLot(), impactApplication.getDateLot());
        assertEquals(demanceCalcul.getApplication().getNomEquipementPhysique(), impactApplication.getNomEquipementPhysique());
        assertEquals(demanceCalcul.getApplication().getNomEquipementVirtuel(), impactApplication.getNomEquipementVirtuel());
        assertEquals(demanceCalcul.getApplication().getNomOrganisation(), impactApplication.getNomOrganisation());
        assertEquals(demanceCalcul.getApplication().getNomEntite(), impactApplication.getNomEntite());
        assertEquals(demanceCalcul.getApplication().getNomSourceDonnee(), impactApplication.getNomSourceDonnee());

        assertEquals(demanceCalcul.getDateCalcul(), impactApplication.getDateCalcul());

        assertEquals(demanceCalcul.getImpactEquipementVirtuel().getCritere(), impactApplication.getCritere());
        assertEquals(demanceCalcul.getImpactEquipementVirtuel().getEtapeACV(), impactApplication.getEtapeACV());
        assertEquals(demanceCalcul.getImpactEquipementVirtuel().getUnite(), impactApplication.getUnite());

        assertEquals("1.1", impactApplication.getVersionCalcul());
    }

}