package com.iqser.red.service.persistence.management.v1.processor.dataexchange.service;

import static com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter.convert;

import java.io.File;
import java.io.FileOutputStream;
import java.nio.file.Files;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import com.iqser.red.service.persistence.management.v1.processor.dataexchange.models.ComponentMappingImportModel;
import com.iqser.red.service.persistence.management.v1.processor.dataexchange.models.EntityTypeImportModel;
import com.iqser.red.service.persistence.management.v1.processor.dataexchange.models.ImportTemplateResult;
import com.iqser.red.service.persistence.management.v1.processor.dataexchange.models.TemplateImportInfo;
import com.iqser.red.service.persistence.management.v1.processor.dataexchange.zipreaders.DossierTemplateArchiveReader;
import com.iqser.red.service.persistence.management.v1.processor.dataexchange.zipreaders.ZipEntryIterator;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.ColorsEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.FileAttributesGeneralConfigurationEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.WatermarkEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.ComponentDefinitionEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierAttributeConfigEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierStatusEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierTemplateEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.FileAttributeConfigEntity;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.ConflictException;
import com.iqser.red.service.persistence.management.v1.processor.service.ColorsService;
import com.iqser.red.service.persistence.management.v1.processor.service.ComponentMappingService;
import com.iqser.red.service.persistence.management.v1.processor.service.CurrentApplicationTypeProvider;
import com.iqser.red.service.persistence.management.v1.processor.service.DateFormatsValidationService;
import com.iqser.red.service.persistence.management.v1.processor.service.LayoutParsingTypeProvider;
import com.iqser.red.service.persistence.management.v1.processor.service.DefaultDateFormatsProvider;
import com.iqser.red.service.persistence.management.v1.processor.service.ReportTemplateService;
import com.iqser.red.service.persistence.management.v1.processor.service.RulesValidationService;
import com.iqser.red.service.persistence.management.v1.processor.service.WatermarkService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.ComponentDefinitionPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DateFormatsPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierAttributeConfigPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierTemplatePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileAttributeConfigPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.LegalBasisMappingPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.ReportTemplatePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.RulesPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierTemplateRepository;
import com.iqser.red.service.persistence.management.v1.processor.settings.FileManagementServiceSettings;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.WatermarkModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinition;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinitionAddRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentMappingMetadata;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DateFormatPatternErrorMessage;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DossierTemplate;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DossierTemplateStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.Colors;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.CreateOrUpdateDossierStatusRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.DossierStatusInfo;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.importexport.ImportDossierTemplateRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.Type;
import com.iqser.red.service.redaction.v1.model.DroolsValidation;
import com.iqser.red.storage.commons.service.StorageService;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.knecon.fforesight.tenantcommons.TenantContext;

import io.micrometer.observation.annotation.Observed;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.experimental.FieldDefaults;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@RequiredArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class DossierTemplateImportService {

    DossierTemplateRepository dossierTemplateRepository;
    LegalBasisMappingPersistenceService legalBasisMappingPersistenceService;
    RulesPersistenceService rulesPersistenceService;
    DateFormatsPersistenceService dateFormatsPersistenceService;
    DossierTemplatePersistenceService dossierTemplatePersistenceService;
    DossierAttributeConfigPersistenceService dossierAttributeConfigPersistenceService;
    FileAttributeConfigPersistenceService fileAttributeConfigPersistenceService;
    ColorsService colorsService;
    DossierStatusPersistenceService dossierStatusPersistenceService;
    WatermarkService watermarkService;
    ReportTemplateService reportTemplateService;
    ReportTemplatePersistenceService reportTemplatePersistenceService;
    RulesValidationService rulesValidationService;
    DateFormatsValidationService dateFormatsValidationService;
    StorageService storageService;
    FileManagementServiceSettings settings;
    ComponentMappingService componentMappingService;
    ComponentDefinitionPersistenceService componentDefinitionPersistenceService;
    EntityTypeImportService entityTypeImportService;
    SystemManagedTypesImport systemManagedTypesImport;
    LayoutParsingTypeProvider layoutParsingTypeProvider;
    CurrentApplicationTypeProvider currentApplicationTypeProvider;
    DefaultDateFormatsProvider defaultDateFormatsProvider;


    public String importDossierTemplate(ImportDossierTemplateRequest request) {

        ImportTemplateResult archiveResult;
        try {
            archiveResult = readDossierTemplateImportArchive(request);
        } catch (Exception e) {
            throw new BadRequestException("The import process failed in step: 0 - while reading the archive content with error: " + e.getMessage());
        }

        return importDossierTemplate(archiveResult).getDossierTemplateId();
    }


    @SneakyThrows
    public ImportTemplateResult readDossierTemplateImportArchive(ImportDossierTemplateRequest request) {

        DossierTemplateArchiveReader dossierTemplateArchiveReader = new DossierTemplateArchiveReader(request.getUserId(),
                                                                                                     request.getDossierTemplateId(),
                                                                                                     request.isUpdateExistingDossierTemplate());

        try (ZipEntryIterator zipEntryIterator = new ZipEntryIterator(request.getArchive(), settings.getCompressionThresholdRatio())) {
            zipEntryIterator.forEachRemaining(dossierTemplateArchiveReader::handleZipEntryData);
        }

        return dossierTemplateArchiveReader.buildResult();
    }


    @Observed(name = "DossierTemplateImportService", contextualName = "import-template")
    public TemplateImportInfo importDossierTemplate(ImportTemplateResult request) {

        long start = System.currentTimeMillis();
        String dossierTemplateId;
        int importStep = 1;
        var dossierTemplateMeta = request.getDossierTemplate();

        if (dossierTemplateMeta.getLayoutParsingType() == null) {
            dossierTemplateMeta.setLayoutParsingType(layoutParsingTypeProvider.deferFromCurrentApplicationType());
        }

        TemplateImportInfo templateImportInfo = TemplateImportInfo.builder().build();

        DossierTemplateEntity existingDossierTemplate = null;
        if (request.getDossierTemplateId() != null && request.isUpdateExistingTemplate()) {
            var dossierTemplateOptional = dossierTemplateRepository.findByIdAndNotDeleted(request.getDossierTemplateId());
            existingDossierTemplate = dossierTemplateOptional.orElse(null);
        }

        try {

            if (existingDossierTemplate != null) {
                dossierTemplateId = existingDossierTemplate.getId();

                // override the existing dossier template
                updateDossierTemplateMeta(existingDossierTemplate, dossierTemplateMeta, request.getUserId());
                dossierTemplateRepository.save(existingDossierTemplate);

                existingDossierTemplate.setDossierTemplateStatus(DossierTemplateStatus.valueOf(dossierTemplatePersistenceService.computeDossierTemplateStatus(
                        existingDossierTemplate).name()));

                // set colors
                importStep = 2;
                this.setColors(dossierTemplateId, request.getColors());

                // set watermarks
                if (CollectionUtils.isNotEmpty(request.getWatermarks())) {
                    importStep = 3;
                    Set<String> toSetWatermarks = request.getWatermarks()
                            .stream()
                            .map(WatermarkModel::getName)
                            .filter(Objects::nonNull)
                            .collect(Collectors.toSet());
                    var currentWatermarkConfigs = watermarkService.getWatermarksForDossierTemplateId(dossierTemplateId);
                    Set<WatermarkEntity> configsToRemove = currentWatermarkConfigs.stream()
                            .filter(c -> !toSetWatermarks.contains(c.getName()))
                            .collect(Collectors.toSet());
                    var watermarkNameToEntity = currentWatermarkConfigs.stream()
                            .collect(Collectors.toMap(WatermarkEntity::getName, Function.identity()));

                    request.getWatermarks()
                            .forEach(watermark -> {
                                log.info("watermark to add: " + watermark.getName());
                                Long initialId = watermark.getId();
                                if (!watermarkNameToEntity.isEmpty() && watermarkNameToEntity.get(watermark.getName()) != null) {

                                    watermark.setId(watermarkNameToEntity.get(watermark.getName()).getId());
                                } else {
                                    watermark.setId(null);
                                }
                                watermark.setDossierTemplateId(dossierTemplateId);
                                var entity = watermarkService.createOrUpdateWatermark(watermark);
                                templateImportInfo.getLongMapping().put(initialId, entity.getId());
                            });
                    configsToRemove.forEach(watermark -> watermarkService.deleteWatermark(watermark.getId()));
                }

                // dossier status
                importStep = 4;
                if (CollectionUtils.isNotEmpty(request.getDossierStatusInfos())) {
                    this.updateDossierStates(request, dossierTemplateId);
                } else { // no states to add, delete current states
                    List<DossierStatusInfo> currentStates = dossierStatusPersistenceService.getAllDossierStatusForTemplate(dossierTemplateId);
                    currentStates.forEach(state -> dossierStatusPersistenceService.deleteDossierStatus(state.getId(), null));
                }

                // update dossier attributes
                importStep = 5;
                if (CollectionUtils.isNotEmpty(request.getDossierAttributesConfigs())) {
                    this.updateDossierAttributes(request, dossierTemplateId, templateImportInfo);
                } else { // no dossier attributes to add, but delete existing ones
                    var currentConfigs = dossierAttributeConfigPersistenceService.getDossierAttributes(dossierTemplateId);
                    currentConfigs.forEach(da -> dossierAttributeConfigPersistenceService.deleteDossierAttribute(da.getId()));
                }

                //update file attributes
                importStep = 6;
                if (CollectionUtils.isNotEmpty(request.getFileAttributesConfigs())) {
                    this.updateFileAttributes(request, dossierTemplateId, templateImportInfo);
                } else { // no file attributes to add, but delete existing
                    var currentConfigs = fileAttributeConfigPersistenceService.getFileAttributes(dossierTemplateId);
                    currentConfigs.forEach(fa -> fileAttributeConfigPersistenceService.deleteFileAttribute(fa.getId()));
                }

                //set report templates
                importStep = 7;
                var existingReports = reportTemplatePersistenceService.findByDossierTemplateId(dossierTemplateId);

                List<String> reportsUpdated = new ArrayList<>();
                if (CollectionUtils.isNotEmpty(request.getReportTemplateUploadRequests())) {
                    request.getReportTemplateUploadRequests()
                            .forEach(reportRequest -> {
                                reportRequest.setDossierTemplateId(dossierTemplateId);
                                var report = reportTemplateService.uploadTemplate(reportRequest);
                                reportsUpdated.add(report.getTemplateId());
                            });
                }

                // delete the reports that were not in the import
                existingReports.forEach(r -> {
                    String storageId = r.getStorageId();
                    if (!reportsUpdated.contains(r.getTemplateId())) {
                        storageService.deleteObject(TenantContext.getTenantId(), storageId);
                        reportTemplatePersistenceService.delete(r.getTemplateId());
                    }
                });

                // set legal basis
                importStep = 8;
                if (CollectionUtils.isNotEmpty(request.getLegalBases())) {
                    legalBasisMappingPersistenceService.setLegalBasisMapping(dossierTemplateId, request.getLegalBases());
                } else { // delete existing
                    legalBasisMappingPersistenceService.deleteLegalBasis(dossierTemplateId);
                }

                // file attribute general configuration
                if (request.getFileAttributesGeneralConfiguration() != null) {
                    importStep = 9;
                    fileAttributeConfigPersistenceService.setFileAttributesGeneralConfig(dossierTemplateId,
                                                                                         convert(request.getFileAttributesGeneralConfiguration(),
                                                                                                 FileAttributesGeneralConfigurationEntity.class));
                }

                // component definitions
                importStep = 10;
                if (CollectionUtils.isNotEmpty(request.getComponentDefinitions())) {
                    this.updateComponents(request, dossierTemplateId);
                } else { // no components to add, but remove existing ones
                    List<ComponentDefinitionEntity> currentComponents = componentDefinitionPersistenceService.findComponentsByDossierTemplateId(dossierTemplateId);
                    this.deleteComponents(currentComponents);
                }

                //set component mappings
                importStep = 11;
                setComponentMappings(dossierTemplateId, request.getComponentMappings());

                importStep = 12;
                entityTypeImportService.updateTypes(dossierTemplateId,
                                                    null,
                                                    entityTypeImportService.updateAndCleanSystemManagedFromImportTypes(request.getEntityTypeImportModel(), dossierTemplateId));

            } else {

                // creates new dossier template
                if (StringUtils.isEmpty(dossierTemplateMeta.getName())) {
                    throw new ConflictException("DossierTemplate name must be set");
                }
                dossierTemplateMeta.setId("");
                this.validateDossierTemplateName(dossierTemplateMeta);
                DossierTemplateEntity dossierTemplateEntity = new DossierTemplateEntity();

                // order is important
                BeanUtils.copyProperties(dossierTemplateMeta, dossierTemplateEntity);
                dossierTemplateEntity.setId(UUID.randomUUID().toString());
                dossierTemplateEntity.setDateAdded(OffsetDateTime.now().truncatedTo(ChronoUnit.MILLIS));
                dossierTemplateEntity.setCreatedBy(request.getUserId());

                var loadedDossierTemplate = dossierTemplateRepository.save(dossierTemplateEntity);
                loadedDossierTemplate.setDossierTemplateStatus(dossierTemplatePersistenceService.computeDossierTemplateStatus(loadedDossierTemplate));

                dossierTemplateId = loadedDossierTemplate.getId();

                // set colors
                importStep = 2;
                this.setColors(dossierTemplateId, request.getColors());

                // set watermarks
                if (CollectionUtils.isNotEmpty(request.getWatermarks())) {
                    importStep = 3;
                    request.getWatermarks()
                            .forEach(watermark -> {
                                Long initialId = watermark.getId();
                                watermark.setDossierTemplateId(dossierTemplateId);
                                watermark.setId(null);
                                var en = watermarkService.createOrUpdateWatermark(watermark);
                                templateImportInfo.getLongMapping().put(initialId, en.getId());
                            });
                }

                // dossier status
                if (CollectionUtils.isNotEmpty(request.getDossierStatusInfos())) {
                    importStep = 4;
                    request.getDossierStatusInfos()
                            .forEach(state -> {
                                state.setId(null);
                                this.updateDossierStatus(dossierTemplateId, state);
                            });
                }

                //set dossier attributes
                if (CollectionUtils.isNotEmpty(request.getDossierAttributesConfigs())) {
                    importStep = 5;
                    request.getDossierAttributesConfigs()
                            .forEach(da -> {
                                String initialId = da.getId();
                                da.setId(null); // in order to create a new dossier attribute
                                var e = dossierAttributeConfigPersistenceService.addOrUpdateDossierAttribute(dossierTemplateId, convert(da, DossierAttributeConfigEntity.class));
                                templateImportInfo.getIdMapping().put(initialId, e.getId());
                            });
                }

                //set file attributes
                if (CollectionUtils.isNotEmpty(request.getFileAttributesConfigs())) {
                    importStep = 6;
                    request.getFileAttributesConfigs()
                            .forEach(fa -> {
                                String initialId = fa.getId();
                                fa.setId(null); // in order to force creation of new file attribute
                                var e = fileAttributeConfigPersistenceService.addOrUpdateFileAttribute(dossierTemplateId, convert(fa, FileAttributeConfigEntity.class));
                                templateImportInfo.getIdMapping().put(initialId, e.getId());
                            });
                }

                //set report templates
                if (CollectionUtils.isNotEmpty(request.getReportTemplateUploadRequests())) {
                    importStep = 7;
                    request.getReportTemplateUploadRequests()
                            .forEach(reportRequest -> {
                                reportRequest.setDossierTemplateId(dossierTemplateId);
                                reportTemplateService.uploadTemplate(reportRequest);
                            });
                }
                // set legal basis
                if (CollectionUtils.isNotEmpty(request.getLegalBases())) {
                    importStep = 8;
                    legalBasisMappingPersistenceService.setLegalBasisMapping(dossierTemplateId, request.getLegalBases());
                }

                // file attribute general configuration
                if (request.getFileAttributesGeneralConfiguration() != null) {
                    importStep = 9;
                    fileAttributeConfigPersistenceService.setFileAttributesGeneralConfig(dossierTemplateId,
                                                                                         convert(request.getFileAttributesGeneralConfiguration(),
                                                                                                 FileAttributesGeneralConfigurationEntity.class));
                }

                // set components
                if (CollectionUtils.isNotEmpty(request.getComponentDefinitions())) {
                    importStep = 10;
                    for (ComponentDefinition componentDefinition : request.getComponentDefinitions()) {
                        ComponentDefinitionAddRequest componentDefinitionAddRequest = ComponentDefinitionAddRequest.builder()
                                .displayName(componentDefinition.getDisplayName())
                                .description(componentDefinition.getDescription())
                                .technicalName(componentDefinition.getTechnicalName())
                                .build();
                        componentDefinitionPersistenceService.insert(componentDefinitionAddRequest, componentDefinition.getRank(), dossierTemplateId);
                    }
                }

                //set component mappings
                importStep = 11;
                setComponentMappings(dossierTemplateId, request.getComponentMappings());

                //set types
                importStep = 12;
                entityTypeImportService.addMissingSystemManagedTypesToImport(request.getEntityTypeImportModel(), systemManagedTypesImport.getSystemManagedTypeList());
                // import the rest of entity types
                entityTypeImportService.importEntityTypes(dossierTemplateId, null, request.getEntityTypeImportModel());

            }

            importStep = 13;
            setRulesWhenCompiled(request, dossierTemplateId);

            importStep = 14;
            setDataFormats(request, dossierTemplateId);

        } catch (Exception e) {
            throw new BadRequestException("The import process failed in step: " + importStep + " with error: " + e.getMessage());
        }

        templateImportInfo.setDossierTemplateId(dossierTemplateId);

        long elapsedTime = System.currentTimeMillis() - start;
        log.info("Finished import dossier template : {} for: {}", elapsedTime, dossierTemplateId);
        return templateImportInfo;
    }


    @SneakyThrows
    private void setComponentMappings(String dossierTemplateId, List<ComponentMappingImportModel> componentMappings) {

        List<ComponentMappingMetadata> existingMappings = componentMappingService.getMetaDataByDossierTemplateId(dossierTemplateId);
        existingMappings.forEach(metadata -> componentMappingService.delete(dossierTemplateId, metadata.getId()));

        for (ComponentMappingImportModel componentMapping : componentMappings) {
            File tmpFile = Files.createTempFile("mapping", ".csv").toFile();
            try (var out = new FileOutputStream(tmpFile)) {
                out.write(componentMapping.csvData());
            }

            ComponentMappingMetadata createdMapping = componentMappingService.create(dossierTemplateId,
                                                                                     componentMapping.metadata().getName(),
                                                                                     componentMapping.metadata().getFileName(),
                                                                                     componentMapping.metadata().getDelimiter(),
                                                                                     componentMapping.metadata().getEncoding(),
                                                                                     tmpFile,
                                                                                     componentMapping.metadata().getQuoteChar());

            componentMappingService.setVersion(createdMapping.getId(), componentMapping.metadata().getVersion());
            assert tmpFile.delete();
        }

    }


    private void setRulesWhenCompiled(ImportTemplateResult request, String dossierTemplateId) {

        DroolsValidation droolsValidation = rulesValidationService.validateRules(RuleFileType.ENTITY, request.getRuleSet());
        if (!droolsValidation.isCompiled()) {
            droolsValidation.getSyntaxErrorMessages()
                    .forEach(errorMessage -> log.error(errorMessage.getMessage()));
            droolsValidation.getBlacklistErrorMessages()
                    .forEach(errorMessage -> log.error(errorMessage.getMessage()));
            throw new BadRequestException("The entity rules do not compile or contain blacklisted keywords!");
        }
        rulesPersistenceService.setRules(request.getRuleSet(),
                                         dossierTemplateId,
                                         RuleFileType.ENTITY,
                                         request.getRulesExportModel().getVersion(),
                                         request.getRulesExportModel().isTimeoutDetected());
        if (request.getComponentRuleSet() != null) {
            DroolsValidation componentDroolsValidation = rulesValidationService.validateRules(RuleFileType.COMPONENT, request.getComponentRuleSet());
            if (!componentDroolsValidation.isCompiled()) {
                componentDroolsValidation.getSyntaxErrorMessages()
                        .forEach(errorMessage -> log.error(errorMessage.getMessage()));
                componentDroolsValidation.getBlacklistErrorMessages()
                        .forEach(errorMessage -> log.error(errorMessage.getMessage()));
                throw new BadRequestException("The component rules do not compile or contain blacklisted keywords!");
            }
            rulesPersistenceService.setRules(request.getComponentRuleSet(),
                                             dossierTemplateId,
                                             RuleFileType.COMPONENT,
                                             request.getComponentRulesExportModel().getVersion(),
                                             request.getComponentRulesExportModel().isTimeoutDetected());

        }
    }


    private void setDataFormats(ImportTemplateResult request, String dossierTemplateId) {

        String dateFormats = request.getDateFormats();
        if (dateFormats == null && currentApplicationTypeProvider.isDocuMine()) {
            dateFormats = defaultDateFormatsProvider.getDateFormats();
        }
        if (dateFormats != null) {
            List<DateFormatPatternErrorMessage> dateFormatPatternErrorMessages = dateFormatsValidationService.validateDateFormats(dateFormats);
            if (!dateFormatPatternErrorMessages.isEmpty()) {
                throw new BadRequestException("The date formats file contains errors");
            }
            dateFormatsPersistenceService.setDateFormats(dateFormats, dossierTemplateId, request.getDateFormatsExportModel().getVersion());
        }

    }


    private void updateDossierTemplateMeta(DossierTemplateEntity dossierTemplateEntity, DossierTemplate dossierTemplate, String userId) {

        dossierTemplate.setId(dossierTemplateEntity.getId());
        if (!dossierTemplateEntity.getName().equalsIgnoreCase(dossierTemplate.getName())) {
            this.validateDossierTemplateName(dossierTemplate);
        }

        BeanUtils.copyProperties(dossierTemplate, dossierTemplateEntity, "id", "modifiedBy", "dateModified", "validFrom", "validTo", "downloadFileTypes");
        OffsetDateTime now = OffsetDateTime.now().truncatedTo(ChronoUnit.MILLIS);
        dossierTemplateEntity.setDateModified(now);
        dossierTemplateEntity.setModifiedBy(userId);
        dossierTemplateEntity.setValidFrom(dossierTemplate.getValidFrom() == null ? dossierTemplateEntity.getValidFrom() : dossierTemplate.getValidFrom());
        dossierTemplateEntity.setValidTo(dossierTemplate.getValidTo() == null ? dossierTemplateEntity.getValidTo() : dossierTemplate.getValidTo());
        dossierTemplateEntity.setLayoutParsingType(layoutParsingTypeProvider.deferFromCurrentApplicationType());

        dossierTemplateEntity.setDownloadFileTypes(dossierTemplate.getDownloadFileTypes() == null || dossierTemplate.getDownloadFileTypes()
                .isEmpty() ? dossierTemplateEntity.getDownloadFileTypes() : dossierTemplate.getDownloadFileTypes());

    }


    private void setColors(String dossierTemplateId, Colors requestedColors) {
        // set colors
        if (requestedColors != null) {
            ColorsEntity colorsEntity = convert(requestedColors, ColorsEntity.class);
            colorsEntity.setDossierTemplateId(dossierTemplateId);
            colorsService.saveColors(colorsEntity);
        }
    }


    private void updateDossierStates(ImportTemplateResult request, String dossierTemplateId) {

        List<DossierStatusInfo> currentStates = dossierStatusPersistenceService.getAllDossierStatusForTemplate(dossierTemplateId);

        Set<String> currentStatesId = currentStates.stream()
                .map(DossierStatusInfo::getId)
                .collect(Collectors.toSet());
        Set<String> stateIdsAdded = new HashSet<>();
        var stateToEntityMap = currentStates.stream()
                .collect(Collectors.toMap(DossierStatusInfo::getName, Function.identity()));
        for (var state : request.getDossierStatusInfos()) {
            state.setDossierTemplateId(dossierTemplateId);
            String stateId;
            if (currentStatesId.contains(state.getId())) { //check by id
                stateId = state.getId();
            } else if (!stateToEntityMap.isEmpty() && stateToEntityMap.get(state.getName()) != null) { //check by name
                state.setId(stateToEntityMap.get(state.getName()).getId()); // force the id found to update the dossier status
            } else { // add the state, no match was found
                state.setId(null); // force null to create the dossier status
            }
            stateId = this.updateDossierStatus(dossierTemplateId, state).getId();
            stateIdsAdded.add(stateId);

        }
        // remove additional states
        Set<String> statesToRemove = currentStatesId.stream()
                .filter(t -> !stateIdsAdded.contains(t))
                .collect(Collectors.toSet());
        statesToRemove.forEach(stateId -> dossierStatusPersistenceService.deleteDossierStatus(stateId, null));
    }


    private void updateDossierAttributes(ImportTemplateResult request, String dossierTemplateId, TemplateImportInfo templateImportInfo) {

        List<DossierAttributeConfigEntity> currentFileAttributes = dossierAttributeConfigPersistenceService.getDossierAttributes(dossierTemplateId);
        Set<String> currentDossierAttributeIds = currentFileAttributes.stream()
                .map(DossierAttributeConfigEntity::getId)
                .collect(Collectors.toSet());
        Set<String> dossierAttributeIdsAdded = new HashSet<>();

        request.getDossierAttributesConfigs()
                .forEach(da -> {
                    String initialId = da.getId();
                    da.setId(dossierAttributeConfigPersistenceService.getDossierAttributeByIdOrName(dossierTemplateId, da.getId(), da.getLabel()));
                    DossierAttributeConfigEntity result = dossierAttributeConfigPersistenceService.addOrUpdateDossierAttribute(dossierTemplateId,
                                                                                                                               convert(da, DossierAttributeConfigEntity.class));
                    dossierAttributeIdsAdded.add(result.getId());
                    templateImportInfo.getIdMapping().put(initialId, result.getId());
                });
        // remove existing dossier attributes and not included in archive
        Set<String> attributesToRemove = currentDossierAttributeIds.stream()
                .filter(da -> !dossierAttributeIdsAdded.contains(da))
                .collect(Collectors.toSet());
        attributesToRemove.forEach(dossierAttributeConfigPersistenceService::deleteDossierAttribute);
    }


    private void updateFileAttributes(ImportTemplateResult request, String dossierTemplateId, TemplateImportInfo templateImportInfo) {

        List<FileAttributeConfigEntity> currentFileAttributes = fileAttributeConfigPersistenceService.getFileAttributes(dossierTemplateId);
        Set<String> currentFileAttributeIds = currentFileAttributes.stream()
                .map(FileAttributeConfigEntity::getId)
                .collect(Collectors.toSet());
        Set<String> fileAttributeIdsAdded = new HashSet<>();
        request.getFileAttributesConfigs()
                .forEach(fa -> {
                    String initialId = fa.getId();
                    //check by id or by dossierTemplate and label and set the corresponding id if found
                    fa.setId(fileAttributeConfigPersistenceService.getFileAttributeByIdOrName(dossierTemplateId, fa.getId(), fa.getLabel()));
                    FileAttributeConfigEntity fileAttributeAdded = fileAttributeConfigPersistenceService.addOrUpdateFileAttribute(dossierTemplateId,
                                                                                                                                  convert(fa, FileAttributeConfigEntity.class));
                    fileAttributeIdsAdded.add(fileAttributeAdded.getId());
                    if (fa.isPrimaryAttribute()) {
                        fileAttributeConfigPersistenceService.updatePrimaryAttribute(fileAttributeAdded.getId());
                    }
                    templateImportInfo.getIdMapping().put(initialId, fa.getId());
                });
        // remove existing file attributes and not included in archive
        Set<String> attributesToRemove = currentFileAttributeIds.stream()
                .filter(fa -> !fileAttributeIdsAdded.contains(fa))
                .collect(Collectors.toSet());
        attributesToRemove.forEach(fileAttributeConfigPersistenceService::deleteFileAttribute);
    }


    private void updateComponents(ImportTemplateResult request, String dossierTemplateId) {

        List<ComponentDefinitionEntity> existingComponentDefinitions = componentDefinitionPersistenceService.findComponentsByDossierTemplateId(dossierTemplateId);
        existingComponentDefinitions.forEach(componentDefinition -> componentDefinitionPersistenceService.delete(componentDefinition.getId()));

        request.getComponentDefinitions()
                .forEach(componentDefinition -> {
                    componentDefinition.setDossierTemplateId(dossierTemplateId);
                    componentDefinitionPersistenceService.insert(MagicConverter.convert(componentDefinition, ComponentDefinitionEntity.class));
                });
    }


    private void deleteComponents(List<ComponentDefinitionEntity> componentDefinitionEntities) {

        componentDefinitionEntities.forEach(componentDefinition -> componentDefinitionPersistenceService.delete(componentDefinition.getId()));
    }


    private void validateDossierTemplateName(DossierTemplate dossierTemplateMeta) {

        int nameSuffix = 0;
        String dossierTemplateName = dossierTemplateMeta.getName();
        while (dossierTemplatePersistenceService.isDossierTemplateNameNotUnique(dossierTemplateMeta.getName(), dossierTemplateMeta.getId())) {
            if (nameSuffix == 0) {
                dossierTemplateMeta.setName("Copy of " + dossierTemplateName);
            } else {
                dossierTemplateMeta.setName("Copy of " + dossierTemplateName + " - " + nameSuffix);
            }
            nameSuffix++;
        }
    }


    private DossierStatusEntity updateDossierStatus(String dossierTemplateId, DossierStatusInfo state) {

        var dossierStatusRequest = CreateOrUpdateDossierStatusRequest.builder()
                .dossierStatusId(state.getId())
                .name(state.getName())
                .description(state.getDescription())
                .color(state.getColor())
                .dossierTemplateId(dossierTemplateId)
                .rank(state.getRank())
                .build();

        return dossierStatusPersistenceService.createOrUpdateDossierStatus(dossierStatusRequest);
    }

}
