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

import static com.iqser.red.service.persistence.management.v1.processor.utils.TypeIdUtils.toTypeId;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.springframework.stereotype.Service;

import com.iqser.red.service.dictionarymerge.commons.DictionaryEntry;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.IdRemovalEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.ManualRedactionEntryEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.ManualResizeRedactionEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierEntity;
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.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.DictionaryManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.StopwordService;
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.FileStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.ResizeRedactionPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.AnnotationStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRequestWithAddToDictionary;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRequestWithRemoveFromDictionary;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.WorkflowStatus;
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.redactionlog.RedactionLogEntry;

import feign.FeignException;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import lombok.extern.slf4j.Slf4j;

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

    DictionaryManagementService dictionaryManagementService;
    DictionaryPersistenceService dictionaryPersistenceService;
    FileStatusPersistenceService fileStatusPersistenceService;
    ResizeRedactionPersistenceService resizeRedactionPersistenceService;
    DossierPersistenceService dossierPersistenceService;
    StopwordService stopwordService;


    public Set<String> handleAddToDictionaryAndReturnModifiedTypeIds(String fileId, String value, ManualRequestWithAddToDictionary manualRequestWithAddToDictionary) {

        if (!manualRequestWithAddToDictionary.isAddToDictionary()) {
            return Collections.emptySet();
        }
        Set<String> typeIdsOfModifiedDictionaries = new HashSet<>();
        if (manualRequestWithAddToDictionary.isAddToAllDossiers()) {
            return addToDossierTemplateDictionary(fileId, value, manualRequestWithAddToDictionary, typeIdsOfModifiedDictionaries);
        }
        return addToDossierDictionary(fileId, value, manualRequestWithAddToDictionary, typeIdsOfModifiedDictionaries);
    }


    private Set<String> addToDossierTemplateDictionary(String fileId,
                                                       String value,
                                                       ManualRequestWithAddToDictionary manualRequestWithAddToDictionary,
                                                       Set<String> typeIdsOfModifiedDictionaries) {

        List<DictionaryEntry> dictionaryEntriesToUnDelete = dictionaryManagementService.getAllEntriesInDossierTemplate(manualRequestWithAddToDictionary.getDossierTemplateTypeId(),
                value,
                manualRequestWithAddToDictionary.getDictionaryEntryType());
        dictionaryEntriesToUnDelete.forEach(entry -> {
            typeIdsOfModifiedDictionaries.add(entry.getTypeId());
            addToDictionary(entry.getTypeId(), value, manualRequestWithAddToDictionary.getDossierId(), fileId, manualRequestWithAddToDictionary.getDictionaryEntryType());
        });
        addToDictionary(manualRequestWithAddToDictionary.getDossierTemplateTypeId(),
                value,
                manualRequestWithAddToDictionary.getDossierId(),
                fileId,
                manualRequestWithAddToDictionary.getDictionaryEntryType());
        typeIdsOfModifiedDictionaries.add(manualRequestWithAddToDictionary.getDossierTemplateTypeId());
        return typeIdsOfModifiedDictionaries;
    }


    private Set<String> addToDossierDictionary(String fileId,
                                               String value,
                                               ManualRequestWithAddToDictionary manualRequestWithAddToDictionary,
                                               Set<String> typeIdsOfModifiedDictionaries) {

        String dossierDictionaryTypeId = manualRequestWithAddToDictionary.getDossierTemplateTypeId() + ":" + manualRequestWithAddToDictionary.getDossierId();
        addToDictionary(dossierDictionaryTypeId, value, manualRequestWithAddToDictionary.getDossierId(), fileId, manualRequestWithAddToDictionary.getDictionaryEntryType());
        typeIdsOfModifiedDictionaries.add(dossierDictionaryTypeId);
        return typeIdsOfModifiedDictionaries;
    }


    public Set<String> handleRemoveFromDictionaryAndReturnModifiedTypeIds(String fileId, ManualRequestWithRemoveFromDictionary manualRequestWithRemoveFromDictionary) {

        if (!manualRequestWithRemoveFromDictionary.isRemoveFromDictionary()) {
            return Collections.emptySet();
        }
        String dossierId = manualRequestWithRemoveFromDictionary.getDossierId();
        String dossierTemplateId = manualRequestWithRemoveFromDictionary.getDossierTemplateId();

        DictionaryEntryType dictionaryEntryType = manualRequestWithRemoveFromDictionary.getDictionaryEntryType();
        Set<String> typeIdsOfModifiedDictionaries = new HashSet<>();
        String dossierTemplateDictionaryTypeId = toTypeId(manualRequestWithRemoveFromDictionary.getTypeToRemove(), manualRequestWithRemoveFromDictionary.getDossierTemplateId());
        String dossierDictionaryTypeId = toTypeId(manualRequestWithRemoveFromDictionary.getTypeToRemove(),
                manualRequestWithRemoveFromDictionary.getDossierTemplateId(),
                manualRequestWithRemoveFromDictionary.getDossierId());

        if (manualRequestWithRemoveFromDictionary.isRemoveFromAllDossiers()) {

            var dictionaryEntriesToRemove = dictionaryManagementService.getAllEntriesInDossierTemplate(dossierTemplateDictionaryTypeId,
                    manualRequestWithRemoveFromDictionary.getValue(),
                    dictionaryEntryType);

            dictionaryEntriesToRemove.forEach(entry -> {
                typeIdsOfModifiedDictionaries.add(entry.getTypeId());
                removeFromDictionary(entry.getTypeId(), entry.getValue(), dossierId, fileId, dictionaryEntryType);
            });
        } else {
            removeFromDictionary(dossierDictionaryTypeId, manualRequestWithRemoveFromDictionary.getValue(), dossierId, fileId, dictionaryEntryType);
            typeIdsOfModifiedDictionaries.add(dossierDictionaryTypeId);
        }

        // This is needed to remove resizeRedactions with addToDictionary.
        removeResizeRedactionsWithAddToDictionary(dossierTemplateId, manualRequestWithRemoveFromDictionary.getValue());

        return typeIdsOfModifiedDictionaries;
    }


    public Set<String> updateDictionaryForResizeRedactions(String dossierId, String fileId, ManualResizeRedactionEntity resizeRedaction, RedactionLogEntry redactionLogEntry) {

        if (resizeRedaction.getUpdateDictionary() != null && resizeRedaction.getUpdateDictionary() && resizeRedaction.getStatus()
                .equals(AnnotationStatus.APPROVED) && (redactionLogEntry.isDictionaryEntry() || redactionLogEntry.isDossierDictionaryEntry())) {

            var dossier = dossierPersistenceService.findByDossierId(dossierId);

            var typeId = buildTypeId(redactionLogEntry.getType(), resizeRedaction, dossier);
            var newValue = resizeRedaction.getValue();
            var oldValue = redactionLogEntry.getValue();
            var dictionaryEntryType = getDictionaryEntryType(redactionLogEntry);

            boolean isShrinking = oldValue != null && oldValue.length() > newValue.length();

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

            if (isShrinking) {
                log.info("Remove old value '{}' from dictionary", oldValue);
                removeFromDictionary(typeId, oldValue, dossierId, fileId, dictionaryEntryType);
                typeIdsOfModifiedDictionaries.add(typeId);

                if (resizeRedaction.isAddToAllDossiers() && redactionLogEntry.isDictionaryEntry()) {
                    String dossierTemplateId = dossier.getDossierTemplateId();
                    var dossiersOfThisDossierTemplate = dossierPersistenceService.findAllDossiersForDossierTemplateId(dossierTemplateId);
                    var type = redactionLogEntry.getType();
                    dossiersOfThisDossierTemplate.forEach(dossierEntity -> {
                        var typeIdOfDossierEntity = toTypeId(type, dossierTemplateId, dossierEntity.getId());
                        removeFromDictionary(typeIdOfDossierEntity, oldValue, dossierId, fileId, dictionaryEntryType);
                        typeIdsOfModifiedDictionaries.add(typeIdOfDossierEntity);
                    });
                }

            }

            log.info("Add new value '{}' to dictionary", newValue);
            addToDictionary(typeId, newValue, dossierId, fileId, dictionaryEntryType);
            typeIdsOfModifiedDictionaries.add(typeId);

            return typeIdsOfModifiedDictionaries;
        }
        return Collections.emptySet();
    }


    private String buildTypeId(String type, ManualResizeRedactionEntity resizeRedaction, DossierEntity dossier) {

        if (resizeRedaction.isAddToAllDossiers()) {
            return toTypeId(type, dossier.getDossierTemplateId());
        } else {
            return toTypeId(type, dossier.getDossierTemplateId(), dossier.getId());
        }

    }


    private DictionaryEntryType getDictionaryEntryType(RedactionLogEntry redactionLogEntry) {

        if (redactionLogEntry.isRecommendation() && redactionLogEntry.isFalsePositive()) {
            return DictionaryEntryType.FALSE_RECOMMENDATION;
        } else if (redactionLogEntry.isFalsePositive()) {
            return DictionaryEntryType.FALSE_POSITIVE;
        } else {
            return DictionaryEntryType.ENTRY;
        }
    }


    public boolean revertAddToDictionary(String fileId, String dossierId, ManualRedactionEntryEntity manualRedactionToRevert) {

        if (!manualRedactionToRevert.isAddToDictionary()) {
            return false;
        }

        revertAddToDictionary(manualRedactionToRevert.getValue(),
                manualRedactionToRevert.getDictionaryEntryType(),
                fileId,
                dossierId,
                manualRedactionToRevert.getTypeIdsOfModifiedDictionaries());
        return true;
    }


    public void revertAddToDictionary(String value, DictionaryEntryType dictionaryEntryType, String fileId, String dossierId, Set<String> typeIdsOfModifiedDictionaries) {

        typeIdsOfModifiedDictionaries.forEach(typeId -> removeFromDictionary(typeId, value, dossierId, fileId, dictionaryEntryType));
    }


    public boolean revertRemoveFromDictionary(String value, String dossierId, String fileId, IdRemovalEntity idRemoval) {

        if (!idRemoval.isRemoveFromDictionary()) {
            return false;
        }

        revertRemoveFromDictionary(value, dossierId, fileId, idRemoval.getTypeIdsOfModifiedDictionaries());
        return true;
    }


    public void revertRemoveFromDictionary(String value, String dossierId, String fileId, Set<String> typeIdsOfModifiedDictionaries) {

        typeIdsOfModifiedDictionaries.forEach(changedTypeId -> addToDictionary(changedTypeId, value, dossierId, fileId, DictionaryEntryType.ENTRY));
    }


    public void validateDictionariesForDelete(ManualRequestWithRemoveFromDictionary request, String type, String dossierTemplateId) {

        if (request.isRemoveFromDictionary()) {
            var dossierTemplateTypeId = toTypeId(type, dossierTemplateId);
            dictionaryManagementService.validateAddRemoveToDossierTemplateDictionary(dossierTemplateTypeId, request.isRemoveFromDictionary(), request.isRemoveFromAllDossiers());
        }
    }


    public void validateDictionariesForAdd(ManualRequestWithAddToDictionary addRedactionRequest, String value) {

        if (addRedactionRequest.isAddToDictionary()) {
            if (addRedactionRequest.isAddToAllDossiers()) {
                // validate add to dossier template dictionaries
                dictionaryManagementService.validateAddRemoveToDossierTemplateDictionary(addRedactionRequest.getDossierTemplateTypeId());
            }
            validateDictionary(addRedactionRequest, value);
        }
    }


    private void validateDictionary(ManualRequestWithAddToDictionary addRedactionRequest, String value) {

        try {
            if (!addRedactionRequest.isForceAddToDictionary() && stopwordService.isStopword(value)) {
                throw new ConflictException("The entry you are trying to add is a stopword");
            }
            dictionaryPersistenceService.getType(addRedactionRequest.getDossierTemplateTypeId());
        } catch (NotFoundException e) {
            throw new BadRequestException("Invalid type: " + addRedactionRequest.getDossierTemplateTypeId());
        }
    }


    private void addToDictionary(String typeId, String value, String dossierId, String fileId, DictionaryEntryType dictionaryEntryType) {

        try {
            log.debug("Adding entry: {} to {} for {} / {}", value, typeId, dossierId, fileId);
            dictionaryManagementService.addEntries(typeId, List.of(value), false, false, dictionaryEntryType != null ? dictionaryEntryType : DictionaryEntryType.ENTRY);
        } catch (Exception e) {
            throw new BadRequestException(e.getMessage());
        }
    }


    private void removeFromDictionary(String typeId, String value, String dossierId, String fileId, DictionaryEntryType dictionaryEntryType) {

        try {
            log.debug("Deleting entries to {} for {} / {}", typeId, dossierId, fileId);
            dictionaryManagementService.deleteEntries(typeId, List.of(value), dictionaryEntryType != null ? dictionaryEntryType : DictionaryEntryType.ENTRY);
        } catch (FeignException e) {
            throw new BadRequestException(e.getMessage());
        }
    }


    private void removeResizeRedactionsWithAddToDictionary(String dossierTemplateId, String redactionLogEntryValue) {

        var resizeRedactionsWithSameValue = resizeRedactionPersistenceService.findByAnnotationStatusAndValue(AnnotationStatus.APPROVED, redactionLogEntryValue);
        resizeRedactionsWithSameValue.forEach(resizeRedaction -> {
            var file = fileStatusPersistenceService.getStatus(resizeRedaction.getId().getFileId());
            var dossierForResizeRedaction = dossierPersistenceService.findByDossierId(file.getDossierId());
            if (!file.getWorkflowStatus().equals(WorkflowStatus.APPROVED) && dossierTemplateId.equals(dossierForResizeRedaction.getDossierTemplateId())) {
                resizeRedactionPersistenceService.hardDelete(resizeRedaction.getId().getFileId(), resizeRedaction.getId().getAnnotationId());
            }
        });
    }

}
