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.DemandeCalculImpactEquipementVirtuel;
import org.mte.numecoeval.calculs.domain.data.erreur.TypeErreurCalcul;
import org.mte.numecoeval.calculs.domain.data.indicateurs.ImpactEquipementVirtuel;
import org.mte.numecoeval.calculs.domain.data.trace.TraceCalculImpactEquipementVirtuel;
import org.mte.numecoeval.calculs.domain.exception.CalculImpactException;
import org.mte.numecoeval.calculs.domain.port.input.service.CalculImpactEquipementVirtuelService;
import org.mte.numecoeval.calculs.domain.traceur.TraceCalculImpactVirtuelUtils;
import org.mte.numecoeval.calculs.domain.traceur.TraceUtils;

@Slf4j
@AllArgsConstructor
public class CalculImpactEquipementVirtuelServiceImpl implements CalculImpactEquipementVirtuelService {

    private static final String VERSION_CALCUL = "1.1";
    public static final String TYPE_EQUIPEMENT_VIRTUEL_STOCKAGE = "stockage";
    public static final String TYPE_EQUIPEMENT_VIRTUEL_CALCUL = "calcul";
    private final ObjectMapper objectMapper;

    @Override
    public ImpactEquipementVirtuel calculerImpactEquipementVirtuel(DemandeCalculImpactEquipementVirtuel demandeCalcul) {
        ImpactEquipementVirtuel 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: {}",
                    e.getErrorType(), e.getMessage(),
                    demandeCalcul.getImpactEquipement().getEtapeACV(),
                    demandeCalcul.getImpactEquipement().getCritere(),
                    demandeCalcul.getEquipementVirtuel().getNomEquipementPhysique(),
                    demandeCalcul.getEquipementVirtuel().getNomEquipementVirtuel());

            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: {}",
                    TypeErreurCalcul.ERREUR_TECHNIQUE.getCode(), e.getMessage(),
                    demandeCalcul.getImpactEquipement().getEtapeACV(),
                    demandeCalcul.getImpactEquipement().getCritere(),
                    demandeCalcul.getEquipementVirtuel().getNomEquipementPhysique(),
                    demandeCalcul.getEquipementVirtuel().getNomEquipementVirtuel());
            impactErreur = buildImpactForError(demandeCalcul, new CalculImpactException(TypeErreurCalcul.ERREUR_TECHNIQUE.getCode(), e.getMessage()));
        }

        return impactErreur;
    }

    private ImpactEquipementVirtuel calculerImpact(DemandeCalculImpactEquipementVirtuel demandeCalcul) throws CalculImpactException {
        if (!"OK".equals(demandeCalcul.getImpactEquipement().getStatutIndicateur())) {
            throw new CalculImpactException(
                    TypeErreurCalcul.ERREUR_FONCTIONNELLE.getCode(),
                    "L'indicateur d'impact équipement associé est au statut : " + demandeCalcul.getImpactEquipement().getStatutIndicateur()
            );
        }
        if (demandeCalcul.getImpactEquipement().getImpactUnitaire() == null) {
            throw new CalculImpactException(
                    TypeErreurCalcul.ERREUR_FONCTIONNELLE.getCode(),
                    "L'impact unitaire de l'équipement physique parent est null"
            );
        }

        Double valeurImpactEquipementPhysique = demandeCalcul.getImpactEquipement().getImpactUnitaire();
        Double consoElecMoyenneEquipementPhysique = demandeCalcul.getImpactEquipement().getConsoElecMoyenne();

        TraceCalculImpactEquipementVirtuel trace = TraceCalculImpactVirtuelUtils.buildTrace(demandeCalcul);

        CalculImpactUnitaireEquipementVirtuel result = calculImpactUnitaire(demandeCalcul, valeurImpactEquipementPhysique, consoElecMoyenneEquipementPhysique);

        return ImpactEquipementVirtuel.builder()
                .dateCalcul(demandeCalcul.getDateCalcul())
                .versionCalcul(VERSION_CALCUL)
                .etapeACV(demandeCalcul.getImpactEquipement().getEtapeACV())
                .critere(demandeCalcul.getImpactEquipement().getCritere())
                .unite(demandeCalcul.getImpactEquipement().getUnite())
                .statutIndicateur("OK")
                .trace(TraceUtils.getTraceFromTraceur(objectMapper, trace))
                .nomLot(demandeCalcul.getEquipementVirtuel().getNomLot())
                .nomSourceDonnee(demandeCalcul.getEquipementVirtuel().getNomSourceDonnee())
                .dateLot(demandeCalcul.getEquipementVirtuel().getDateLot())
                .nomOrganisation(demandeCalcul.getEquipementVirtuel().getNomOrganisation())
                .nomEntite(demandeCalcul.getEquipementVirtuel().getNomEntite())
                .nomEquipement(demandeCalcul.getEquipementVirtuel().getNomEquipementPhysique())
                .nomEquipementVirtuel(demandeCalcul.getEquipementVirtuel().getNomEquipementVirtuel())
                .cluster(demandeCalcul.getEquipementVirtuel().getCluster())
                .impactUnitaire(result.valeurImpactUnitaire())
                .consoElecMoyenne(result.consoElecMoyenne())
                .idEntree(demandeCalcul.getEquipementVirtuel().getId())
                .build();
    }

    private ImpactEquipementVirtuel buildImpactForError(DemandeCalculImpactEquipementVirtuel demandeCalcul, CalculImpactException exception) {
        return ImpactEquipementVirtuel.builder()
                .dateCalcul(demandeCalcul.getDateCalcul())
                .versionCalcul(VERSION_CALCUL)
                .etapeACV(demandeCalcul.getImpactEquipement().getEtapeACV())
                .critere(demandeCalcul.getImpactEquipement().getCritere())
                .unite(demandeCalcul.getImpactEquipement().getUnite())
                .statutIndicateur("ERREUR")
                .trace(TraceUtils.getTraceFromTraceur(objectMapper, TraceCalculImpactVirtuelUtils.buildTraceErreur(exception)))
                .nomLot(demandeCalcul.getEquipementVirtuel().getNomLot())
                .nomSourceDonnee(demandeCalcul.getEquipementVirtuel().getNomSourceDonnee())
                .dateLot(demandeCalcul.getEquipementVirtuel().getDateLot())
                .nomOrganisation(demandeCalcul.getEquipementVirtuel().getNomOrganisation())
                .nomEntite(demandeCalcul.getEquipementVirtuel().getNomEntite())
                .nomEquipement(demandeCalcul.getEquipementVirtuel().getNomEquipementPhysique())
                .nomEquipementVirtuel(demandeCalcul.getEquipementVirtuel().getNomEquipementVirtuel())
                .cluster(demandeCalcul.getEquipementVirtuel().getCluster())
                .impactUnitaire(null)
                .consoElecMoyenne(null)
                .idEntree(demandeCalcul.getEquipementVirtuel().getId())
                .build();
    }

    private static CalculImpactUnitaireEquipementVirtuel calculImpactUnitaire(DemandeCalculImpactEquipementVirtuel demandeCalcul, Double valeurImpactEquipementPhysique, Double consoElecMoyenneEquipementPhysique) throws CalculImpactException {
        double valeurImpactUnitaire;
        Double consoElecMoyenne;
        if (cleRepartitionEstDisponible(demandeCalcul)) {
            valeurImpactUnitaire = valeurImpactEquipementPhysique * demandeCalcul.getEquipementVirtuel().getCleRepartition();
            consoElecMoyenne = consoElecMoyenneEquipementPhysique != null ? consoElecMoyenneEquipementPhysique * demandeCalcul.getEquipementVirtuel().getCleRepartition() : null;
        } else if (calculPourCalculEstDisponible(demandeCalcul)) {
            valeurImpactUnitaire = valeurImpactEquipementPhysique * demandeCalcul.getEquipementVirtuel().getVCPU() / demandeCalcul.getNbTotalVCPU();
            consoElecMoyenne = consoElecMoyenneEquipementPhysique != null ? consoElecMoyenneEquipementPhysique * demandeCalcul.getEquipementVirtuel().getVCPU() / demandeCalcul.getNbTotalVCPU() : null;
        } else if (calculPourStockageEstDisponible(demandeCalcul)) {
            valeurImpactUnitaire = valeurImpactEquipementPhysique * demandeCalcul.getEquipementVirtuel().getCapaciteStockage() / demandeCalcul.getStockageTotalVirtuel();
            consoElecMoyenne = consoElecMoyenneEquipementPhysique != null ? consoElecMoyenneEquipementPhysique * demandeCalcul.getEquipementVirtuel().getCapaciteStockage() / demandeCalcul.getStockageTotalVirtuel() : null;
        } else if (calculParRepartiParNombreEquipementEstDisponible(demandeCalcul)) {
            valeurImpactUnitaire = valeurImpactEquipementPhysique / demandeCalcul.getNbEquipementsVirtuels();
            consoElecMoyenne = consoElecMoyenneEquipementPhysique != null ? consoElecMoyenneEquipementPhysique / demandeCalcul.getNbEquipementsVirtuels() : null;
        } else {
            throw new CalculImpactException(TypeErreurCalcul.ERREUR_FONCTIONNELLE.getCode(), "Certaines données sur l'équipement virtuel sont manquantes ou incorrectes");
        }
        return new CalculImpactUnitaireEquipementVirtuel(valeurImpactUnitaire, consoElecMoyenne);
    }

    public static boolean calculParRepartiParNombreEquipementEstDisponible(DemandeCalculImpactEquipementVirtuel demandeCalcul) {
        return demandeCalcul.getNbEquipementsVirtuels() != null && demandeCalcul.getNbEquipementsVirtuels() >= 1;
    }

    public static boolean calculPourStockageEstDisponible(DemandeCalculImpactEquipementVirtuel demandeCalcul) {
        return TYPE_EQUIPEMENT_VIRTUEL_STOCKAGE.equals(demandeCalcul.getEquipementVirtuel().getTypeEqv()) && demandeCalcul.getStockageTotalVirtuel() != null;
    }

    public static boolean calculPourCalculEstDisponible(DemandeCalculImpactEquipementVirtuel demandeCalcul) {
        return TYPE_EQUIPEMENT_VIRTUEL_CALCUL.equals(demandeCalcul.getEquipementVirtuel().getTypeEqv()) && demandeCalcul.getNbTotalVCPU() != null;
    }

    public static boolean cleRepartitionEstDisponible(DemandeCalculImpactEquipementVirtuel demandeCalcul) {
        return demandeCalcul.getEquipementVirtuel().getCleRepartition() != null;
    }

    /**
     * Record pour le stockage du résultat du calcul d'impact
     *
     * @param valeurImpactUnitaire impact unitaire pour l'équipement virtuel
     * @param consoElecMoyenne     conso électrique moyenne pour l'équipement virtuel
     */
    private record CalculImpactUnitaireEquipementVirtuel(double valeurImpactUnitaire, Double consoElecMoyenne) {
    }

}
