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

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;

import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.BaseDictionaryEntry;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.TypeEntity;
import com.iqser.red.service.persistence.management.v1.processor.dataexchange.models.EntityTypeImportModel;
import com.iqser.red.service.persistence.management.v1.processor.service.DictionaryManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DictionaryPersistenceService;
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.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntryType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.Type;

import io.micrometer.common.KeyValue;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.annotation.Observed;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;

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

    public static final String NUMBER_OF_ENTITIES = "number of entities";
    DictionaryManagementService dictionaryManagementService;
    DictionaryPersistenceService dictionaryPersistenceService;
    EntryPersistenceService entryPersistenceService;
    DossierTemplatePersistenceService dossierTemplatePersistenceService;
    ObservationRegistry registry;


    @Observed(name = "EntityTypeImportService", contextualName = "update-entity-type")
    public void updateTypes(String dossierTemplateId, String dossierId, EntityTypeImportModel importModel) {

        enrichObservation(importModel);

        List<TypeEntity> currentTypes = dossierTemplatePersistenceService.getTypesForDossierTemplate(dossierTemplateId)
                .stream()
                .filter(t -> t.getDossierId() == null)
                .filter(t -> !t.isSystemManaged())
                .collect(Collectors.toList());

        Set<String> currentTypesId = currentTypes.stream()
                .map(TypeEntity::getId)
                .collect(Collectors.toSet());

        Set<String> typeIdsAdded = new HashSet<>();

        var typeToEntityMap = currentTypes.stream()
                .collect(Collectors.toMap(TypeEntity::getType, Function.identity()));

        for (var type : importModel.getTypes()) {

            type.setDossierTemplateId(dossierTemplateId);
            type.setDossierId(dossierId);

            String typeId;
            if (currentTypesId.contains(type.getId())) { //check by id
                typeId = type.getId();
                // type found, possible to be deleted, undelete it first before updating
                dictionaryPersistenceService.undeleteType(typeId);
                dictionaryManagementService.updateTypeValue(typeId, type);
            } else if (!typeToEntityMap.isEmpty() && typeToEntityMap.get(type.getType()) != null) { //check by name
                typeId = typeToEntityMap.get(type.getType()).getId();
                // type found, possible to be deleted, undelete it first before updating
                dictionaryPersistenceService.undeleteType(typeId);
                dictionaryManagementService.updateTypeValue(typeId, type);
            } else { // add the type, no match was found
                var returnedType = dictionaryManagementService.addType(type);
                typeId = returnedType.getTypeId();
            }


            // Setting dictionary version for an existing dossierTemplate can break the analysis dictionary logic,
            // when files have newer dictionary versions set in entity log. This will lead to endless analysis.
            // Do not call this:
            // dictionaryPersistenceService.setVersion(typeId, type.getVersion());

            typeIdsAdded.add(typeId);

            this.addEntries(importModel.getEntries(), importModel.getDeletedEntries(), typeId, type.getType(), DictionaryEntryType.ENTRY);
            this.addEntries(importModel.getFalsePositives(), importModel.getDeletedFalsePositives(), typeId, type.getType(), DictionaryEntryType.FALSE_POSITIVE);
            this.addEntries(importModel.getFalseRecommendations(), importModel.getDeletedFalseRecommendations(), typeId, type.getType(), DictionaryEntryType.FALSE_RECOMMENDATION);

        }
        // remove additional types and not included in the archive
        this.deleteTypes(currentTypes, typeIdsAdded);
    }


    @Observed(name = "EntityTypeImportService", contextualName = "import-entity-type")
    public void importEntityTypes(String dossierTemplateId, String dossierId, EntityTypeImportModel importModel) {

        if (importModel == null) {
            return;
        }
        enrichObservation(importModel);

        for (Type type : importModel.getTypes()) {

            type.setDossierTemplateId(dossierTemplateId);
            type.setDossierId(dossierId);

            Type returnedType = dictionaryManagementService.addType(type);

            this.addEntries(importModel.getEntries(), importModel.getDeletedEntries(), returnedType.getTypeId(), returnedType.getType(), DictionaryEntryType.ENTRY);
            this.addEntries(importModel.getFalsePositives(),
                            importModel.getDeletedFalsePositives(),
                            returnedType.getTypeId(),
                            returnedType.getType(),
                            DictionaryEntryType.FALSE_POSITIVE);
            this.addEntries(importModel.getFalseRecommendations(),
                            importModel.getDeletedFalseRecommendations(),
                            returnedType.getTypeId(),
                            returnedType.getType(),
                            DictionaryEntryType.FALSE_RECOMMENDATION);

            dictionaryPersistenceService.setVersions(returnedType.getTypeId(), type.getVersion(), type.getAiCreationVersion());
        }
    }

    public void addMissingSystemManagedTypesToImport(EntityTypeImportModel importModel, List<Type> systemManagedTypes) {
        Set<String> defaultSystemManagedTypesByName = systemManagedTypes.stream()
                .map(Type::getType)
                .collect(Collectors.toSet());
        importModel.getTypes()
                .stream()
                .filter(t -> defaultSystemManagedTypesByName.contains(t.getType()) || t.isSystemManaged())
                .forEach(t -> {
                    if (!t.isSystemManaged()) {
                        t.setSystemManaged(true);
                    }
                });

        Set<String> systemManagedTypesFromImport = importModel.getTypes()
                .stream()
                .filter(t -> defaultSystemManagedTypesByName.contains(t.getType()) || t.isSystemManaged())
                .map(Type::getType)
                .collect(Collectors.toSet());
        List<Type> systemManagedTypesNotIncludedInTheImport = systemManagedTypes.stream()
                .filter(type -> !systemManagedTypesFromImport.contains(type.getType()))
                .collect(Collectors.toList());
        importModel.getTypes().addAll(systemManagedTypesNotIncludedInTheImport);
    }

    public EntityTypeImportModel updateAndCleanSystemManagedFromImportTypes(EntityTypeImportModel importModel, String dossierTemplateId) {

        List<TypeEntity> currentSystemManagedTypes = dossierTemplatePersistenceService.getTypesForDossierTemplate(dossierTemplateId)
                .stream()
                .filter(t -> t.getDossierId() == null)
                .filter(t -> t.isSystemManaged())
                .collect(Collectors.toList());

        var currentSystemManagedTypesNameToTypeMap = currentSystemManagedTypes.stream()
                .collect(Collectors.toMap(TypeEntity::getType, Function.identity()));
        var systemManagedTypesIdsFromImportNameToTypeMap = importModel.getTypes()
                .stream()
                .filter(t -> currentSystemManagedTypesNameToTypeMap.keySet().contains(t.getType()) || t.isSystemManaged())
                .collect(Collectors.toMap(Type::getType, Function.identity()));

        if (!systemManagedTypesIdsFromImportNameToTypeMap.isEmpty()) {
            systemManagedTypesIdsFromImportNameToTypeMap.entrySet().forEach(entry -> {
                var typeId = currentSystemManagedTypesNameToTypeMap.get(entry.getKey()).getId();
                entry.getValue().setDossierTemplateId(dossierTemplateId);
                entry.getValue().setDossierId(null);
                dictionaryManagementService.updateTypeValue(typeId, entry.getValue());

                this.addEntries(importModel.getEntries(), importModel.getDeletedEntries(), typeId, entry.getKey(), DictionaryEntryType.ENTRY);
                this.addEntries(importModel.getFalsePositives(), importModel.getDeletedFalsePositives(), typeId, entry.getKey(), DictionaryEntryType.FALSE_POSITIVE);
                this.addEntries(importModel.getFalseRecommendations(), importModel.getDeletedFalseRecommendations(), typeId, entry.getKey(), DictionaryEntryType.FALSE_RECOMMENDATION);

            });
            //clean the import
            cleanSystemManagedFromImportTypes(importModel, systemManagedTypesIdsFromImportNameToTypeMap.keySet());
        }
        return importModel;
    }

    private void cleanSystemManagedFromImportTypes(EntityTypeImportModel importModel, Set<String> systemManagedTypesIdsFromImport) {
        importModel.getTypes().removeIf(t -> systemManagedTypesIdsFromImport.contains(t.getType()));

        importModel.getEntries().keySet().removeIf(systemManagedTypesIdsFromImport::contains);
        importModel.getFalsePositives().keySet().removeIf(systemManagedTypesIdsFromImport::contains);
        importModel.getFalseRecommendations().keySet().removeIf(systemManagedTypesIdsFromImport::contains);
        importModel.getDeletedEntries().keySet().removeIf(systemManagedTypesIdsFromImport::contains);
        importModel.getDeletedFalsePositives().keySet().removeIf(systemManagedTypesIdsFromImport::contains);
        importModel.getDeletedFalseRecommendations().keySet().removeIf(systemManagedTypesIdsFromImport::contains);
    }

    private void enrichObservation(EntityTypeImportModel importModel) {

        if (registry.getCurrentObservation() != null) {

            int numberOfEntities = 0;
            KeyValue existingValue = registry.getCurrentObservation().getContext().getHighCardinalityKeyValue(NUMBER_OF_ENTITIES);
            if (existingValue != null) {
                numberOfEntities = Integer.parseInt(existingValue.getValue());
            }
            registry.getCurrentObservation().highCardinalityKeyValue(NUMBER_OF_ENTITIES, String.valueOf(numberOfEntities + importModel.getTypes().size()));
        }
    }


    private void deleteTypes(List<TypeEntity> currentTypes, Set<String> typeIdsAdded) {

        Set<String> typesToRemove = currentTypes.stream()
                .filter(t -> !t.isDeleted()) // remove the ones already soft deleted
                .map(TypeEntity::getId)
                .filter(t -> !typeIdsAdded.contains(t)) // exclude the type ids already added from the import
                .collect(Collectors.toSet());
        typesToRemove.forEach(dictionaryManagementService::deleteType);
    }


    private void addEntries(Map<String, List<String>> entries, Map<String, List<String>> deleteEntries, String typeId, String typeName, DictionaryEntryType dictionaryType) {

        if (entries != null && !entries.isEmpty() && entries.get(typeName) != null && !entries.get(typeName).isEmpty()) {
            dictionaryManagementService.addEntries(typeId, entries.get(typeName), true, true, dictionaryType, true);
        } else { // no entries, delete current entries
            List<String> existing = entryPersistenceService.getEntries(typeId, dictionaryType, null)
                    .stream()
                    .map(BaseDictionaryEntry::getValue)
                    .collect(Collectors.toList());
            if (!existing.isEmpty()) {
                dictionaryManagementService.deleteEntries(typeId, existing, dictionaryType);
            }
        }

        if (deleteEntries != null && !deleteEntries.isEmpty() && deleteEntries.get(typeName) != null && !deleteEntries.get(typeName).isEmpty()) {
            dictionaryManagementService.deleteEntries(typeId, deleteEntries.get(typeName), dictionaryType);
        }

    }

}
