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.DemandeCalculImpactReseau;
import org.mte.numecoeval.calculs.domain.data.erreur.TypeErreurCalcul;
import org.mte.numecoeval.calculs.domain.data.indicateurs.ImpactReseau;
import org.mte.numecoeval.calculs.domain.data.referentiel.ReferentielImpactReseau;
import org.mte.numecoeval.calculs.domain.exception.CalculImpactException;
import org.mte.numecoeval.calculs.domain.port.input.service.CalculImpactReseauService;
import org.mte.numecoeval.calculs.domain.traceur.TraceCalculImpactReseauUtils;
import org.mte.numecoeval.calculs.domain.traceur.TraceUtils;

import java.util.Objects;

@Slf4j
@AllArgsConstructor
public class CalculImpactReseauServiceImpl implements CalculImpactReseauService {

    private static final String VERSION_CALCUL = "1.0";

    private final ObjectMapper objectMapper;

    @Override
    public ImpactReseau calculerImpactReseau(DemandeCalculImpactReseau demandeCalcul) {
        try {
            if (demandeCalcul.getEquipementPhysique().getGoTelecharge() == null) {
                throw new CalculImpactException(
                        TypeErreurCalcul.ERREUR_FONCTIONNELLE.getCode(),
                        "Erreur de calcul impact réseau: Etape: %s, Critere: %s, Equipement Physique: %s : la valeur en_EqP(Equipement).goTelecharge est nulle"
                                .formatted(
                                        demandeCalcul.getEtape().getCode(),
                                        demandeCalcul.getCritere().getNomCritere(),
                                        demandeCalcul.getEquipementPhysique().getNomEquipementPhysique()
                                )
                );
            }

            var referentielImpactReseau = getReferentielImpactReseau(demandeCalcul, REF_RESEAU);

            return calculerImpact(demandeCalcul, referentielImpactReseau);
        } catch (CalculImpactException exception) {
            log.debug("{} : {}", exception.getErrorType(), exception.getMessage());
            return buildCalculForError(demandeCalcul, exception);
        } catch (Exception exception) {
            log.debug("{} : {}", TypeErreurCalcul.ERREUR_TECHNIQUE, exception.getMessage());
            return buildCalculForError(demandeCalcul, new CalculImpactException(TypeErreurCalcul.ERREUR_TECHNIQUE.getCode(), exception.getMessage()));
        }
    }

    private ReferentielImpactReseau getReferentielImpactReseau(DemandeCalculImpactReseau demandeCalcul, String refReseau) throws CalculImpactException {
        var referentielImpactReseau = demandeCalcul.getImpactsReseau().stream()
                .filter(Objects::nonNull)
                .filter(refImpactReseau ->
                        demandeCalcul.getEtape().getCode().equals(refImpactReseau.getEtapeACV())
                                && demandeCalcul.getCritere().getNomCritere().equals(refImpactReseau.getCritere())
                                && refReseau.equals(refImpactReseau.getRefReseau())
                )
                .findFirst()
                .orElseThrow(() -> new CalculImpactException(
                                TypeErreurCalcul.ERREUR_FONCTIONNELLE.getCode(),
                                "Erreur de calcul impact réseau: Etape: %s, Critere: %s, RefReseau: %s, Equipement Physique: %s : La référence ImpactReseau n'existe pas."
                                        .formatted(
                                                demandeCalcul.getEtape().getCode(),
                                                demandeCalcul.getCritere().getNomCritere(),
                                                REF_RESEAU,
                                                demandeCalcul.getEquipementPhysique().getNomEquipementPhysique()
                                        )
                        )
                );

        if (Objects.isNull(referentielImpactReseau.getImpactReseauMobileMoyen())) {
            throw new CalculImpactException(
                    TypeErreurCalcul.ERREUR_FONCTIONNELLE.getCode(),
                    "Erreur de calcul impact réseau: Etape: %s, Critere: %s, RefReseau: %s, Equipement Physique: %s : L'impactReseauMobileMoyen de la référence est null."
                            .formatted(
                                    demandeCalcul.getEtape().getCode(),
                                    demandeCalcul.getCritere().getNomCritere(),
                                    REF_RESEAU,
                                    demandeCalcul.getEquipementPhysique().getNomEquipementPhysique()
                            )
            );
        }

        return referentielImpactReseau;
    }

    private ImpactReseau calculerImpact(DemandeCalculImpactReseau demandeCalcul, ReferentielImpactReseau referentielImpactReseau) {
        return ImpactReseau.builder()
                .versionCalcul(VERSION_CALCUL)
                .dateCalcul(demandeCalcul.getDateCalcul())
                .nomLot(demandeCalcul.getEquipementPhysique().getNomLot())
                .nomSourceDonnee(demandeCalcul.getEquipementPhysique().getNomSourceDonnee())
                .dateLot(demandeCalcul.getEquipementPhysique().getDateLot())
                .etapeACV(demandeCalcul.getEtape().getCode())
                .critere(demandeCalcul.getCritere().getNomCritere())
                .statutIndicateur("OK")
                .source(referentielImpactReseau.getSource())
                .nomEntite(demandeCalcul.getEquipementPhysique().getNomEntite())
                .nomOrganisation(demandeCalcul.getEquipementPhysique().getNomOrganisation())
                .nomEquipement(demandeCalcul.getEquipementPhysique().getNomEquipementPhysique())
                .impactUnitaire(
                        demandeCalcul.getEquipementPhysique().getGoTelecharge() * referentielImpactReseau.getImpactReseauMobileMoyen()
                )
                .unite(referentielImpactReseau.getUnite())
                .trace(
                        TraceUtils.getTraceFromTraceur(
                                objectMapper,
                                TraceCalculImpactReseauUtils.buildTrace(demandeCalcul, referentielImpactReseau))
                )
                .build();
    }

    private ImpactReseau buildCalculForError(DemandeCalculImpactReseau demandeCalcul, CalculImpactException exception) {
        return ImpactReseau.builder()
                .versionCalcul(VERSION_CALCUL)
                .dateCalcul(demandeCalcul.getDateCalcul())
                .nomLot(demandeCalcul.getEquipementPhysique().getNomLot())
                .nomSourceDonnee(demandeCalcul.getEquipementPhysique().getNomSourceDonnee())
                .dateLot(demandeCalcul.getEquipementPhysique().getDateLot())
                .etapeACV(demandeCalcul.getEtape().getCode())
                .critere(demandeCalcul.getCritere().getNomCritere())
                .statutIndicateur("ERREUR")
                .nomEntite(demandeCalcul.getEquipementPhysique().getNomEntite())
                .nomOrganisation(demandeCalcul.getEquipementPhysique().getNomOrganisation())
                .nomEquipement(demandeCalcul.getEquipementPhysique().getNomEquipementPhysique())
                .unite(demandeCalcul.getCritere().getUnite())
                .impactUnitaire(null)
                .unite(demandeCalcul.getCritere().getUnite())
                .trace(TraceUtils.getTraceFromTraceur(
                        objectMapper,
                        TraceCalculImpactReseauUtils.buildTraceErreur(exception))
                )
                .build();
    }

}