package org.mte.numecoeval.expositiondonneesentrees.infrastructure.service;

import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.mte.numecoeval.expositiondonneesentrees.domain.model.DataCenter;
import org.mte.numecoeval.expositiondonneesentrees.domain.model.EquipementPhysique;
import org.mte.numecoeval.expositiondonneesentrees.domain.model.OperationNonIT;
import org.mte.numecoeval.expositiondonneesentrees.domain.ports.output.ReferentielServicePort;
import org.mte.numecoeval.expositiondonneesentrees.infrastructure.config.MessageProperties;
import org.mte.numecoeval.expositiondonneesentrees.referentiels.generated.api.model.CorrespondanceRefEquipementDTO;
import org.mte.numecoeval.expositiondonneesentrees.utils.Constants;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;


@Service
@AllArgsConstructor
public class ErrorManagementService {

    final MessageProperties messageProperties;

    final ReferentielServicePort referentielServicePort;

    @Value("#{'${constraints.mode-utilisation}'.split(',')}")
    private List<String> modeUtilisationList;

    /**
     * Vérifie si la qualité de la donnée d'un item d'un inventaire fait partie de cette enum (BASSE, MOYENNE, HAUTE)
     * si ce n'est pas le cas, on lance un avertissement
     * la valeur qualite est déjà en majuscule
     *
     * @param qualite la qualité  en majuscule
     * @param type    le type d'item
     * @param nom     le nom de l'équipement
     * @return l'avertissement associé ou null
     */
    public String checkQualiteDonnees(String qualite, String type, String nom) {
        if (qualite == null) return null;
        if (Constants.QUALITES_DONNEES.contains(qualite)) return null;
        return messageProperties.getMessages().get("QUALITE_INVALIDE").formatted(type, nom, qualite);
    }

    /**
     * Vérifie si la syntaxe du nom fourni comme typeItem (dans l'inventaire d'un équipement physique ou d'une opération non IT) est correcte
     * C'est à dire que le type ne contient pas les caractères '/' ou '\'.
     * si ce n'est pas le cas, on lance une erreur
     *
     * @param type    la qualité  en majuscule
     * @param nomItem le nom d'item : équipement ou opération non IT
     * @param nom     le nom de l'équipement
     * @return l'avertissement associé ou null
     */
    public String checkNomTypeItem(String type, String nomItem, String nom) {
        if (type == null) return null;
        if (!type.contains("/") && !type.contains("\\")) {
            return null;
        }
        return messageProperties.getMessages().get("TYPE_ITEM_INVALIDE").formatted(nomItem, nom, type);
    }

    /**
     * Vérifie si la localisation du datacenter existe dans la table ref_MixElec.pays
     *
     * @param dataCenter le datacenter
     * @return la liste d'erreur
     */
    public List<String> checkDataCenter(DataCenter dataCenter) {
        var result = new ArrayList<String>();

        // CA 1.3
        if (!referentielServicePort.hasMixElec(dataCenter.getLocalisation())) {
            result.add(messageProperties.getMessages().get("DATACENTER_MIX_ELEC_LOCALISATION_INCONNUE").formatted(dataCenter.getLocalisation(), dataCenter.getNomLongDatacenter()));
        }

        return result;
    }

    /**
     * Vérifie un equipement physique
     *
     * @param equipementPhysique     l'equipement physique
     * @param refEquipementParDefaut la reference d'equipement par default
     * @return la paire (erreurs, avertissements)
     */
    public Pair<List<String>, List<String>> checkEquipementPhysique(EquipementPhysique equipementPhysique, String refEquipementParDefaut) {
        var erreurs = new ArrayList<String>();

        // CA 1.2
        // L'ajout d'un équipement physique dont le pays d'utilisation n'existe pas dans la table ref_MixElec.pays sort l'erreur suivante dans le rapport de contrôle
        if (StringUtils.isNotBlank(equipementPhysique.getPaysDUtilisation()) &&
                !referentielServicePort.hasMixElec(equipementPhysique.getPaysDUtilisation())) {

            erreurs.add(messageProperties.getMessages().get("ITEM_MIX_ELEC_LOCALISATION_INCONNUE").formatted(equipementPhysique.getPaysDUtilisation(), equipementPhysique.getNomEquipementPhysique()));
        }

        String refEquipement = refEquipementParDefaut;

        // CA 1.5
        // L'ajout d'un équipement physique dont le type d'équipement ne possède pas de refEquipementParDefaut et dont le Modele n'est pas présent dans la table des ref_CorrespondanceRefEqP sort l'erreur suivante dans le rapport de contrôle
        if (StringUtils.isBlank(refEquipementParDefaut)) {
            if (equipementPhysique.getModele() == null) {
                erreurs.add(messageProperties.getMessages().get("EQUIPEMENT_CORRESPONDANCE_INCONNUE").formatted(equipementPhysique.getNomEquipementPhysique(), equipementPhysique.getType()));
            } else {
                CorrespondanceRefEquipementDTO refCorrespondance = referentielServicePort.getCorrespondance(equipementPhysique.getModele());
                if (refCorrespondance == null) {
                    erreurs.add(messageProperties.getMessages().get("EQUIPEMENT_CORRESPONDANCE_INCONNUE").formatted(equipementPhysique.getNomEquipementPhysique(), equipementPhysique.getType()));
                } else {
                    refEquipement = refCorrespondance.getRefEquipementCible();
                }
            }
        }

        // CA 2.1
        // L'ajout d'un équipement physique dont la référence d'impact (déterminée à partir de la table de correspondance ou à partir de la table type équipement) est nulle sort un warning
        var avertissements = new ArrayList<String>();

        var etapes = referentielServicePort.getAllEtapes();
        var criteres = referentielServicePort.getAllCriteres();
        for (var critere : criteres) {
            for (var etape : etapes) {
                var impact = referentielServicePort.getFacteurCaracterisation(critere.getNomCritere(), etape.getCode(), refEquipement);
                if (impact == null) {
                    avertissements.add(messageProperties.getMessages().get("ITEM_IMPACT_INCONNU").formatted(refEquipement, etape.getCode(), critere.getNomCritere()));
                }
            }
        }

        // CA 3.1
        // L'ajout d'un équipement dont la date de retrait (equipementPhysique.DateRetrait) précède la date d'achat (equipementPhysique.DateAchat)
        if (equipementPhysique.getDateAchat() != null && equipementPhysique.getDateRetrait() != null &&
                equipementPhysique.getDateAchat().isAfter(equipementPhysique.getDateRetrait())) {

            erreurs.add(messageProperties.getMessages().get("EQUIPEMENT_DATE_INCOHERENTE").formatted(equipementPhysique.getNomEquipementPhysique()));
        }
        // CA 4.1
        // L'ajout d'un équipement dont la duree d'usage interne est négative
        if (equipementPhysique.getDureeUsageInterne() != null && equipementPhysique.getDureeUsageInterne() <= 0) {
            erreurs.add(messageProperties.getMessages().get("EQUIPEMENT_DUREE_USAGE_INTERNE_INCOHERENTE").formatted(equipementPhysique.getNomEquipementPhysique()));
        }
        if (equipementPhysique.getDureeUsageAmont() != null && equipementPhysique.getDureeUsageAmont() < 0) {
            erreurs.add(messageProperties.getMessages().get("EQUIPEMENT_DUREE_USAGE_INCOHERENTE").formatted("amont", equipementPhysique.getNomEquipementPhysique()));
        }
        if (equipementPhysique.getDureeUsageAval() != null && equipementPhysique.getDureeUsageAval() < 0) {
            erreurs.add(messageProperties.getMessages().get("EQUIPEMENT_DUREE_USAGE_INCOHERENTE").formatted("aval", equipementPhysique.getNomEquipementPhysique()));
        }
        //CA 5.1
        // L'ajout d'un équipement dont le mode d'utilisation est autre que COPE, BYOD ou null
        if (equipementPhysique.getModeUtilisation() != null && !modeUtilisationList.contains(equipementPhysique.getModeUtilisation())) {
            avertissements.add(messageProperties.getMessages().get("EQUIPEMENT_MODE_UTILISATION_INCONNU").formatted(equipementPhysique.getModeUtilisation()));
        }
        //CA 6.1
        // L'ajout d'un équipement dont le taux d'utilisation n'est pas compris entre 0 et 1
        Double taux = equipementPhysique.getTauxUtilisation();
        if (taux != null && (taux < 0 || taux > 1)) {
            avertissements.add(messageProperties.getMessages().get("EQUIPEMENT_TAUX_UTILISATION_INVALIDE").formatted(taux));
        }
        return Pair.of(erreurs, avertissements);
    }

    /**
     * Vérifie une operation non IT
     *
     * @param operationNonIT l'equipement physique
     * @param type           la reference d'equipement par default
     * @return la paire (erreurs, avertissements)
     */
    public Pair<List<String>, List<String>> checkOperationNonIT(OperationNonIT operationNonIT, String type) {
        var erreurs = new ArrayList<String>();
        var avertissements = new HashSet<String>();
        // L'ajout d'une operation non it dont le type n'est pas renseigné sort une erreur
        if (StringUtils.isBlank(type)) {
            erreurs.add(messageProperties.getMessages().get("ITEM_CORRESPONDANCE_INCONNUE").formatted(operationNonIT.getNomItemNonIT(), operationNonIT.getType()));
        } else {
            // L'ajout d'une opération non it dont la référence d'impact (déterminée à partir de la table ref_type_item) est nulle sort un warning
            var etapes = referentielServicePort.getAllEtapes();
            var criteres = referentielServicePort.getAllCriteres();
            for (var critere : criteres) {
                for (var etape : etapes) {
                    var impact = referentielServicePort.getFacteurCaracterisation(critere.getNomCritere(), etape.getCode(), type);
                    if (impact == null) {
                        avertissements.add(messageProperties.getMessages().get("ITEM_IMPACT_INCONNU").formatted(type, etape.getCode(), critere.getNomCritere()));
                    }
                }
            }
        }
        return Pair.of(erreurs, avertissements.stream().toList());
    }
}
