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

import java.nio.file.Files;
import java.nio.file.Path;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.FileSystemUtils;

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.TypeEntity;
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.DossierTemplateEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.FileAttributeConfigEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.ReportTemplateEntity;
import com.iqser.red.service.persistence.management.v1.processor.exception.ConflictException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.model.ComponentMapping;
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.DictionaryPersistenceService;
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.EntryPersistenceService;
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.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.ComponentDefinitionAddRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.CloneDossierTemplateRequest;
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.dossier.CreateOrUpdateDossierStatusRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.legalbasis.LegalBasis;
import com.iqser.red.storage.commons.service.StorageService;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.knecon.fforesight.tenantcommons.TenantContext;

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 DossierTemplateCloneService {

    DossierTemplateRepository dossierTemplateRepository;
    LegalBasisMappingPersistenceService legalBasisMappingPersistenceService;
    RulesPersistenceService rulesPersistenceService;
    DateFormatsPersistenceService dateFormatsPersistenceService;
    DossierTemplatePersistenceService dossierTemplatePersistenceService;
    DossierAttributeConfigPersistenceService dossierAttributeConfigPersistenceService;
    DictionaryPersistenceService dictionaryPersistenceService;
    EntryPersistenceService entryPersistenceService;
    FileAttributeConfigPersistenceService fileAttributeConfigPersistenceService;
    ReportTemplatePersistenceService reportTemplatePersistenceService;
    ColorsService colorsService;
    StorageService storageService;
    DossierStatusPersistenceService dossierStatusPersistenceService;
    WatermarkService watermarkService;
    FileManagementStorageService fileManagementStorageService;
    ComponentMappingService componentMappingService;
    ComponentDefinitionPersistenceService componentDefinitionPersistenceService;


    public DossierTemplateEntity cloneDossierTemplate(String dossierTemplateId, CloneDossierTemplateRequest cloneDossierTemplateRequest) {

        if (StringUtils.isEmpty(cloneDossierTemplateRequest.getName())) {
            throw new ConflictException("DossierTemplate name must be set");
        }
        dossierTemplatePersistenceService.validateDossierTemplate(cloneDossierTemplateRequest.getName(), cloneDossierTemplateRequest.getDescription());
        dossierTemplatePersistenceService.validateDossierTemplateNameIsUnique(cloneDossierTemplateRequest.getName());

        Optional<DossierTemplateEntity> dossierTemplate = dossierTemplateRepository.findById(dossierTemplateId);
        if (dossierTemplate.isEmpty()) {
            throw new NotFoundException(String.format(DossierTemplatePersistenceService.DOSSIER_TEMPLATE_NOT_FOUND_MESSAGE, dossierTemplateId));

        }

        DossierTemplateEntity clonedDossierTemplate = DossierTemplateEntity.copyDossierTemplateEntityWithoutChildEntities(dossierTemplate.get());
        dossierTemplatePersistenceService.validateDossierTemplateForDuplicateRanks(dossierTemplateId);
        OffsetDateTime now = OffsetDateTime.now().truncatedTo(ChronoUnit.MILLIS);
        clonedDossierTemplate.setId(UUID.randomUUID().toString());
        clonedDossierTemplate.setName(cloneDossierTemplateRequest.getName());
        if (cloneDossierTemplateRequest.getDescription() != null && !cloneDossierTemplateRequest.getDescription().isEmpty()) {
            clonedDossierTemplate.setDescription(cloneDossierTemplateRequest.getDescription());
        }
        clonedDossierTemplate.setDateAdded(now);
        clonedDossierTemplate.setDateModified(now);
        clonedDossierTemplate.setCreatedBy(cloneDossierTemplateRequest.getCloningUserId());
        clonedDossierTemplate.setModifiedBy(cloneDossierTemplateRequest.getCloningUserId());
        if (cloneDossierTemplateRequest.getValidFrom() != null) {
            clonedDossierTemplate.setValidFrom(cloneDossierTemplateRequest.getValidFrom());
        }
        if (cloneDossierTemplateRequest.getValidTo() != null) {
            clonedDossierTemplate.setValidTo(cloneDossierTemplateRequest.getValidTo());
        }

        //set rules
        cloneRules(dossierTemplateId, clonedDossierTemplate.getId());
        //set date formats
        cloneDateFormats(dossierTemplateId, clonedDossierTemplate.getId());
        //set legal basis
        cloneLegalBasisMapping(dossierTemplateId, clonedDossierTemplate.getId());

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

        dossierTemplatePersistenceService.insertDossierTemplate(clonedDossierTemplate);

        //set dictionaries
        cloneDictionariesWithEntries(dossierTemplateId, clonedDossierTemplate.getId());

        //set dossier attributes
        cloneDossierAttributesConfig(dossierTemplateId, clonedDossierTemplate.getId());

        //set file attributes
        cloneFileAttributesGeneralConfig(dossierTemplateId, clonedDossierTemplate.getId());
        cloneFileAttributesConfig(dossierTemplateId, clonedDossierTemplate.getId());

        //set report templates
        cloneReportTemplates(dossierTemplateId, clonedDossierTemplate.getId());

        //set colors
        cloneColors(dossierTemplateId, clonedDossierTemplate.getId());

        //set dossier status
        cloneDossierStates(dossierTemplateId, clonedDossierTemplate.getId());

        // set the watermark configurations
        cloneWatermarks(dossierTemplateId, clonedDossierTemplate.getId());

        cloneComponentMappings(dossierTemplateId, clonedDossierTemplate.getId());

        // set components
        cloneComponents(dossierTemplateId, clonedDossierTemplate.getId());

        return clonedDossierTemplate;
    }


    private void cloneComponents(String dossierTemplate, String clonedDossierTemplateId) {

        List<ComponentDefinitionEntity> componentDefinitionEntities = componentDefinitionPersistenceService.findComponentsByDossierTemplateId(dossierTemplate);

        for (ComponentDefinitionEntity componentDefinitionEntity : componentDefinitionEntities) {
            ComponentDefinitionAddRequest componentDefinitionAddRequest = ComponentDefinitionAddRequest.builder()
                    .description(componentDefinitionEntity.getDescription())
                    .technicalName(componentDefinitionEntity.getTechnicalName())
                    .displayName(componentDefinitionEntity.getDisplayName())
                    .build();
            componentDefinitionPersistenceService.insert(componentDefinitionAddRequest, componentDefinitionEntity.getRank(), clonedDossierTemplateId);
        }
    }


    private void cloneRules(String dossierTemplateId, String clonedDossierTemplateId) {

        for (RuleFileType ruleFileType : RuleFileType.values()) {
            var rulesOptional = rulesPersistenceService.getRules(dossierTemplateId, ruleFileType);

            if (rulesOptional.isEmpty()) {
                log.debug("No {} rules found, will not be cloned!", ruleFileType);
            } else {
                rulesPersistenceService.setRules(rulesOptional.get().getValue(), clonedDossierTemplateId, ruleFileType);
            }

        }
    }


    private void cloneDateFormats(String dossierTemplateId, String clonedDossierTemplateId) {

        var optionalDateFormats = dateFormatsPersistenceService.getDateFormats(dossierTemplateId);

        if (optionalDateFormats.isEmpty()) {
            log.debug("No date formats found, will not be cloned!");
        } else {
            dateFormatsPersistenceService.setDateFormats(optionalDateFormats.get().getValue(), clonedDossierTemplateId);
        }

    }


    private void cloneLegalBasisMapping(String dossierTemplateId, String clonedDossierTemplateId) {

        legalBasisMappingPersistenceService.setLegalBasisMapping(clonedDossierTemplateId,
                                                                 MagicConverter.convert(legalBasisMappingPersistenceService.getLegalBasisMapping(dossierTemplateId),
                                                                                        LegalBasis.class));
    }


    @SneakyThrows
    private void cloneComponentMappings(String dossierTemplateId, String clonedDossierTemplateId) {

        Path dir = Files.createTempDirectory("componentMappingsForClone");
        List<ComponentMapping> componentMappings = componentMappingService.getMappingFilesByDossierTemplateId(dossierTemplateId, dir);
        for (ComponentMapping componentMapping : componentMappings) {
            componentMappingService.create(clonedDossierTemplateId,
                                           componentMapping.metaData().getName(),
                                           componentMapping.metaData().getFileName(),
                                           componentMapping.metaData().getDelimiter(),
                                           componentMapping.metaData().getEncoding(),
                                           componentMapping.file(),
                                           componentMapping.metaData().getQuoteChar());
        }
        FileSystemUtils.deleteRecursively(dir);
    }


    private void cloneDictionariesWithEntries(String dossierTemplateId, String clonedDossierTemplateId) {

        var types = dictionaryPersistenceService.getAllTypesForDossierTemplate(dossierTemplateId, false);
        for (TypeEntity t : types) {
            var type = dictionaryPersistenceService.addType(t.getType(),
                                                            clonedDossierTemplateId,
                                                            t.getHexColor(),
                                                            t.getRecommendationHexColor(),
                                                            t.getSkippedHexColor(),
                                                            t.getRank(),
                                                            t.isHint(),
                                                            t.isCaseInsensitive(),
                                                            t.isRecommendation(),
                                                            t.getDescription(),
                                                            t.isAiCreationEnabled(),
                                                            t.getAiDescription(),
                                                            t.isAddToDictionaryAction(),
                                                            t.getLabel(),
                                                            null,
                                                            t.isHasDictionary(),
                                                            t.isSystemManaged(),
                                                            t.isAutoHideSkipped(),
                                                            t.isDossierDictionaryOnly(),
                                                            t.isExperimental());
            entryPersistenceService.cloneEntries(t.getId(), type.getId());
        }
    }


    private void cloneDossierAttributesConfig(String dossierTemplateId, String clonedDossierTemplateId) {

        List<DossierAttributeConfigEntity> dacList = new ArrayList<>();
        var dossierAttributesConfig = dossierAttributeConfigPersistenceService.getDossierAttributes(dossierTemplateId);
        dossierAttributesConfig.forEach(dossierAttributeConfigEntity -> {
            var dac = DossierAttributeConfigEntity.builder()
                    .label(dossierAttributeConfigEntity.getLabel())
                    .editable(dossierAttributeConfigEntity.isEditable())
                    .placeholder(dossierAttributeConfigEntity.getPlaceholder())
                    .type(dossierAttributeConfigEntity.getType())
                    .build();
            dacList.add(dac);
        });
        dossierAttributeConfigPersistenceService.setDossierAttributesConfig(clonedDossierTemplateId, dacList);
    }


    private void cloneFileAttributesGeneralConfig(String dossierTemplateId, String clonedDossierTemplateId) {

        try {
            var fileAttributesGeneralConfig = fileAttributeConfigPersistenceService.getFileAttributesGeneralConfiguration(dossierTemplateId);
            var fagc = FileAttributesGeneralConfigurationEntity.builder()
                    .dossierTemplateId(clonedDossierTemplateId)
                    .delimiter(fileAttributesGeneralConfig.getDelimiter())
                    .encoding(fileAttributesGeneralConfig.getEncoding())
                    .filenameMappingColumnHeaderName(fileAttributesGeneralConfig.getFilenameMappingColumnHeaderName())
                    .build();
            fileAttributeConfigPersistenceService.setFileAttributesGeneralConfig(clonedDossierTemplateId, fagc);
        } catch (NotFoundException e) {
            log.debug("No file attribute config found for cloning {}", dossierTemplateId);
        }
    }


    private void cloneFileAttributesConfig(String dossierTemplateId, String clonedDossierTemplateId) {

        List<FileAttributeConfigEntity> facList = new ArrayList<>();
        var fileAttributesConfig = fileAttributeConfigPersistenceService.getFileAttributes(dossierTemplateId);
        fileAttributesConfig.forEach(fileAttributeConfigEntity -> {
            var fac = FileAttributeConfigEntity.builder()
                    .csvColumnHeader(fileAttributeConfigEntity.getCsvColumnHeader())
                    .label(fileAttributeConfigEntity.getLabel())
                    .primaryAttribute(fileAttributeConfigEntity.isPrimaryAttribute())
                    .editable(fileAttributeConfigEntity.isEditable())
                    .filterable(fileAttributeConfigEntity.isFilterable())
                    .displayedInFileList(fileAttributeConfigEntity.isDisplayedInFileList())
                    .placeholder(fileAttributeConfigEntity.getPlaceholder())
                    .type(fileAttributeConfigEntity.getType())
                    .build();
            facList.add(fac);
        });
        fileAttributeConfigPersistenceService.setFileAttributesConfig(clonedDossierTemplateId, facList);
    }


    @SneakyThrows
    private void cloneReportTemplates(String dossierTemplateId, String clonedDossierTemplateId) {

        var reportTemplates = reportTemplatePersistenceService.findByDossierTemplateId(dossierTemplateId);
        for (ReportTemplateEntity rte : reportTemplates) {
            if (storageService.objectExists(TenantContext.getTenantId(), rte.getStorageId())) {
                var storedReportTemplate = fileManagementStorageService.getObject(TenantContext.getTenantId(), rte.getStorageId());
                String storageId = clonedDossierTemplateId + "/" + rte.getFileName();
                String templateId = UUID.randomUUID().toString();
                storageService.storeObject(TenantContext.getTenantId(), storageId, storedReportTemplate);
                storedReportTemplate.close();
                reportTemplatePersistenceService.insert(clonedDossierTemplateId, templateId, storageId, rte.getFileName(), rte.isActiveByDefault(), rte.isMultiFileReport());
            } else {
                log.debug("Deleted Report-template {} will not be cloned", rte.getTemplateId());
            }
        }
    }


    private void cloneColors(String dossierTemplateId, String clonedDossierTemplateId) {

        var colors = colorsService.getColors(dossierTemplateId);
        var clonedColors = new ColorsEntity();
        BeanUtils.copyProperties(colors, clonedColors);
        clonedColors.setDossierTemplateId(clonedDossierTemplateId);
        colorsService.saveColors(clonedColors);
    }


    private void cloneDossierStates(String dossierTemplateId, String clonedDossierTemplateId) {

        var states = dossierStatusPersistenceService.getAllDossierStatusForTemplate(dossierTemplateId);
        for (var state : states) {
            var clonedState = CreateOrUpdateDossierStatusRequest.builder()
                    .name(state.getName())
                    .description(state.getDescription())
                    .color(state.getColor())
                    .dossierTemplateId(clonedDossierTemplateId)
                    .rank(state.getRank())
                    .build();
            dossierStatusPersistenceService.createOrUpdateDossierStatus(clonedState);
        }
    }


    private void cloneWatermarks(String dossierTemplateId, String clonedDossierTemplateId) {

        var watermarkList = watermarkService.getWatermarksForDossierTemplateId(dossierTemplateId);
        for (var watermark : watermarkList) {
            var clonedWatermark = new WatermarkModel();
            BeanUtils.copyProperties(watermark, clonedWatermark, "id", "dossierTemplateId");
            clonedWatermark.setDossierTemplateId(clonedDossierTemplateId);
            watermarkService.createOrUpdateWatermark(clonedWatermark);
        }
    }

}
