package org.mte.numecoeval.calculs.domain.port.input.service.impl;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.mte.numecoeval.calculs.domain.data.demande.DemandeCalculImpactApplication;
import org.mte.numecoeval.calculs.domain.data.erreur.TypeErreurCalcul;
import org.mte.numecoeval.calculs.domain.data.indicateurs.ImpactApplication;
import org.mte.numecoeval.calculs.domain.exception.CalculImpactException;
import org.mte.numecoeval.calculs.domain.port.input.service.CalculImpactApplicationService;
import org.mte.numecoeval.calculs.domain.traceur.TraceCalculImpactApplicationUtils;
import org.mte.numecoeval.calculs.domain.traceur.TraceCalculImpactVirtuelUtils;
import org.mte.numecoeval.calculs.domain.traceur.TraceUtils;

@Slf4j
@AllArgsConstructor
public class CalculImpactApplicationServiceImpl implements CalculImpactApplicationService {

    private static final String VERSION_CALCUL = "1.1";

    private ObjectMapper objectMapper;

    @Override
    public ImpactApplication calculImpactApplicatif(DemandeCalculImpactApplication demandeCalcul) {
        ImpactApplication impactErreur;
        try {
            return calculerImpact(demandeCalcul);
        } catch (CalculImpactException e) {
            log.debug("Erreur de calcul d'impact d'équipement virtuel : Type : {}, Cause: {}, Etape: {}, Critere: {}, Nom Equipment Physique: {}, Nom Equipement Virtuel: {}, Nom Application: {}, Type Environnement: {}",
                    e.getErrorType(), e.getMessage(),
                    demandeCalcul.getImpactEquipementVirtuel().getEtapeACV(),
                    demandeCalcul.getImpactEquipementVirtuel().getCritere(),
                    demandeCalcul.getApplication().getNomEquipementPhysique(),
                    demandeCalcul.getApplication().getNomEquipementVirtuel(),
                    demandeCalcul.getApplication().getNomApplication(),
                    demandeCalcul.getApplication().getTypeEnvironnement()
            );

            impactErreur = buildImpactForError(demandeCalcul, e);
        } catch (Exception e) {
            log.debug("Erreur de calcul d'impact d'équipement virtuel : Type : {}, Cause: {}, Etape: {}, Critere: {}, Nom Equipment Physique: {}, Nom Equipement Virtuel: {}, Nom Application: {}, Type Environnement: {}",
                    TypeErreurCalcul.ERREUR_TECHNIQUE.getCode(), e.getMessage(),
                    demandeCalcul.getImpactEquipementVirtuel().getEtapeACV(),
                    demandeCalcul.getImpactEquipementVirtuel().getCritere(),
                    demandeCalcul.getApplication().getNomEquipementPhysique(),
                    demandeCalcul.getApplication().getNomEquipementVirtuel(),
                    demandeCalcul.getApplication().getNomApplication(),
                    demandeCalcul.getApplication().getTypeEnvironnement()
            );
            impactErreur = buildImpactForError(demandeCalcul, new CalculImpactException(TypeErreurCalcul.ERREUR_TECHNIQUE.getCode(), e.getMessage()));
        }

        return impactErreur;
    }

    private ImpactApplication buildImpactForError(DemandeCalculImpactApplication demandeCalcul, CalculImpactException exception) {
        return ImpactApplication.builder()
                .dateCalcul(demandeCalcul.getDateCalcul())
                .versionCalcul(VERSION_CALCUL)
                .etapeACV(demandeCalcul.getImpactEquipementVirtuel().getEtapeACV())
                .critere(demandeCalcul.getImpactEquipementVirtuel().getCritere())
                .statutIndicateur("ERREUR")
                .trace(TraceUtils.getTraceFromTraceur(objectMapper, TraceCalculImpactVirtuelUtils.buildTraceErreur(exception)))
                .nomLot(demandeCalcul.getApplication().getNomLot())
                .nomSourceDonnee(demandeCalcul.getApplication().getNomSourceDonnee())
                .dateLot(demandeCalcul.getApplication().getDateLot())
                .unite(demandeCalcul.getImpactEquipementVirtuel().getUnite())
                .nomOrganisation(demandeCalcul.getApplication().getNomOrganisation())
                .nomEntite(demandeCalcul.getApplication().getNomEntite())
                .nomEquipementPhysique(demandeCalcul.getApplication().getNomEquipementPhysique())
                .nomEquipementVirtuel(demandeCalcul.getApplication().getNomEquipementVirtuel())
                .nomApplication(demandeCalcul.getApplication().getNomApplication())
                .typeEnvironnement(demandeCalcul.getApplication().getTypeEnvironnement())
                .domaine(demandeCalcul.getApplication().getDomaine())
                .sousDomaine(demandeCalcul.getApplication().getSousDomaine())
                .impactUnitaire(null)
                .consoElecMoyenne(null)
                .build();
    }

    private ImpactApplication calculerImpact(DemandeCalculImpactApplication demandeCalcul) throws CalculImpactException {
        if (!"OK".equals(demandeCalcul.getImpactEquipementVirtuel().getStatutIndicateur())) {
            throw new CalculImpactException(
                    TypeErreurCalcul.ERREUR_FONCTIONNELLE.getCode(),
                    "L'indicateur d'impact équipement virtuel associé est au statut : " + demandeCalcul.getImpactEquipementVirtuel().getStatutIndicateur()
            );
        }
        if (demandeCalcul.getImpactEquipementVirtuel().getImpactUnitaire() == null) {
            throw new CalculImpactException(
                    TypeErreurCalcul.ERREUR_FONCTIONNELLE.getCode(),
                    "L'indicateur d'impact équipement virtuel associé est null."
            );
        }

        var nbApplications = demandeCalcul.getNbApplicationsForCalcul();

        var resultCalcul = new CalculImpactApplicationResult(
                demandeCalcul.getImpactEquipementVirtuel().getImpactUnitaire() / nbApplications,
                demandeCalcul.getImpactEquipementVirtuel().getConsoElecMoyenne() != null ? demandeCalcul.getImpactEquipementVirtuel().getConsoElecMoyenne() / nbApplications : null
        );

        return ImpactApplication.builder()
                .dateCalcul(demandeCalcul.getDateCalcul())
                .nomApplication(demandeCalcul.getApplication().getNomApplication())
                .typeEnvironnement(demandeCalcul.getApplication().getTypeEnvironnement())
                .domaine(demandeCalcul.getApplication().getDomaine())
                .sousDomaine(demandeCalcul.getApplication().getSousDomaine())
                .etapeACV(demandeCalcul.getImpactEquipementVirtuel().getEtapeACV())
                .critere(demandeCalcul.getImpactEquipementVirtuel().getCritere())
                .unite(demandeCalcul.getImpactEquipementVirtuel().getUnite())
                .nomEquipementVirtuel(demandeCalcul.getApplication().getNomEquipementVirtuel())
                .nomEquipementPhysique(demandeCalcul.getApplication().getNomEquipementPhysique())
                .nomOrganisation(demandeCalcul.getApplication().getNomOrganisation())
                .nomEntite(demandeCalcul.getApplication().getNomEntite())
                .nomLot(demandeCalcul.getApplication().getNomLot())
                .nomSourceDonnee(demandeCalcul.getApplication().getNomSourceDonnee())
                .dateLot(demandeCalcul.getApplication().getDateLot())
                .versionCalcul(VERSION_CALCUL)
                .statutIndicateur("OK")
                .trace(TraceUtils.getTraceFromTraceur(objectMapper, TraceCalculImpactApplicationUtils.buildTrace(demandeCalcul)))
                .impactUnitaire(resultCalcul.valeurImpact)
                .consoElecMoyenne(resultCalcul.consoElecMoyenne)
                .build();
    }

    private record CalculImpactApplicationResult(Double valeurImpact, Double consoElecMoyenne) {
    }

}
