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

import static com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.ManualRedactionMapper.getDictionaryEntryType;
import static com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter.convert;

import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.google.common.collect.Sets;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.iqser.red.service.persistence.management.v1.processor.configuration.MessagingConfiguration;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.AnnotationEntityId;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.IdRemovalEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.ManualForceRedactionEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.ManualLegalBasisChangeEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.annotations.ManualRecategorizationEntity;
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.annotations.RectangleEntity;
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.model.ManualChangesQueryOptions;
import com.iqser.red.service.persistence.management.v1.processor.service.CommentService;
import com.iqser.red.service.persistence.management.v1.processor.service.EntityLogMergeService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
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.AddRedactionPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.ForceRedactionPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.LegalBasisChangePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.RecategorizationPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.RemoveRedactionPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.ResizeRedactionPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.ManualRecategorizationMapper;
import com.iqser.red.service.persistence.management.v1.processor.utils.ManualRedactionEntryMapper;
import com.iqser.red.service.persistence.management.v1.processor.utils.ManualResizeRedactionMapper;
import com.iqser.red.service.persistence.management.v1.processor.utils.TypeIdUtils;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AnalyzeRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.MessageType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RequestEntryPair;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.Engine;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntryType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.Position;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.AddRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ForceRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.LegalBasisChangeRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualAnnotationResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactionEntrySource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ManualRedactions;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.RecategorizationRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.Rectangle;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.RemoveRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.ResizeRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.IdRemoval;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualForceRedaction;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualLegalBasisChange;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualRecategorization;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualRedactionEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualResizeRedaction;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier;
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.manual.AddRedactionRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ForceRedactionRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.LegalBasisChangeRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.RecategorizationRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.RemoveRedactionRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.ResizeRedactionRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.service.EntityLogMongoService;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.knecon.fforesight.tenantcommons.TenantContext;

import io.micrometer.observation.annotation.Observed;
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 ManualRedactionService {

    DossierPersistenceService dossierPersistenceService;
    AddRedactionPersistenceService addRedactionPersistenceService;
    RemoveRedactionPersistenceService removeRedactionPersistenceService;
    ForceRedactionPersistenceService forceRedactionPersistenceService;
    CommentService commentService;
    RecategorizationPersistenceService recategorizationPersistenceService;
    LegalBasisChangePersistenceService legalBasisChangePersistenceService;
    ResizeRedactionPersistenceService resizeRedactionPersistenceService;
    FileStatusService fileStatusService;
    ManualRedactionProviderService manualRedactionProviderService;
    EntityLogMongoService entityLogMongoService;
    HashFunction hashFunction = Hashing.murmur3_128();
    ManualRedactionDictionaryUpdateHandler manualRedactionDictionaryUpdateHandler;
    FileStatusPersistenceService fileStatusPersistenceService;
    ManualRedactionMapper manualRedactionMapper;
    RabbitTemplate rabbitTemplate;
    EntityLogMergeService entityLogMergeService;
    PendingEntryFactory pendingEntryFactory;


    @Transactional
    @Observed(name = "ManualRedactionService", contextualName = "add-manual-redaction")
    public List<ManualAnnotationResponse> addAddRedaction(String dossierId, String fileId, Set<AddRedactionRequestModel> addRedactionRequests, Dossier dossier) {

        if (addRedactionRequests.isEmpty()) {
            throw new BadRequestException("add redaction requests is empty for request: dossierId:" + dossierId + " fileId:" + fileId);
        }

        var response = new ArrayList<ManualAnnotationResponse>();
        List<ManualRedactionEntryEntity> manualRedactionEntryEntities = new ArrayList<>();
        List<AddRedactionRequest> requests = manualRedactionMapper.toAddRedactionRequestList(dossierId, addRedactionRequests, dossier);

        var dossierEntity = dossierPersistenceService.getAndValidateDossier(dossierId);
        FileModel fileStatus = fileStatusService.getStatus(fileId);

        for (AddRedactionRequest addRedactionRequest : requests) {

            validateRequest(addRedactionRequest);

            manualRedactionDictionaryUpdateHandler.validateDictionariesForAdd(addRedactionRequest, addRedactionRequest.getValue());
            validatePositions(fileId, addRedactionRequest);

            String annotationId = getNewAnnotationId();

            ManualRedactionEntryEntity manualRedactionEntryEntity = addRedactionPersistenceService.insert(fileId, annotationId, addRedactionRequest);
            manualRedactionEntryEntities.add(manualRedactionEntryEntity);

            Set<String> typeIdsOfModifiedDictionaries = manualRedactionDictionaryUpdateHandler.handleAddToDictionaryAndReturnModifiedTypeIds(fileId,
                                                                                                                                             addRedactionRequest.getValue(),
                                                                                                                                             addRedactionRequest);

            addRedactionPersistenceService.updateModifiedDictionaries(fileId, annotationId, !typeIdsOfModifiedDictionaries.isEmpty(), typeIdsOfModifiedDictionaries);

            EntityLogEntry entityLogEntry;
            if (!addRedactionRequest.isAddToDictionary() && !addRedactionRequest.isAddToAllDossiers()) {
                entityLogEntry = entityLogMergeService.buildEntityLogEntry(MagicConverter.convert(manualRedactionEntryEntity,
                                                                                                  ManualRedactionEntry.class,
                                                                                                  new ManualRedactionEntryMapper()),
                                                                           getAnalysisNumber(dossierId, fileId),
                                                                           dossierEntity);
            } else {
                entityLogEntry = pendingEntryFactory.buildAddToDictionaryEntry(MagicConverter.convert(manualRedactionEntryEntity,
                                                                                                      ManualRedactionEntry.class,
                                                                                                      new ManualRedactionEntryMapper()));
            }
            Long commentId = commentService.addCommentAndGetId(fileId, annotationId, addRedactionRequest.getComment(), addRedactionRequest.getUser());
            response.add(ManualAnnotationResponse.builder().annotationId(annotationId).commentId(commentId).entityLogEntry(entityLogEntry).build());
        }

        var localManualRedactionEntryEntities = manualRedactionEntryEntities.stream()
                .filter(manualRedactionEntry -> !manualRedactionEntry.isAddToDictionary() && !manualRedactionEntry.isAddToAllDossiers())
                .collect(Collectors.toList());

        if (!localManualRedactionEntryEntities.isEmpty() && fileStatus.isExcludedFromAutomaticAnalysis()) {
            ManualRedactions manualRedactions = ManualRedactions.builder().entriesToAdd(convertEntriesToAdd(localManualRedactionEntryEntities)).build();
            sendToSurroundingTextAnalysisQueue(fileId, dossierEntity, fileStatusService.getStatus(fileId), manualRedactions);
        } else {
            reprocess(dossierId, fileId);
        }
        fileStatusPersistenceService.setLastManualChangeDate(fileId, OffsetDateTime.now());

        return response;
    }


    private void validateRequest(AddRedactionRequest addRedactionRequest) {

        if (addRedactionRequest.getValue().length() > 4000) {
            throw new BadRequestException("Redaction value can not exceed 4000 characters");
        }
    }


    private Set<ManualRedactionEntry> convertEntriesToAdd(List<ManualRedactionEntryEntity> source) {

        return source.stream()
                .map(entry -> convert(entry, ManualRedactionEntry.class, new ManualRedactionEntryMapper()))
                .collect(Collectors.toSet());
    }


    @Transactional
    public List<ManualAnnotationResponse> addRemoveRedaction(String dossierId,
                                                             String fileId,
                                                             Set<RemoveRedactionRequestModel> removeRedactionRequests,
                                                             String dossierTemplateId,
                                                             boolean includeUnprocessed) {

        if (removeRedactionRequests.isEmpty()) {
            throw new BadRequestException("remove redaction requests is empty for request: dossierId:" + dossierId + " fileId:" + fileId);
        }
        var numberOfDictRemoves = removeRedactionRequests.stream()
                .filter(removeRedactionRequestModel -> removeRedactionRequestModel.isRemoveFromDictionary() || removeRedactionRequestModel.isRemoveFromAllDossiers())
                .count();
        if (numberOfDictRemoves > 100) {
            throw new BadRequestException("Maximum number of remove from dictionary requests is 100.");
        }
        var response = new ArrayList<ManualAnnotationResponse>();
        List<RequestEntryPair<RemoveRedactionRequest>> requests = manualRedactionMapper.toRemoveRedactionRequestList(dossierId,
                                                                                                                     fileId,
                                                                                                                     dossierTemplateId,
                                                                                                                     removeRedactionRequests,
                                                                                                                     includeUnprocessed);

        Set<String> verifiedTypes = new HashSet<>();
        //validate removing from dossier template dictionary
        for (RequestEntryPair<RemoveRedactionRequest> removeRedactionRequestRequestEntryPair : requests) {
            RemoveRedactionRequest removeRedactionRequest = removeRedactionRequestRequestEntryPair.getRequest();
            EntityLogEntry entityLogEntry = removeRedactionRequestRequestEntryPair.getEntityLogEntry();

            if (verifiedTypes.add(removeRedactionRequest.getTypeToRemove())) {
                manualRedactionDictionaryUpdateHandler.validateDictionariesForDelete(removeRedactionRequest,
                                                                                     removeRedactionRequest.getTypeToRemove(),
                                                                                     removeRedactionRequest.getDossierTemplateId());
            }

            log.info("add removeRedaction for file {} and annotation {}", fileId, removeRedactionRequest.getAnnotationId());
            IdRemovalEntity idRemoval = removeRedactionPersistenceService.insert(fileId, removeRedactionRequest);

            if ((entityLogEntry.isDictionaryEntry() || entityLogEntry.isDossierDictionaryEntry())
                && (entityLogEntry.getEngines().contains(Engine.DICTIONARY)
                    || entityLogEntry.getEngines().contains(Engine.DOSSIER_DICTIONARY))
                && entityLogEntry.getEngines().contains(Engine.MANUAL)) {
                insertLocalRemove(fileId, removeRedactionRequest);
            }

            Long commentId = commentService.addCommentAndGetId(fileId,
                                                               removeRedactionRequest.getAnnotationId(),
                                                               removeRedactionRequest.getComment(),
                                                               removeRedactionRequest.getUser());

            Set<String> typeIdsOfModifiedDictionaries = manualRedactionDictionaryUpdateHandler.handleRemoveFromDictionaryAndReturnModifiedTypeIds(fileId, removeRedactionRequest);

            boolean removedFromDictionary = !typeIdsOfModifiedDictionaries.isEmpty();

            removeRedactionPersistenceService.updateModifiedDictionaries(fileId, removeRedactionRequest.getAnnotationId(), removedFromDictionary, typeIdsOfModifiedDictionaries);

            if (!idRemoval.isRemoveFromAllDossiers() && !idRemoval.isRemoveFromDictionary()) {
                entityLogMergeService.mergeIdToRemove(MagicConverter.convert(idRemoval, IdRemoval.class), entityLogEntry, getAnalysisNumber(dossierId, fileId));
            } else {
                entityLogEntry = pendingEntryFactory.buildRemoveFromDictionary(MagicConverter.convert(idRemoval, IdRemoval.class), entityLogEntry);
            }
            response.add(ManualAnnotationResponse.builder().annotationId(removeRedactionRequest.getAnnotationId()).commentId(commentId).entityLogEntry(entityLogEntry).build());
        }

        reprocess(dossierId, fileId);

        fileStatusPersistenceService.setLastManualChangeDate(fileId, OffsetDateTime.now());

        return response;
    }


    private void insertLocalRemove(String fileId, RemoveRedactionRequest removeRedactionRequest) {

        removeRedactionPersistenceService.insert(fileId,
                                                 RemoveRedactionRequest.builder()
                                                         .annotationId(removeRedactionRequest.getAnnotationId())
                                                         .value(removeRedactionRequest.getValue())
                                                         .dictionaryEntryType(removeRedactionRequest.getDictionaryEntryType())
                                                         .user(removeRedactionRequest.getUser())
                                                         .dossierId(removeRedactionRequest.getDossierId())
                                                         .dossierTemplateId(removeRedactionRequest.getDossierTemplateId())
                                                         .typeToRemove(removeRedactionRequest.getTypeToRemove())
                                                         .removeFromDictionary(false)
                                                         .removeFromAllDossiers(false)
                                                         .page(removeRedactionRequest.getPage())
                                                         .build());
    }


    @Transactional
    public List<ManualAnnotationResponse> addForceRedaction(String dossierId, String fileId, Set<ForceRedactionRequestModel> forceRedactionRequests) {

        if (forceRedactionRequests.isEmpty()) {
            throw new BadRequestException("force redaction requests is empty for request: dossierId:" + dossierId + " fileId:" + fileId);
        }
        var response = new ArrayList<ManualAnnotationResponse>();
        dossierPersistenceService.getAndValidateDossier(dossierId);
        List<RequestEntryPair<ForceRedactionRequest>> requests = manualRedactionMapper.toForceRedactionRequestList(dossierId,
                                                                                                                   fileId,
                                                                                                                   forceRedactionRequests,
                                                                                                                   getEntityLogEntryConsumer(fileId));

        for (RequestEntryPair<ForceRedactionRequest> forceRedactionRequestRequestEntryPair : requests) {
            ForceRedactionRequest forceRedactionRequest = forceRedactionRequestRequestEntryPair.getRequest();
            EntityLogEntry entityLogEntry = forceRedactionRequestRequestEntryPair.getEntityLogEntry();
            ManualForceRedactionEntity manualForceRedaction = forceRedactionPersistenceService.insert(fileId, forceRedactionRequest);

            Long commentId = commentService.addCommentAndGetId(fileId,
                                                               forceRedactionRequest.getAnnotationId(),
                                                               forceRedactionRequest.getComment(),
                                                               forceRedactionRequest.getUser());

            entityLogMergeService.mergeForceRedaction(MagicConverter.convert(manualForceRedaction, ManualForceRedaction.class),
                                                      entityLogEntry,
                                                      getAnalysisNumber(dossierId, fileId));
            response.add(ManualAnnotationResponse.builder().annotationId(forceRedactionRequest.getAnnotationId()).commentId(commentId).entityLogEntry(entityLogEntry).build());
        }

        reprocess(dossierId, fileId);

        fileStatusPersistenceService.setLastManualChangeDate(fileId, OffsetDateTime.now());

        return response;
    }


    @Transactional
    public List<ManualAnnotationResponse> addLegalBasisChange(String dossierId, String fileId, Set<LegalBasisChangeRequestModel> legalBasisChangeRequests) {

        if (legalBasisChangeRequests.isEmpty()) {
            throw new BadRequestException("legal absis change requests is empty for request: dossierId:" + dossierId + " fileId:" + fileId);
        }

        var response = new ArrayList<ManualAnnotationResponse>();
        dossierPersistenceService.getAndValidateDossier(dossierId);
        List<RequestEntryPair<LegalBasisChangeRequest>> requests = manualRedactionMapper.toLegalBasisChangeRequestList(dossierId,
                                                                                                                       fileId,
                                                                                                                       legalBasisChangeRequests,
                                                                                                                       getEntityLogEntryConsumer(fileId));

        for (RequestEntryPair<LegalBasisChangeRequest> legalBasisChangeRequestRequestEntryPair : requests) {
            LegalBasisChangeRequest legalBasisChangeRequest = legalBasisChangeRequestRequestEntryPair.getRequest();
            EntityLogEntry entityLogEntry = legalBasisChangeRequestRequestEntryPair.getEntityLogEntry();
            ManualLegalBasisChangeEntity manualLegalBasisChange = legalBasisChangePersistenceService.insert(fileId, legalBasisChangeRequest);

            Long commentId = commentService.addCommentAndGetId(fileId,
                                                               legalBasisChangeRequest.getAnnotationId(),
                                                               legalBasisChangeRequest.getComment(),
                                                               legalBasisChangeRequest.getUser());

            entityLogMergeService.mergeLegalBasisChange(MagicConverter.convert(manualLegalBasisChange, ManualLegalBasisChange.class),
                                                        entityLogEntry,
                                                        getAnalysisNumber(dossierId, fileId));
            response.add(ManualAnnotationResponse.builder().annotationId(legalBasisChangeRequest.getAnnotationId()).commentId(commentId).entityLogEntry(entityLogEntry).build());
        }
        reprocess(dossierId, fileId);
        fileStatusPersistenceService.setLastManualChangeDate(fileId, OffsetDateTime.now());

        return response;
    }


    @Transactional
    public List<ManualAnnotationResponse> addRecategorization(String dossierId,
                                                              String fileId,
                                                              Dossier dossier,
                                                              Set<RecategorizationRequestModel> recategorizationRequests,
                                                              boolean includeUnprocessed) {

        if (recategorizationRequests.isEmpty()) {
            throw new BadRequestException("recat redaction requests is empty for request: dossierId:" + dossierId + " fileId:" + fileId);
        }

        var response = new ArrayList<ManualAnnotationResponse>();
        dossierPersistenceService.getAndValidateDossier(dossierId);
        var dossierEntity = dossierPersistenceService.getAndValidateDossier(dossierId);
        List<RequestEntryPair<RecategorizationRequest>> requests = manualRedactionMapper.toRecategorizationRequestList(dossierId,
                                                                                                                       fileId,
                                                                                                                       dossier.getDossierTemplateId(),
                                                                                                                       recategorizationRequests,
                                                                                                                       includeUnprocessed,
                                                                                                                       getEntityLogEntryConsumer(fileId));
        for (RequestEntryPair<RecategorizationRequest> recategorizationRequestRequestEntryPair : requests) {
            RecategorizationRequest recategorizationRequest = recategorizationRequestRequestEntryPair.getRequest();
            EntityLogEntry entityLogEntry = recategorizationRequestRequestEntryPair.getEntityLogEntry();
            manualRedactionDictionaryUpdateHandler.validateDictionariesForAdd(recategorizationRequest, recategorizationRequest.getValue());
            manualRedactionDictionaryUpdateHandler.validateDictionariesForDelete(recategorizationRequest,
                                                                                 recategorizationRequest.getTypeToRemove(),
                                                                                 recategorizationRequest.getDossierTemplateId());

            ManualRecategorizationEntity recategorizationEntity = recategorizationPersistenceService.createOrUpdate(fileId, recategorizationRequest);

            Set<String> typeIdsOfDictionariesWithAdd = manualRedactionDictionaryUpdateHandler.handleAddToDictionaryAndReturnModifiedTypeIds(fileId,
                                                                                                                                            recategorizationRequest.getValue(),
                                                                                                                                            recategorizationRequest);

            Set<String> typeIdsOfDictionariesWithDelete = manualRedactionDictionaryUpdateHandler.handleRemoveFromDictionaryAndReturnModifiedTypeIds(fileId,
                                                                                                                                                    recategorizationRequest);
            recategorizationEntity.setTypeIdsOfDictionariesWithAdd(typeIdsOfDictionariesWithAdd);
            recategorizationEntity.setTypeIdsOfDictionariesWithDelete(typeIdsOfDictionariesWithDelete);

            Long commentId = commentService.addCommentAndGetId(fileId,
                                                               recategorizationRequest.getAnnotationId(),
                                                               recategorizationRequest.getComment(),
                                                               recategorizationRequest.getUser());

            if (!recategorizationRequest.isAddToAllDossiers() && !recategorizationRequest.isAddToDictionary()) {
                var entry = entityLogMergeService.mergeRecategorization(MagicConverter.convert(recategorizationEntity,
                                                                                               ManualRecategorization.class,
                                                                                               new ManualRecategorizationMapper()),
                                                                        entityLogEntry,
                                                                        dossierEntity,
                                                                        getAnalysisNumber(dossierId, fileId));
                if (entry != null) {
                    entityLogEntry = entry;
                }
            } else {
                entityLogEntry = pendingEntryFactory.buildRecategorizeWithDictionary(MagicConverter.convert(recategorizationEntity,
                                                                                                            ManualRecategorization.class,
                                                                                                            new ManualRecategorizationMapper()), entityLogEntry);
            }
            response.add(ManualAnnotationResponse.builder().annotationId(recategorizationRequest.getAnnotationId()).commentId(commentId).entityLogEntry(entityLogEntry).build());

            recategorizationPersistenceService.saveAndFlush(recategorizationEntity);
        }

        reprocess(dossierId, fileId);

        fileStatusPersistenceService.setLastManualChangeDate(fileId, OffsetDateTime.now());

        return response;
    }


    @Transactional
    @Observed(name = "ManualRedactionService", contextualName = "add-manual-redaction")
    public List<ManualAnnotationResponse> addResizeRedaction(String dossierId,
                                                             String fileId,
                                                             Set<ResizeRedactionRequestModel> resizeRedactionRequests,
                                                             boolean includeUnprocessed) {

        if (resizeRedactionRequests.isEmpty()) {
            throw new BadRequestException("resize redaction requests is empty for request: dossierId:" + dossierId + " fileId:" + fileId);
        }

        List<ManualAnnotationResponse> response = new ArrayList<>();
        List<ManualResizeRedactionEntity> manualResizeRedactionEntities = new ArrayList<>();
        List<RequestEntryPair<ResizeRedactionRequest>> requests = manualRedactionMapper.toResizeRedactionRequestList(dossierId,
                                                                                                                     fileId,
                                                                                                                     resizeRedactionRequests,
                                                                                                                     getEntityLogEntryConsumer(fileId),
                                                                                                                     includeUnprocessed);

        for (RequestEntryPair<ResizeRedactionRequest> resizeRedactionRequestRequestEntryPair : requests) {

            ResizeRedactionRequest resizeRedactionRequest = resizeRedactionRequestRequestEntryPair.getRequest();
            EntityLogEntry entityLogEntry = resizeRedactionRequestRequestEntryPair.getEntityLogEntry();
            Long commentId = null;
            var resizeRedaction = resizeRedactionPersistenceService.insert(fileId, resizeRedactionRequest);
            manualResizeRedactionEntities.add(resizeRedaction);

            if (resizeRedactionRequest.getComment() != null) {
                commentId = commentService.addCommentAndGetId(fileId,
                                                              resizeRedactionRequest.getAnnotationId(),
                                                              resizeRedactionRequest.getComment(),
                                                              resizeRedactionRequest.getUser());
            }

            Set<String> typeIdsOfModifiedDictionaries = manualRedactionDictionaryUpdateHandler.updateDictionaryForResizeRedactions(dossierId,
                                                                                                                                   fileId,
                                                                                                                                   resizeRedaction,
                                                                                                                                   entityLogEntry);
            resizeRedactionPersistenceService.updateModifiedDictionaries(resizeRedaction.getId().getFileId(),
                                                                         resizeRedaction.getId().getAnnotationId(),
                                                                         typeIdsOfModifiedDictionaries);

            if (!resizeRedactionRequest.isAddToAllDossiers() && !resizeRedactionRequest.getUpdateDictionary()) {
                entityLogMergeService.mergeResizeRedaction(MagicConverter.convert(resizeRedaction, ManualResizeRedaction.class, new ManualResizeRedactionMapper()),
                                                           entityLogEntry,
                                                           getAnalysisNumber(dossierId, fileId));
            } else {
                entityLogEntry = pendingEntryFactory.buildResizeWithDictionary(MagicConverter.convert(resizeRedaction,
                                                                                                      ManualResizeRedaction.class,
                                                                                                      new ManualResizeRedactionMapper()), entityLogEntry);
            }
            response.add(ManualAnnotationResponse.builder().annotationId(resizeRedactionRequest.getAnnotationId()).commentId(commentId).entityLogEntry(entityLogEntry).build());
        }

        manualResizeRedactionEntities = manualResizeRedactionEntities.stream()
                .filter(manualRedactionEntry -> !manualRedactionEntry.getUpdateDictionary())
                .collect(Collectors.toList());

        FileModel fileStatus = fileStatusService.getStatus(fileId);
        if (!manualResizeRedactionEntities.isEmpty() && fileStatus.isExcludedFromAutomaticAnalysis()) {
            ManualRedactions manualRedactions = ManualRedactions.builder().resizeRedactions(convertResizeRedactions(manualResizeRedactionEntities)).build();
            sendToSurroundingTextAnalysisQueue(fileId, dossierPersistenceService.getAndValidateDossier(dossierId), fileStatusService.getStatus(fileId), manualRedactions);
        } else {
            reprocess(dossierId, fileId);
        }

        fileStatusPersistenceService.setLastManualChangeDate(fileId, OffsetDateTime.now());

        return response;
    }


    private Set<ManualResizeRedaction> convertResizeRedactions(List<ManualResizeRedactionEntity> source) {

        return source.stream()
                .map(entry -> convert(entry, ManualResizeRedaction.class, new ManualResizeRedactionMapper()))
                .collect(Collectors.toSet());
    }


    private void validatePositions(String fileId, AddRedactionRequest addRedactionRequest) {

        var numberOfPages = fileStatusService.getStatus(fileId).getNumberOfPages();
        addRedactionRequest.getPositions()
                .stream()
                .filter(p -> p.getPage() > numberOfPages)
                .findAny()
                .ifPresent(p -> new BadRequestException("Invalid page found in the request"));
    }


    private void reprocess(String dossierId, String fileId) {

        fileStatusService.setStatusReprocessForManual(dossierId, fileId, true);
    }


    public ManualRedactions getManualRedactions(String fileId, ManualChangesQueryOptions options) {

        return manualRedactionProviderService.getManualRedactions(fileId, options);
    }


    @Transactional
    public void updateProcessedDate(ManualRedactions manualRedactions) {

        if (manualRedactions != null) {

            manualRedactions.getEntriesToAdd()
                    .forEach(e -> {
                        if (e.getProcessedDate() == null) {
                            addRedactionPersistenceService.markAsProcessed(e);
                        }
                    });
            manualRedactions.getIdsToRemove()
                    .forEach(e -> {
                        if (e.getProcessedDate() == null) {
                            removeRedactionPersistenceService.markAsProcessed(e);
                        }
                    });
            manualRedactions.getForceRedactions()
                    .forEach(e -> {
                        if (e.getProcessedDate() == null) {
                            forceRedactionPersistenceService.markAsProcessed(e.getAnnotationId(), e.getFileId());
                        }
                    });
            manualRedactions.getRecategorizations()
                    .forEach(e -> {
                        if (e.getProcessedDate() == null) {
                            recategorizationPersistenceService.markAsProcessed(e.getAnnotationId(), e.getFileId());
                        }
                    });
            manualRedactions.getResizeRedactions()
                    .forEach(e -> {
                        if (e.getProcessedDate() == null) {
                            resizeRedactionPersistenceService.markAsProcessed(e.getAnnotationId(), e.getFileId());
                        }
                    });
            manualRedactions.getLegalBasisChanges()
                    .forEach(e -> {
                        if (e.getProcessedDate() == null) {
                            legalBasisChangePersistenceService.markAsProcessed(e.getAnnotationId(), e.getFileId());
                        }
                    });
        }
    }


    public int addManualRedactionEntries(List<ManualRedactionEntry> manualRedactionEntriesToAdd, boolean addAsProcessed) {

        List<ManualRedactionEntryEntity> manualRedactionEntryEntities = manualRedactionEntriesToAdd.stream()
                .map(this::toManualRedactionEntryEntity)
                .toList();

        if (addAsProcessed) {
            manualRedactionEntryEntities.forEach(e -> e.setProcessedDate(OffsetDateTime.now()));
        }

        return addRedactionPersistenceService.insert(manualRedactionEntryEntities).size();
    }


    private ManualRedactionEntryEntity toManualRedactionEntryEntity(ManualRedactionEntry manualRedactionEntry) {

        var file = fileStatusPersistenceService.getStatus(manualRedactionEntry.getFileId());
        String dossierId = file.getDossierId();
        String dossierTemplateId = dossierPersistenceService.getDossierTemplateId(dossierId);
        String type = manualRedactionEntry.getType();
        ManualRedactionEntryEntity manualRedactionEntryEntity = MagicConverter.convert(manualRedactionEntry, ManualRedactionEntryEntity.class);
        String typeId;
        if (manualRedactionEntry.isAddToDossierDictionary()) {
            typeId = TypeIdUtils.toTypeId(type, dossierTemplateId, dossierId);
        } else {
            typeId = TypeIdUtils.toTypeId(type, dossierTemplateId);
        }
        manualRedactionEntryEntity.setTypeId(typeId);
        manualRedactionEntryEntity.setTypeIdsOfModifiedDictionaries(Set.of(typeId));
        manualRedactionEntryEntity.setId(new AnnotationEntityId(manualRedactionEntry.getAnnotationId(), manualRedactionEntry.getFileId()));
        manualRedactionEntryEntity.setPositions(manualRedactionEntry.getPositions()
                                                        .stream()
                                                        .map(p -> MagicConverter.convert(p, RectangleEntity.class))
                                                        .toList());
        manualRedactionEntryEntity.setRequestDate(manualRedactionEntry.getRequestDate());
        manualRedactionEntryEntity.setFileStatus(file);
        return manualRedactionEntryEntity;
    }


    public void deleteManualRedactionEntries(List<ManualRedactionEntry> list) {

        list.forEach(manualRedactionEntry -> addRedactionPersistenceService.hardDelete(manualRedactionEntry.getFileId(), manualRedactionEntry.getAnnotationId()));
    }


    public void softDeleteForceRedactions(String fileId, List<String> ids) {

        OffsetDateTime now = OffsetDateTime.now();
        ids.forEach(id -> forceRedactionPersistenceService.softDelete(fileId, id, now));
    }


    public void undeleteForceRedactions(String fileId, List<String> ids) {

        ids.forEach(id -> forceRedactionPersistenceService.undelete(fileId, id));
    }


    private void addManualRedactionEntry(String fileId, ManualRedactionEntrySource manualRedactionEntrySource) {

        EntityLogEntry entityLogEntry = manualRedactionEntrySource.getEntityLogEntry();
        ManualRedactionEntry manualRedactionEntry = ManualRedactionEntry.builder()
                .value(entityLogEntry.getValue())
                .reason(entityLogEntry.getReason())
                .section(entityLogEntry.getSection())
                .annotationId(entityLogEntry.getId())
                .type(entityLogEntry.getType())
                .addToDossierDictionary(false)
                .addToDictionary(false)
                .positions(convertPositions(entityLogEntry.getPositions()))
                .rectangle(entityLogEntry.getEntryType() == EntryType.AREA)
                .user(KeycloakSecurity.getUserId())
                .legalBasis(entityLogEntry.getLegalBasis())
                .textAfter(entityLogEntry.getTextAfter())
                .textBefore(entityLogEntry.getTextBefore())
                .dictionaryEntryType(getDictionaryEntryType(entityLogEntry))
                .fileId(fileId)
                .requestDate(OffsetDateTime.now())
                .sourceId(manualRedactionEntrySource.getSourceId())
                .build();

        addManualRedactionEntries(List.of(manualRedactionEntry), false);
    }


    private List<Rectangle> convertPositions(List<Position> positions) {

        return positions.stream()
                .map(rectangle -> new Rectangle(rectangle.x(), rectangle.y(), rectangle.w(), rectangle.h(), rectangle.getPageNumber()))
                .toList();
    }


    private Consumer<ManualRedactionEntrySource> getEntityLogEntryConsumer(String fileId) {

        return entry -> addManualRedactionEntry(fileId, entry);
    }


    private void sendToSurroundingTextAnalysisQueue(String fileId, DossierEntity dossier, FileModel fileModel, ManualRedactions manualRedactions) {

        var fileEntity = fileStatusPersistenceService.getStatus(fileId);
        var analyseRequest = AnalyzeRequest.builder()
                .messageType(MessageType.SURROUNDING_TEXT_ANALYSIS)
                .dossierId(dossier.getId())
                .analysisNumber(fileModel.getNumberOfAnalyses() + 1)
                .sectionsToReanalyse(Sets.newHashSet())
                .fileId(fileId)
                .manualRedactions(manualRedactions)
                .dossierTemplateId(dossier.getDossierTemplateId())
                .lastProcessed(fileModel.getLastProcessed())
                .fileAttributes(fileStatusService.convertAttributes(fileEntity.getFileAttributes(), dossier.getDossierTemplateId()))
                .build();

        log.info("Sending Surrounding Text Analysis for unprocessed manual redactions: {} for file: {} ", manualRedactions, fileId);
        rabbitTemplate.convertAndSend(MessagingConfiguration.REDACTION_PRIORITY_REQUEST_EXCHANGE, TenantContext.getTenantId(), analyseRequest);
    }


    private int getAnalysisNumber(String dossierId, String fileId) {

        Optional<Integer> analysisNumber = entityLogMongoService.findLatestAnalysisNumber(dossierId, fileId);
        return analysisNumber.orElseThrow(() -> new BadRequestException("Can't load latest analysis number"));
    }


    public String getNewAnnotationId() {

        return UUID.randomUUID().toString();
    }

}
