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

import static com.iqser.red.service.persistence.management.v1.processor.service.ReanalysisRequiredStatusService.VersionType.COMPONENT_RULES;
import static com.iqser.red.service.persistence.management.v1.processor.service.ReanalysisRequiredStatusService.VersionType.DICTIONARY;
import static com.iqser.red.service.persistence.management.v1.processor.service.ReanalysisRequiredStatusService.VersionType.LEGAL_BASIS;
import static com.iqser.red.service.persistence.management.v1.processor.service.ReanalysisRequiredStatusService.VersionType.RULES;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.stereotype.Service;

import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierEntity;
import com.iqser.red.service.persistence.management.v1.processor.exception.DossierNotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DictionaryPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.LegalBasisMappingPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.RulesPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.ProcessingStatus;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@RequiredArgsConstructor
public class ReanalysisRequiredStatusService {

    private final DictionaryPersistenceService dictionaryPersistenceService;
    private final RulesPersistenceService rulesPersistenceService;
    private final DossierPersistenceService dossierPersistenceService;
    private final LegalBasisMappingPersistenceService legalBasisMappingPersistenceService;


    public FileModel enhanceFileStatusWithAnalysisRequirements(FileModel fileModel) {

        return enhanceFileStatusWithAnalysisRequirements(Collections.singletonList(fileModel), false).iterator().next();
    }


    public List<FileModel> enhanceFileStatusWithAnalysisRequirements(List<FileModel> fileModels, boolean ignoreProcessingStates) {

        Map<String, Map<VersionType, Long>> dossierTemplateVersionMap = new HashMap<>();
        Map<String, Long> dossierVersionMap = new HashMap<>();
        Map<String, DossierEntity> dossierMap = new HashMap<>();
        fileModels.forEach(entry -> {
            var analysisRequiredResult = computeAnalysisRequired(entry, ignoreProcessingStates, dossierTemplateVersionMap, dossierVersionMap, dossierMap);
            entry.setReanalysisRequired(analysisRequiredResult.isReanalysisRequired());
            entry.setFullAnalysisRequired(analysisRequiredResult.isFullAnalysisRequired());
        });

        return fileModels;
    }


    private AnalysisRequiredResult computeAnalysisRequired(FileModel fileStatus,
                                                           boolean ignoreProcessingStates,
                                                           Map<String, Map<VersionType, Long>> dossierTemplateVersionMap,
                                                           Map<String, Long> dossierVersionMap,
                                                           Map<String, DossierEntity> dossierMap) {

        // enhance with dossierTemplateId
        DossierEntity dossier;
        try {
            dossier = dossierMap.computeIfAbsent(fileStatus.getDossierId(), k -> dossierPersistenceService.findByDossierId(fileStatus.getDossierId()));
        } catch (DossierNotFoundException e) {
            log.info("Dossier {} was not found, thus analysis is not required", fileStatus.getDossierId());
            return new AnalysisRequiredResult(false, false);
        }

        fileStatus.setDossierArchived(dossier.getArchivedTime() != null);
        fileStatus.setDossierTemplateId(dossier.getDossierTemplateId());
        fileStatus.setDossierStatusId(dossier.getDossierStatusId());

        if (dossier.getSoftDeletedTime() != null || dossier.getHardDeletedTime() != null || dossier.getArchivedTime() != null) {
            log.info("Dossier {} is deleted, thus analysis is not required", fileStatus.getDossierId());
            return new AnalysisRequiredResult(false, false);
        }

        if (ProcessingStatus.ERROR.equals(fileStatus.getProcessingStatus()) && !ignoreProcessingStates) {
            return new AnalysisRequiredResult(false, true);
        }

        if (ProcessingStatus.PROCESSED.equals(fileStatus.getProcessingStatus()) || ProcessingStatus.PRE_PROCESSED.equals(fileStatus.getProcessingStatus()) || ignoreProcessingStates) {

            switch (fileStatus.getWorkflowStatus()) {
                case NEW:
                case UNDER_REVIEW:
                case UNDER_APPROVAL:
                    if (fileStatus.getLastProcessed() == null) {
                        return new AnalysisRequiredResult(false, true);
                    }
                    if (fileStatus.getLastFileAttributeChange() != null && fileStatus.getLastProcessed().isBefore(fileStatus.getLastFileAttributeChange())) {
                        return new AnalysisRequiredResult(true, false);
                    } else {
                        return requiresReanalysisBasedOnVersionDifference(fileStatus, dossier, dossierTemplateVersionMap, dossierVersionMap);
                    }
                default:
                    return new AnalysisRequiredResult(false, false);
            }
        }
        return new AnalysisRequiredResult(false, false);

    }


    private AnalysisRequiredResult requiresReanalysisBasedOnVersionDifference(FileModel fileStatus,
                                                                              DossierEntity dossier,
                                                                              Map<String, Map<VersionType, Long>> dossierTemplateVersionMap,
                                                                              Map<String, Long> dossierVersionMap) {

        // get relevant versions
        var dossierTemplateVersions = dossierTemplateVersionMap.computeIfAbsent(dossier.getDossierTemplateId(), k -> buildVersionData(dossier.getDossierTemplateId()));
        var dossierDictionaryVersion = dossierVersionMap.computeIfAbsent(fileStatus.getDossierId(), k -> getDossierVersionData(fileStatus.getDossierId()));

        // compute matches
        var rulesVersionMatches = fileStatus.getRulesVersion() == dossierTemplateVersions.getOrDefault(RULES, -1L);
        var componentRulesVersionMatches = fileStatus.getComponentRulesVersion() == dossierTemplateVersions.getOrDefault(COMPONENT_RULES, -1L);
        var dictionaryVersionMatches = fileStatus.getDictionaryVersion() == dossierTemplateVersions.getOrDefault(DICTIONARY, -1L);
        var legalBasisVersionMatches = fileStatus.getLegalBasisVersion() == dossierTemplateVersions.getOrDefault(LEGAL_BASIS, -1L);
        var dossierDictionaryVersionMatches = Math.max(fileStatus.getDossierDictionaryVersion(), 0) == dossierDictionaryVersion;

        var reanalysisRequired = !dictionaryVersionMatches || !dossierDictionaryVersionMatches;
        var fullAnalysisRequired = !rulesVersionMatches || !componentRulesVersionMatches || !legalBasisVersionMatches;
        if (reanalysisRequired || fullAnalysisRequired) {
            log.info(
                    "For file: {} analysis is required because -> ruleVersionMatches: {}/{}, componentRuleVersionMatches {}/{}, dictionaryVersionMatches: {}/{}, legalBasisVersionMatches: {}/{}, dossierDictionaryVersionMatches: {}/{}",
                    fileStatus.getFilename(),
                    fileStatus.getRulesVersion(),
                    dossierTemplateVersions.getOrDefault(RULES, -1L),
                    fileStatus.getComponentRulesVersion(),
                    dossierTemplateVersions.getOrDefault(COMPONENT_RULES, -1L),
                    fileStatus.getDictionaryVersion(),
                    dossierTemplateVersions.getOrDefault(DICTIONARY, -1L),
                    fileStatus.getLegalBasisVersion(),
                    dossierTemplateVersions.getOrDefault(LEGAL_BASIS, -1L),
                    Math.max(fileStatus.getDossierDictionaryVersion(), 0),
                    dossierDictionaryVersion);
        }
        return new AnalysisRequiredResult(reanalysisRequired, fullAnalysisRequired);

    }


    private Map<VersionType, Long> buildVersionData(String dossierTemplateId) {

        var versions = new HashMap<VersionType, Long>();

        versions.put(RULES, rulesPersistenceService.getVersion(dossierTemplateId, RuleFileType.ENTITY));
        versions.put(COMPONENT_RULES, rulesPersistenceService.getVersion(dossierTemplateId, RuleFileType.COMPONENT));
        versions.put(DICTIONARY, dictionaryPersistenceService.getVersion(dossierTemplateId));
        versions.put(LEGAL_BASIS, legalBasisMappingPersistenceService.getVersion(dossierTemplateId));

        return versions;
    }


    private Long getDossierVersionData(String dossierId) {

        return dictionaryPersistenceService.getVersionForDossier(dossierId);
    }


    public FileModel enhanceFileStatusWithAnalysisRequirements(FileModel fileModel, boolean ignoreProcessingStates) {

        return enhanceFileStatusWithAnalysisRequirements(Collections.singletonList(fileModel), ignoreProcessingStates).iterator().next();
    }


    public List<FileModel> enhanceFileStatusWithAnalysisRequirements(List<FileModel> fileModels) {

        return enhanceFileStatusWithAnalysisRequirements(fileModels, false);
    }


    public enum VersionType {
        RULES,
        COMPONENT_RULES,
        DICTIONARY,
        LEGAL_BASIS
    }

    @Data
    @AllArgsConstructor
    public static class AnalysisRequiredResult {

        private boolean reanalysisRequired;
        private boolean fullAnalysisRequired;

    }

}
