package org.mte.numecoeval.expositiondonneesentrees.domain.ports.input.impl;

import lombok.AllArgsConstructor;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.mte.numecoeval.expositiondonneesentrees.domain.model.*;
import org.mte.numecoeval.expositiondonneesentrees.domain.ports.input.ImportDonneesEntreePort;
import org.mte.numecoeval.expositiondonneesentrees.domain.ports.output.ReferentielServicePort;
import org.mte.numecoeval.expositiondonneesentrees.generated.api.model.StatutTraitement;
import org.mte.numecoeval.expositiondonneesentrees.infrastructure.helper.CSVHelper;
import org.mte.numecoeval.expositiondonneesentrees.infrastructure.service.DefaultValueService;
import org.mte.numecoeval.expositiondonneesentrees.infrastructure.service.ErrorManagementService;
import org.mte.numecoeval.expositiondonneesentrees.referentiels.generated.api.model.TypeItemDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.time.LocalDate;
import java.util.*;

@AllArgsConstructor
public class ImportDonneesEntreePortImpl implements ImportDonneesEntreePort {

    private static final String LABEL_NOM_EQUIPEMENT_PHYSIQUE = "nomEquipementPhysique";
    private static final String LABEL_NOM_VM = "nomVM";
    private static final String LABEL_NOM_EQUIPEMENT_VIRTUEL = "nomEquipementVirtuel";
    private static final String LABEL_nom_item_non_it = "nomItemNonIT";

    private static final String[] EQUIPEMENT_PHYSIQUE_HEADER = EQUIPEMENT_PHYSIQUE_CSV_HEADER.split(CSV_SEPARATOR);
    private static final String[] DATA_CENTER_HEADER = DATA_CENTER_CSV_HEADER.split(CSV_SEPARATOR);
    private static final String[] EQUIPEMENT_VIRTUEL_HEADER = EQUIPEMENT_VIRTUEL_CSV_HEADER.split(CSV_SEPARATOR);
    private static final String[] APPLICATION_HEADER = APPLICATION_CSV_HEADER.split(CSV_SEPARATOR);
    private static final String[] MESSAGERIE_HEADER = MESSAGERIE_CSV_HEADER.split(CSV_SEPARATOR);
    private static final String[] ENTITE_HEADER = ENTITE_CSV_HEADER.split(CSV_SEPARATOR);
    private static final String[] OPERATION_NON_IT_HEADER = OPERATION_NON_IT_CSV_HEADER.split(CSV_SEPARATOR);
    private static final String HEADER_NOM_ENTITE = "nomEntite";
    private static final String HEADER_NOM_SOURCE_DONNEE = "nomSourceDonnee";
    private static final String LIGNE_INCONSISTENTE = "LIGNE_INCONSISTENTE";
    private static final Logger LOGGER = LoggerFactory.getLogger(ImportDonneesEntreePortImpl.class);
    private static final String STATUT_TRAITEMENT_EN_ATTENTE = StatutTraitement.EN_ATTENTE.getValue();

    final ReferentielServicePort referentielServicePort;

    final ErrorManagementService errorManagementService;

    final DefaultValueService defaultValueService;

    final Map<String, String> errorMessages;

    @Override
    public ResultatImport importCsv(String nomOrganisation, String nomLot, String dateLot,
                                    MultipartFile csvDataCenter,
                                    MultipartFile csvEquipementPhysique,
                                    MultipartFile csvEquipementVirtuel,
                                    MultipartFile csvApplication,
                                    MultipartFile csvOperationNonIT,
                                    MultipartFile csvMessagerie,
                                    MultipartFile csvEntite
    ) {

        LocalDate dateLotAsDate = dateLot == null || "".equals(dateLot) ? null : LocalDate.parse(dateLot);
        ResultatImport resultatImport = new ResultatImport();
        resultatImport.getDonneesEntree().setStatutTraitement(STATUT_TRAITEMENT_EN_ATTENTE);
        resultatImport.getDonneesEntree().setDateLot(dateLotAsDate);
        resultatImport.getDonneesEntree().setNomOrganisation(nomOrganisation);
        resultatImport.getDonneesEntree().setNomLot(nomLot);

        if (csvDataCenter != null) {
            Pair<RapportImport, List<DataCenter>> importDataCenter = importDataCenter(nomLot, dateLotAsDate, nomOrganisation, csvDataCenter);
            resultatImport.getRapports().add(importDataCenter.getKey());
            resultatImport.getDonneesEntree().setDataCenters(importDataCenter.getValue());
        }

        if (csvEquipementPhysique != null) {
            Pair<RapportImport, List<EquipementPhysique>> importEquipementsPhysiques = importEquipementsPhysiques(nomLot, dateLotAsDate, nomOrganisation, csvEquipementPhysique);
            resultatImport.getRapports().add(importEquipementsPhysiques.getKey());
            resultatImport.getDonneesEntree().setEquipementsPhysiques(importEquipementsPhysiques.getValue());
        }

        if (csvEquipementVirtuel != null) {
            Pair<RapportImport, List<EquipementVirtuel>> importEquipementVirtuel = importEquipementsVirtuels(nomLot, dateLotAsDate, nomOrganisation, csvEquipementVirtuel);
            resultatImport.getRapports().add(importEquipementVirtuel.getKey());
            resultatImport.getDonneesEntree().getEquipementsVirtuels().addAll(importEquipementVirtuel.getValue());
        }

        if (csvApplication != null) {
            Pair<RapportImport, List<Application>> importApplications = importApplications(nomLot, dateLotAsDate, nomOrganisation, csvApplication);
            resultatImport.getRapports().add(importApplications.getKey());
            resultatImport.getDonneesEntree().getApplications().addAll(importApplications.getValue());
        }
        if (csvOperationNonIT != null) {
            Pair<RapportImport, List<OperationNonIT>> importOperationsNonIT = importOperationsNonIT(nomLot, dateLotAsDate, nomOrganisation, csvOperationNonIT);
            resultatImport.getRapports().add(importOperationsNonIT.getKey());
            resultatImport.getDonneesEntree().setOperationsNonIT(importOperationsNonIT.getValue());
        }
        if (csvMessagerie != null) {
            Pair<RapportImport, List<Messagerie>> importMessagerie = importMessageries(nomLot, dateLotAsDate, nomOrganisation, csvMessagerie);
            resultatImport.getRapports().add(importMessagerie.getKey());
            resultatImport.getDonneesEntree().setMessageries(importMessagerie.getValue());
        }

        if (csvEntite != null) {
            Pair<RapportImport, List<Entite>> importEntites = importEntite(nomLot, dateLotAsDate, nomOrganisation, csvEntite);
            resultatImport.getRapports().add(importEntites.getKey());
            resultatImport.getDonneesEntree().setEntites(importEntites.getValue());
        }

        return resultatImport;
    }

    @Override
    public Pair<RapportImport, List<DataCenter>> importDataCenter(String nomLot, LocalDate dateLot, String nomOrganisation, MultipartFile csvDataCenter) {
        List<DataCenter> domainModels = new ArrayList<>();
        RapportImport rapportImport = new RapportImport();
        rapportImport.setFichier(csvDataCenter.getOriginalFilename());
        rapportImport.setType("datacenter");

        try (Reader reader = new InputStreamReader(csvDataCenter.getInputStream())) {
            Iterable<CSVRecord> records = CSVFormat.DEFAULT.builder()
                    .setHeader()
                    .setDelimiter(CSV_SEPARATOR)
                    .setTrim(true)
                    .setAllowMissingColumnNames(true)
                    .setSkipHeaderRecord(true)
                    .build().parse(reader);
            records.forEach(csvRecord -> {
                if (!Arrays.stream(DATA_CENTER_HEADER).allMatch(csvRecord::isMapped)) {
                    rapportImport.getErreurs().add(errorMessages.get(LIGNE_INCONSISTENTE).formatted(csvDataCenter.getOriginalFilename(), csvRecord.getRecordNumber() + 1));
                } else {
                    String localisation = CSVHelper.safeString(csvRecord, "localisation");
                    String nomLongDataCenter = CSVHelper.safeString(csvRecord, "nomLongDatacenter");
                    String nomCourtDataCenter = CSVHelper.safeString(csvRecord, "nomCourtDatacenter");

                    String qualite = CSVHelper.safeString(csvRecord, "qualite");
                    if (qualite != null) qualite = qualite.toUpperCase();

                    String checkQualite = errorManagementService.checkQualiteDonnees(qualite, "Le datacenter", nomCourtDataCenter);
                    if (checkQualite != null) {
                        qualite = null;
                        rapportImport.getAvertissements().add(checkQualite);
                    }

                    var dataCenter = DataCenter.builder()
                            .statutTraitement(STATUT_TRAITEMENT_EN_ATTENTE)
                            .nomLot(nomLot)
                            .dateLot(dateLot)
                            .nomOrganisation(nomOrganisation)
                            .nomCourtDatacenter(nomCourtDataCenter)
                            .nomLongDatacenter(nomLongDataCenter)
                            .pue(CSVHelper.safeDouble(csvRecord, "pue"))
                            .localisation(localisation)
                            .nomEntite(CSVHelper.safeString(csvRecord, HEADER_NOM_ENTITE))
                            .nomSourceDonnee(CSVHelper.safeString(csvRecord, HEADER_NOM_SOURCE_DONNEE))
                            .qualite(qualite)
                            .build();

                    domainModels.add(dataCenter);
                    rapportImport.getErreurs().addAll(errorManagementService.checkDataCenter(dataCenter));
                }
            });

        } catch (FileNotFoundException e) {
            LOGGER.error("Erreur CSV pour des DataCenters introuvable", e);
            return Pair.of(RapportImport.builder()
                            .erreurs(Collections.singletonList("Le fichier CSV des DataCenters n'est pas trouvable."))
                            .build()
                    , Collections.emptyList()
            );
        } catch (Exception e) {
            LOGGER.error("Erreur durant la lecture d'un CSV pour des DataCenters", e);

            return Pair.of(RapportImport.builder()
                            .erreurs(Collections.singletonList("Le fichier CSV des DataCenter n'est pas lisible par le système."))
                            .build()
                    , Collections.emptyList()
            );
        }

        rapportImport.setNbrLignesImportees(domainModels.size());
        return Pair.of(rapportImport, domainModels);
    }

    @Override
    public Pair<RapportImport, List<EquipementPhysique>> importEquipementsPhysiques(String nomLot, LocalDate dateLot, String nomOrganisation, MultipartFile csvEquipementPhysique) {
        List<EquipementPhysique> domainModels = new ArrayList<>();
        RapportImport rapportImport = new RapportImport();
        rapportImport.setFichier(csvEquipementPhysique.getOriginalFilename());
        rapportImport.setType("equipement_physique");
        List<TypeItemDTO> typesItem = referentielServicePort.getAllTypesItem();
        Set<String> avertissements = new HashSet<>();

        try (Reader reader = new InputStreamReader(csvEquipementPhysique.getInputStream())) {
            Iterable<CSVRecord> records = CSVFormat.DEFAULT.builder()
                    .setHeader()
                    .setAllowMissingColumnNames(true)
                    .setDelimiter(CSV_SEPARATOR)
                    .setTrim(true)
                    .setSkipHeaderRecord(true)
                    .build().parse(reader);

            records.forEach(csvRecord ->
            {
                String nomEquipementPhysique = CSVHelper.safeString(csvRecord, LABEL_NOM_EQUIPEMENT_PHYSIQUE, "equipement");
                String typeEquipement = csvRecord.get("type");
                String checkNomTypeItem = errorManagementService.checkNomTypeItem(typeEquipement, "L'équipement physique", nomEquipementPhysique);
                var refTypeEquipementOpt = typesItem.stream()
                        .filter(refType -> refType.getType().equals(typeEquipement))
                        .findFirst();

                if (!Arrays.stream(EQUIPEMENT_PHYSIQUE_HEADER).allMatch(csvRecord::isMapped)) {
                    rapportImport.getErreurs().add(errorMessages.get(LIGNE_INCONSISTENTE).formatted(csvEquipementPhysique.getOriginalFilename(), csvRecord.getRecordNumber() + 1));
                } else if (checkNomTypeItem != null) {
                    rapportImport.getErreurs().add(checkNomTypeItem);
                } else if (refTypeEquipementOpt.isEmpty()) {
                    // CA 1.1
                    rapportImport.getErreurs().add(errorMessages.get("TYPE_ITEM_INCONNU").formatted(csvRecord.get("type"), CSVHelper.safeString(csvRecord, LABEL_NOM_EQUIPEMENT_PHYSIQUE, "equipement")));
                } else {
                    var goTelechargeStr = CSVHelper.safeString(csvRecord, "goTelecharge");
                    Float goTelecharge = goTelechargeStr == null ? null : NumberUtils.toFloat(goTelechargeStr);
                    var dureeUsageInterneStr = CSVHelper.safeString(csvRecord, "dureeUsageInterne");
                    Double dureeUsageInterne = NumberUtils.isCreatable(dureeUsageInterneStr) ? NumberUtils.toDouble(dureeUsageInterneStr) : null;
                    var dureeUsageAmontStr = CSVHelper.safeString(csvRecord, "dureeUsageAmont");
                    Double dureeUsageAmont = dureeUsageAmontStr == null ? 0 : NumberUtils.toDouble(dureeUsageAmontStr);
                    var dureeUsageAvalStr = CSVHelper.safeString(csvRecord, "dureeUsageAval");
                    Double dureeUsageAval = dureeUsageAvalStr == null ? 0 : NumberUtils.toDouble(dureeUsageAvalStr);
                    var tauxUtilisationStr = CSVHelper.safeString(csvRecord, "tauxUtilisation");
                    Double tauxUtilisation = NumberUtils.isCreatable(tauxUtilisationStr) ? NumberUtils.toDouble(tauxUtilisationStr) : null;

                    String qualite = CSVHelper.safeString(csvRecord, "qualite");
                    if (qualite != null) qualite = qualite.toUpperCase();

                    String checkQualite = errorManagementService.checkQualiteDonnees(qualite, "L'équipement physique", nomEquipementPhysique);
                    if (checkQualite != null) {
                        qualite = null;
                        avertissements.add(checkQualite);
                    }

                    EquipementPhysique equipementToAdd = EquipementPhysique.builder()
                            .statutTraitement(STATUT_TRAITEMENT_EN_ATTENTE)
                            .nomLot(nomLot)
                            .dateLot(dateLot)
                            .nomOrganisation(nomOrganisation)
                            .modele(CSVHelper.safeString(csvRecord, "modele", "refEquipement"))
                            .type(typeEquipement)
                            .quantite(CSVHelper.safeDouble(csvRecord, "quantite"))
                            .nomEquipementPhysique(nomEquipementPhysique)
                            .statut(CSVHelper.safeString(csvRecord, "statut"))
                            .paysDUtilisation(CSVHelper.safeString(csvRecord, "paysDUtilisation"))
                            .utilisateur(CSVHelper.safeString(csvRecord, "utilisateur"))
                            .dateAchat(CSVHelper.safeParseLocalDate(csvRecord, "dateAchat"))
                            .dateRetrait(CSVHelper.safeParseLocalDate(csvRecord, "dateRetrait"))
                            .dureeUsageInterne(dureeUsageInterne)
                            .dureeUsageAmont(dureeUsageAmont)
                            .dureeUsageAval(dureeUsageAval)
                            .nbCoeur(CSVHelper.safeString(csvRecord, "nbCoeur"))
                            .nomCourtDatacenter(CSVHelper.safeString(csvRecord, "nomCourtDatacenter", "refDatacenter"))
                            .goTelecharge(goTelecharge)
                            .modeUtilisation(CSVHelper.safeString(csvRecord, "modeUtilisation"))
                            .tauxUtilisation(tauxUtilisation)
                            .nbJourUtiliseAn(CSVHelper.safeDouble(csvRecord, "nbJourUtiliseAn"))
                            .consoElecAnnuelle(CSVHelper.safeDouble(csvRecord, "consoElecAnnuelle"))
                            .serveur(refTypeEquipementOpt.get().isServeur())
                            .nomEntite(CSVHelper.safeString(csvRecord, HEADER_NOM_ENTITE))
                            .nomSourceDonnee(CSVHelper.safeString(csvRecord, HEADER_NOM_SOURCE_DONNEE))
                            .qualite(qualite)
                            .build();

                    var erreurs = errorManagementService.checkEquipementPhysique(equipementToAdd, refTypeEquipementOpt.get().getRefItemParDefaut());
                    rapportImport.getErreurs().addAll(erreurs.getKey());
                    avertissements.addAll(erreurs.getValue());
                    defaultValueService.setEquipementValeurDefaut(equipementToAdd);
                    domainModels.add(equipementToAdd);
                }
            });

        } catch (FileNotFoundException e) {
            LOGGER.error("Erreur CSV pour des équipements physiques introuvable", e);
            return Pair.of(RapportImport.builder()
                            .erreurs(Collections.singletonList("Le fichier CSV des équipements physiques n'est pas trouvable."))
                            .build()
                    , Collections.emptyList()
            );
        } catch (Exception e) {
            LOGGER.error("Erreur durant la lecture d'un CSV pour des équipements physiques", e);

            return Pair.of(RapportImport.builder()
                            .erreurs(Collections.singletonList("Le fichier CSV des équipements physiques n'est pas lisible par le système."))
                            .build()
                    , Collections.emptyList()
            );
        }

        rapportImport.setNbrLignesImportees(domainModels.size());
        rapportImport.setAvertissements(avertissements.stream().toList());
        return Pair.of(rapportImport, domainModels);
    }

    @Override
    public Pair<RapportImport, List<EquipementVirtuel>> importEquipementsVirtuels(String nomLot, LocalDate dateLot, String nomOrganisation, MultipartFile csvEquipementVirtuel) {
        List<EquipementVirtuel> domainModels = new ArrayList<>();
        RapportImport rapportImport = new RapportImport();
        rapportImport.setFichier(csvEquipementVirtuel.getOriginalFilename());
        rapportImport.setType("equipement_virtuel");

        try (Reader reader = new InputStreamReader(csvEquipementVirtuel.getInputStream())) {
            Iterable<CSVRecord> records = CSVFormat.DEFAULT.builder()
                    .setHeader()
                    .setAllowMissingColumnNames(true)
                    .setDelimiter(CSV_SEPARATOR)
                    .setTrim(true)
                    .setSkipHeaderRecord(true)
                    .build().parse(reader);
            records.forEach(csvRecord ->
            {
                boolean isRecordOK = isFieldIsMappedAtLeastOnce(csvRecord, LABEL_NOM_EQUIPEMENT_VIRTUEL, LABEL_NOM_VM)
                        && Arrays.stream(EQUIPEMENT_VIRTUEL_HEADER).allMatch(csvRecord::isMapped);
                if (!isRecordOK) {
                    rapportImport.getErreurs().add(errorMessages.get(LIGNE_INCONSISTENTE).formatted(csvEquipementVirtuel.getOriginalFilename(), csvRecord.getRecordNumber() + 1));
                } else {
                    String nomEquipementVirtuel = CSVHelper.safeString(csvRecord, LABEL_NOM_EQUIPEMENT_VIRTUEL, LABEL_NOM_VM);
                    String qualite = CSVHelper.safeString(csvRecord, "qualite");
                    if (qualite != null) qualite = qualite.toUpperCase();

                    String checkQualite = errorManagementService.checkQualiteDonnees(qualite, "L'équipement virtuel", nomEquipementVirtuel);
                    if (checkQualite != null) {
                        qualite = null;
                        rapportImport.getAvertissements().add(checkQualite);
                    }

                    EquipementVirtuel equipementVirtuel = EquipementVirtuel.builder()
                            .statutTraitement(STATUT_TRAITEMENT_EN_ATTENTE)
                            .nomLot(nomLot)
                            .dateLot(dateLot)
                            .nomOrganisation(nomOrganisation)
                            .nomEquipementVirtuel(nomEquipementVirtuel)
                            .nomEquipementPhysique(CSVHelper.safeString(csvRecord, LABEL_NOM_EQUIPEMENT_PHYSIQUE))
                            .nomSourceDonneeEquipementPhysique(CSVHelper.safeString(csvRecord, "nomSourceDonneeEquipementPhysique"))
                            .vCPU(CSVHelper.safeInteger(csvRecord, "vCPU"))
                            .cluster(CSVHelper.safeString(csvRecord, "cluster"))
                            .consoElecAnnuelle(CSVHelper.safeDouble(csvRecord, "consoElecAnnuelle"))
                            .typeEqv(CSVHelper.safeString(csvRecord, "typeEqv"))
                            .capaciteStockage(CSVHelper.safeDouble(csvRecord, "capaciteStockage"))
                            .cleRepartition(CSVHelper.safeDouble(csvRecord, "cleRepartition"))
                            .nomEntite(CSVHelper.safeString(csvRecord, HEADER_NOM_ENTITE))
                            .nomSourceDonnee(CSVHelper.safeString(csvRecord, HEADER_NOM_SOURCE_DONNEE))
                            .qualite(qualite)
                            .build();

                    domainModels.add(equipementVirtuel);
                }
            });

        } catch (FileNotFoundException e) {
            LOGGER.error("Erreur CSV pour des équipements virtuels introuvable", e);
            return Pair.of(RapportImport.builder()
                            .erreurs(Collections.singletonList("Le fichier CSV des équipements virtuels n'est pas trouvable."))
                            .build()
                    , Collections.emptyList()
            );
        } catch (Exception e) {
            LOGGER.error("Erreur durant la lecture d'un CSV pour des équipements virtuels", e);

            return Pair.of(RapportImport.builder()
                            .erreurs(Collections.singletonList("Le fichier CSV des équipements virtuels n'est pas lisible par le système."))
                            .build()
                    , Collections.emptyList()
            );
        }

        rapportImport.setNbrLignesImportees(domainModels.size());
        return Pair.of(rapportImport, domainModels);

    }

    @Override
    public Pair<RapportImport, List<Application>> importApplications(String nomLot, LocalDate dateLot, String nomOrganisation, MultipartFile csvApplication) {
        List<Application> domainModels = new ArrayList<>();
        RapportImport rapportImport = new RapportImport();
        rapportImport.setFichier(csvApplication.getOriginalFilename());
        rapportImport.setType("application");

        try (Reader reader = new InputStreamReader(csvApplication.getInputStream())) {
            Iterable<CSVRecord> records = CSVFormat.DEFAULT.builder()
                    .setHeader()
                    .setAllowMissingColumnNames(true)
                    .setDelimiter(CSV_SEPARATOR)
                    .setTrim(true)
                    .setSkipHeaderRecord(true)
                    .build().parse(reader);
            records.forEach(csvRecord ->
            {
                boolean isRecordOK = isFieldIsMappedAtLeastOnce(csvRecord, LABEL_NOM_EQUIPEMENT_VIRTUEL, LABEL_NOM_VM)
                        && Arrays.stream(APPLICATION_HEADER).allMatch(csvRecord::isMapped);
                if (!isRecordOK) {
                    rapportImport.getErreurs().add(errorMessages.get(LIGNE_INCONSISTENTE).formatted(csvApplication.getOriginalFilename(), csvRecord.getRecordNumber() + 1));
                } else {
                    String nomApplication = CSVHelper.safeString(csvRecord, "nomApplication");
                    String qualite = CSVHelper.safeString(csvRecord, "qualite");
                    if (qualite != null) qualite = qualite.toUpperCase();

                    String checkQualite = errorManagementService.checkQualiteDonnees(qualite, "L'application", nomApplication);
                    if (checkQualite != null) {
                        qualite = null;
                        rapportImport.getAvertissements().add(checkQualite);
                    }
                    Application application = Application.builder()
                            .statutTraitement(STATUT_TRAITEMENT_EN_ATTENTE)
                            .nomLot(nomLot)
                            .dateLot(dateLot)
                            .nomOrganisation(nomOrganisation)
                            .nomApplication(nomApplication)
                            .typeEnvironnement(CSVHelper.safeString(csvRecord, "typeEnvironnement"))
                            .nomEquipementVirtuel(CSVHelper.safeString(csvRecord, LABEL_NOM_EQUIPEMENT_VIRTUEL, LABEL_NOM_VM))
                            .nomSourceDonneeEquipementVirtuel(CSVHelper.safeString(csvRecord, "nomSourceDonneeEquipementVirtuel"))
                            .domaine(CSVHelper.safeString(csvRecord, "domaine"))
                            .sousDomaine(CSVHelper.safeString(csvRecord, "sousDomaine"))
                            .nomEntite(CSVHelper.safeString(csvRecord, HEADER_NOM_ENTITE))
                            .nomEquipementPhysique(CSVHelper.safeString(csvRecord, LABEL_NOM_EQUIPEMENT_PHYSIQUE))
                            .nomSourceDonnee(CSVHelper.safeString(csvRecord, HEADER_NOM_SOURCE_DONNEE))
                            .qualite(qualite)
                            .build();

                    domainModels.add(application);
                }
            });

        } catch (FileNotFoundException e) {
            LOGGER.error("Erreur CSV pour des applications introuvable", e);
            return Pair.of(RapportImport.builder()
                            .erreurs(Collections.singletonList("Le fichier CSV des Applications n'est pas trouvable."))
                            .build()
                    , Collections.emptyList()
            );
        } catch (Exception e) {
            LOGGER.error("Erreur durant la lecture d'un CSV pour des Applications", e);

            return Pair.of(RapportImport.builder()
                            .erreurs(Collections.singletonList("Le fichier CSV des Applications n'est pas lisible par le système."))
                            .build()
                    , Collections.emptyList()
            );
        }

        rapportImport.setNbrLignesImportees(domainModels.size());
        return Pair.of(rapportImport, domainModels);
    }

    @Override
    public Pair<RapportImport, List<OperationNonIT>> importOperationsNonIT(String nomLot, LocalDate dateLot, String nomOrganisation, MultipartFile csvOperationNonIT) {
        List<OperationNonIT> domainModels = new ArrayList<>();
        RapportImport rapportImport = new RapportImport();
        rapportImport.setFichier(csvOperationNonIT.getOriginalFilename());
        rapportImport.setType("operation_non_it");
        List<TypeItemDTO> typesItem = referentielServicePort.getAllTypesItem();

        try (Reader reader = new InputStreamReader(csvOperationNonIT.getInputStream())) {
            Iterable<CSVRecord> records = CSVFormat.DEFAULT.builder()
                    .setHeader()
                    .setAllowMissingColumnNames(true)
                    .setDelimiter(CSV_SEPARATOR)
                    .setTrim(true)
                    .setSkipHeaderRecord(true)
                    .build().parse(reader);
            records.forEach(csvRecord ->
            {
                String nomItem = CSVHelper.safeString(csvRecord, LABEL_nom_item_non_it);
                String typeItem = csvRecord.get("type");
                String checkNomTypeItem = errorManagementService.checkNomTypeItem(typeItem, "L'opération non IT", nomItem);
                var refTypeItemOpt = typesItem.stream()
                        .filter(refType -> refType.getType().equals(typeItem))
                        .findFirst();
                String error = checkCSVRecord(csvRecord, OPERATION_NON_IT_HEADER, OPERATION_NON_IT_NOT_BLANK_FIELDS);
                if (error != null) {
                    rapportImport.getErreurs().add(error);
                } else if (checkNomTypeItem != null) {
                    rapportImport.getErreurs().add(checkNomTypeItem);
                } else if (refTypeItemOpt.isEmpty()) {
                    rapportImport.getErreurs().add(errorMessages.get("TYPE_ITEM_INCONNU").formatted(csvRecord.get("type"), nomItem));
                } else {
                    String qualite = CSVHelper.safeString(csvRecord, "qualite");
                    if (qualite != null) qualite = qualite.toUpperCase();

                    String checkQualite = errorManagementService.checkQualiteDonnees(qualite, "L'item", nomItem);
                    if (checkQualite != null) {
                        qualite = null;
                        rapportImport.getAvertissements().add(checkQualite);
                    }

                    OperationNonIT operationToAdd = OperationNonIT.builder()
                            .statutTraitement(STATUT_TRAITEMENT_EN_ATTENTE)
                            .nomLot(nomLot)
                            .dateLot(dateLot)
                            .nomOrganisation(nomOrganisation)
                            .nomItemNonIT(nomItem)
                            .quantite(CSVHelper.safeDouble(csvRecord, "quantite"))
                            .type(csvRecord.get("type"))
                            .dureeDeVie(CSVHelper.safeDouble(csvRecord, "dureeDeVie"))
                            .localisation(CSVHelper.safeString(csvRecord, "localisation"))
                            .nomEntite(CSVHelper.safeString(csvRecord, HEADER_NOM_ENTITE))
                            .nomSourceDonnee(CSVHelper.safeString(csvRecord, HEADER_NOM_SOURCE_DONNEE))
                            .nomCourtDatacenter(CSVHelper.safeString(csvRecord, "nomCourtDatacenter", "refDatacenter"))
                            .description(CSVHelper.safeString(csvRecord, "description"))
                            .consoElecAnnuelle(CSVHelper.safeDouble(csvRecord, "consoElecAnnuelle"))
                            .qualite(qualite)
                            .build();

                    var erreurs = errorManagementService.checkOperationNonIT(operationToAdd, refTypeItemOpt.get().getRefItemParDefaut());

                    rapportImport.getErreurs().addAll(erreurs.getKey());
                    rapportImport.getAvertissements().addAll(erreurs.getValue());

                    domainModels.add(operationToAdd);
                }
            });

        } catch (FileNotFoundException e) {
            LOGGER.error("Erreur CSV pour des opérations non IT introuvables", e);
            return Pair.of(RapportImport.builder()
                            .erreurs(Collections.singletonList("Le fichier CSV des opérations non IT n'est pas trouvable."))
                            .build()
                    , Collections.emptyList()
            );
        } catch (Exception e) {
            LOGGER.error("Erreur durant la lecture d'un CSV pour des opérations non IT", e);
            return Pair.of(RapportImport.builder()
                            .erreurs(Collections.singletonList("Le fichier CSV des opérations non IT n'est pas lisible par le système."))
                            .build()
                    , Collections.emptyList()
            );
        }
        rapportImport.setNbrLignesImportees(domainModels.size());
        return Pair.of(rapportImport, domainModels);
    }

    @Override
    public Pair<RapportImport, List<Messagerie>> importMessageries(String nomLot, LocalDate dateLot, String nomOrganisation, MultipartFile csvMessagerie) {
        List<Messagerie> domainModels = new ArrayList<>();
        RapportImport rapportImport = new RapportImport();
        rapportImport.setFichier(csvMessagerie.getOriginalFilename());
        rapportImport.setType("messagerie");

        try (Reader reader = new InputStreamReader(csvMessagerie.getInputStream())) {
            Iterable<CSVRecord> records = CSVFormat.DEFAULT.builder()
                    .setHeader()
                    .setAllowMissingColumnNames(true)
                    .setDelimiter(CSV_SEPARATOR)
                    .setTrim(true)
                    .setSkipHeaderRecord(true)
                    .build().parse(reader);
            records.forEach(csvRecord ->
            {
                if (!Arrays.stream(MESSAGERIE_HEADER).allMatch(csvRecord::isMapped)) {
                    rapportImport.getErreurs().add(errorMessages.get(LIGNE_INCONSISTENTE).formatted(csvMessagerie.getOriginalFilename(), csvRecord.getRecordNumber() + 1));
                } else {
                    domainModels.add(
                            Messagerie.builder()
                                    .statutTraitement(STATUT_TRAITEMENT_EN_ATTENTE)
                                    .nomLot(nomLot)
                                    .dateLot(dateLot)
                                    .nomOrganisation(nomOrganisation)
                                    .nombreMailEmis(CSVHelper.safeInteger(csvRecord, "nombreMailEmis"))
                                    .nombreMailEmisXDestinataires(CSVHelper.safeInteger(csvRecord, "nombreMailEmisXDestinataires"))
                                    .volumeTotalMailEmis(CSVHelper.safeInteger(csvRecord, "volumeTotalMailEmis"))
                                    .moisAnnee(CSVHelper.safeInteger(csvRecord, "MoisAnnee"))
                                    .nomEntite(CSVHelper.safeString(csvRecord, HEADER_NOM_ENTITE))
                                    .nomSourceDonnee(CSVHelper.safeString(csvRecord, HEADER_NOM_SOURCE_DONNEE))
                                    .build()
                    );
                }
            });

        } catch (FileNotFoundException e) {
            LOGGER.error("Erreur CSV pour de la messagerie introuvable", e);
            return Pair.of(RapportImport.builder()
                            .erreurs(Collections.singletonList("Le fichier CSV de la messagerie n'est pas trouvable."))
                            .build()
                    , Collections.emptyList()
            );
        } catch (Exception e) {
            LOGGER.error("Erreur durant la lecture d'un CSV pour de la messagerie", e);

            return Pair.of(RapportImport.builder()
                            .erreurs(Collections.singletonList("Le fichier CSV de la messagerie n'est pas lisible par le système."))
                            .build()
                    , Collections.emptyList()
            );
        }

        rapportImport.setNbrLignesImportees(domainModels.size());
        return Pair.of(rapportImport, domainModels);
    }

    @Override
    public Pair<RapportImport, List<Entite>> importEntite(String nomLot, LocalDate dateLot, String nomOrganisation, MultipartFile csvEntite) {
        List<Entite> domainModels = new ArrayList<>();
        RapportImport rapportImport = new RapportImport();
        rapportImport.setFichier(csvEntite.getOriginalFilename());
        rapportImport.setType("entite");

        try (Reader reader = new InputStreamReader(csvEntite.getInputStream())) {
            Iterable<CSVRecord> records = CSVFormat.DEFAULT.builder()
                    .setHeader()
                    .setAllowMissingColumnNames(true)
                    .setDelimiter(CSV_SEPARATOR)
                    .setTrim(true)
                    .setSkipHeaderRecord(true)
                    .build().parse(reader);
            records.forEach(csvRecord ->
            {
                if (!Arrays.stream(ENTITE_HEADER).allMatch(csvRecord::isMapped)) {
                    rapportImport.getErreurs().add(errorMessages.get(LIGNE_INCONSISTENTE).formatted(csvEntite.getOriginalFilename(), csvRecord.getRecordNumber() + 1));
                } else {
                    domainModels.add(
                            Entite.builder()
                                    .nomLot(nomLot)
                                    .dateLot(dateLot)
                                    .nomOrganisation(nomOrganisation)
                                    .nomEntite(CSVHelper.safeString(csvRecord, HEADER_NOM_ENTITE))
                                    .responsableEntite(CSVHelper.safeString(csvRecord, "responsableEntite"))
                                    .responsableNumeriqueDurable(CSVHelper.safeString(csvRecord, "responsableNumeriqueDurable"))
                                    .nbCollaborateurs(CSVHelper.safeInteger(csvRecord, "nbCollaborateurs"))
                                    .nomSourceDonnee(CSVHelper.safeString(csvRecord, HEADER_NOM_SOURCE_DONNEE))
                                    .build()
                    );
                }
            });

        } catch (FileNotFoundException e) {
            LOGGER.error("Erreur CSV pour des entités introuvable", e);
            return Pair.of(RapportImport.builder()
                            .erreurs(Collections.singletonList("Le fichier CSV des entités n'est pas trouvable."))
                            .build()
                    , Collections.emptyList()
            );
        } catch (Exception e) {
            LOGGER.error("Erreur durant la lecture d'un CSV pour les entités", e);

            return Pair.of(RapportImport.builder()
                            .erreurs(Collections.singletonList("Le fichier CSV des entités n'est pas lisible par le système."))
                            .build()
                    , Collections.emptyList()
            );
        }

        rapportImport.setNbrLignesImportees(domainModels.size());
        return Pair.of(rapportImport, domainModels);
    }
}

