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

import static com.iqser.red.service.persistence.management.v1.processor.configuration.MessagingConfiguration.CHUNKING_REQUEST_EXCHANGE;

import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;

import com.google.common.collect.Sets;
import com.iqser.red.service.pdftron.redaction.v1.api.model.ProcessUntouchedDocumentRequest;
import com.iqser.red.service.persistence.management.v1.processor.configuration.MessagingConfiguration;
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.FileAttributeEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.FileEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.projection.DossierStatsFileProjection;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.InternalServerErrorException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.model.AnalysisType;
import com.iqser.red.service.persistence.management.v1.processor.model.CvAnalysisServiceRequest;
import com.iqser.red.service.persistence.management.v1.processor.model.FileIdentifier;
import com.iqser.red.service.persistence.management.v1.processor.model.ManualChangesQueryOptions;
import com.iqser.red.service.persistence.management.v1.processor.model.OCRStatusUpdateResponse;
import com.iqser.red.service.persistence.management.v1.processor.model.VisualLayoutParsingServiceRequest;
import com.iqser.red.service.persistence.management.v1.processor.model.image.ImageServiceRequest;
import com.iqser.red.service.persistence.management.v1.processor.model.websocket.AnalyseStatus;
import com.iqser.red.service.persistence.management.v1.processor.model.websocket.FileEventType;
import com.iqser.red.service.persistence.management.v1.processor.service.layoutparsing.ChunkingRequestFactory;
import com.iqser.red.service.persistence.management.v1.processor.service.layoutparsing.LayoutParsingRequestFactory;
import com.iqser.red.service.persistence.management.v1.processor.service.layoutparsing.QueueMessageIdentifierService;
import com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.ManualRedactionProviderService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.ComponentDefinitionPersistenceService;
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.DossierTemplatePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileAttributeConfigPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.ViewedPagesPersistenceService;
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.CommentPersistenceService;
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.service.websocket.WebsocketService;
import com.iqser.red.service.persistence.management.v1.processor.settings.FileManagementServiceSettings;
import com.iqser.red.service.persistence.management.v1.processor.utils.FileModelMapper;
import com.iqser.red.service.persistence.management.v1.processor.utils.StorageIdUtils;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AnalyzeRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AnalyzeResult;
import com.iqser.red.service.persistence.service.v1.api.shared.model.BulkLocalRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttribute;
import com.iqser.red.service.persistence.service.v1.api.shared.model.MessageType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.NerServiceRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentDefinition;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileErrorInfo;
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.FileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.ProcessingStatus;
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.manual.AddRedactionBulkLocalRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.service.ComponentLogMongoService;
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.llm.service.EntityAiDescription;
import com.knecon.fforesight.llm.service.LlmNerMessage;
import com.knecon.fforesight.service.layoutparser.internal.api.queue.LayoutParsingQueueNames;
import com.knecon.fforesight.service.ocr.v1.api.model.AzureOcrFeature;
import com.knecon.fforesight.service.ocr.v1.api.model.DocumentRequest;
import com.knecon.fforesight.tenantcommons.TenantContext;

import jakarta.transaction.Transactional;
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 FileStatusService {

    FileStatusPersistenceService fileStatusPersistenceService;
    DossierPersistenceService dossierPersistenceService;
    DossierTemplatePersistenceService dossierTemplatePersistenceService;
    RabbitTemplate rabbitTemplate;
    ManualRedactionProviderService manualRedactionProviderService;
    FileManagementStorageService fileManagementStorageService;
    LegalBasisChangePersistenceService legalBasisChangePersistenceService;
    RecategorizationPersistenceService recategorizationPersistenceService;
    CommentPersistenceService commentPersistenceService;
    ForceRedactionPersistenceService forceRedactionPersistenceService;
    RemoveRedactionPersistenceService removeRedactionPersistenceService;
    AddRedactionPersistenceService addRedactionPersistenceService;
    ResizeRedactionPersistenceService resizeRedactionPersistenceService;
    FileAttributeConfigPersistenceService fileAttributeConfigPersistenceService;
    FileManagementServiceSettings settings;
    ReanalysisRequiredStatusService reanalysisRequiredStatusService;
    ViewedPagesPersistenceService viewedPagesPersistenceService;
    FileManagementServiceSettings fileManagementServiceSettings;
    LayoutParsingRequestFactory layoutParsingRequestFactory;
    ComponentMappingService componentMappingService;
    ComponentDefinitionPersistenceService componentDefinitionPersistenceService;
    EntityLogMongoService entityLogMongoService;
    ComponentLogMongoService componentLogMongoService;
    CurrentApplicationTypeProvider currentApplicationTypeProvider;
    DictionaryPersistenceService dictionaryPersistenceService;

    WebsocketService websocketService;


    public List<FileModel> getAllRelevantStatusesForReanalysisScheduler() {

        var fileEntities = fileStatusPersistenceService.getAllRelevantStatusesForReanalysisScheduler(fileManagementServiceSettings.getMaxErrorRetries());
        var convertedList = MagicConverter.convert(fileEntities, FileModel.class, new FileModelMapper());
        log.debug("Initial list of files for scheduler contains: {} files.", fileEntities.size());
        return reanalysisRequiredStatusService.enhanceFileStatusWithAnalysisRequirements(convertedList)
                .stream()
                .filter(FileModel::isAnalysisRequired)
                .collect(Collectors.toList());
    }


    @Transactional
    public List<FileModel> getActiveFiles(String dossierId) {

        var fileEntities = fileStatusPersistenceService.getActiveFiles(dossierId);
        var convertedList = MagicConverter.convert(fileEntities, FileModel.class, new FileModelMapper());
        return reanalysisRequiredStatusService.enhanceFileStatusWithAnalysisRequirements(convertedList);
    }


    public List<DossierStatsFileProjection> getDossierStatsFiles(String dossierId) {

        return fileStatusPersistenceService.getFilesForDossierStats(dossierId);
    }


    @Transactional
    public List<FileModel> getDossierStatus(String dossierId) {

        var fileEntities = new ArrayList<>(fileStatusPersistenceService.getStatusesForDossier(dossierId));
        var convertedList = MagicConverter.convert(fileEntities, FileModel.class, new FileModelMapper());
        return reanalysisRequiredStatusService.enhanceFileStatusWithAnalysisRequirements(convertedList);
    }


    @Transactional
    public List<FileModel> getDossierTemplateStatus(String dossierTemplateId) {

        var fileEntities = new ArrayList<>(fileStatusPersistenceService.getStatusesForDossierTemplate(dossierTemplateId));
        var convertedList = MagicConverter.convert(fileEntities, FileModel.class, new FileModelMapper());
        return reanalysisRequiredStatusService.enhanceFileStatusWithAnalysisRequirements(convertedList);
    }


    @Transactional
    public List<FileModel> getAllFiles() {

        var fileEntities = new ArrayList<>(fileStatusPersistenceService.getAllFiles());
        var convertedList = MagicConverter.convert(fileEntities, FileModel.class, new FileModelMapper());
        return reanalysisRequiredStatusService.enhanceFileStatusWithAnalysisRequirements(convertedList);
    }


    @Transactional
    public List<FileModel> getAllRelevantErrorFiles() {

        var fileEntities = new ArrayList<>(fileStatusPersistenceService.getAllRelevantErrorFiles());
        var convertedList = MagicConverter.convert(fileEntities, FileModel.class, new FileModelMapper());
        return reanalysisRequiredStatusService.enhanceFileStatusWithAnalysisRequirements(convertedList);
    }


    public Set<FileIdentifier> getFileIdentifiersWhereAnalysisFlagCalculationIsRequired() {

        return fileStatusPersistenceService.getFileIdentifiersWhereAnalysisFlagCalculationIsRequired();
    }


    @Transactional
    public List<FileModel> getStatusesAddedBefore(OffsetDateTime end) {

        var fileEntities = fileStatusPersistenceService.getStatusesAddedBefore(end);
        return MagicConverter.convert(fileEntities, FileModel.class, new FileModelMapper());
    }


    public List<FileModel> getSoftDeletedForDossierList(List<String> dossierIds) {

        var fileEntities = fileStatusPersistenceService.getSoftDeletedFiles(dossierIds);
        var convertedList = MagicConverter.convert(fileEntities, FileModel.class, new FileModelMapper());
        return reanalysisRequiredStatusService.enhanceFileStatusWithAnalysisRequirements(convertedList);
    }


    @Transactional
    public FileModel getStatus(String fileId) {

        var fileEntity = fileStatusPersistenceService.getStatus(fileId);
        var converted = MagicConverter.convert(fileEntity, FileModel.class, new FileModelMapper());
        return reanalysisRequiredStatusService.enhanceFileStatusWithAnalysisRequirements(converted);
    }


    public boolean fileExists(String fileId) {

        return fileStatusPersistenceService.statusExists(fileId);
    }


    @Transactional
    public void updateProcessingStatusPreprocessed(String dossierId, String fileId, boolean hasHighlights, long fileSize) {

        fileStatusPersistenceService.updateProcessingStatusPreprocessed(fileId, hasHighlights, fileSize);
        addToAnalysisQueue(dossierId, fileId, false, Set.of(), AnalysisType.DEFAULT);

    }


    protected void addSearchTermOccurrencesAnalysisRequestToAnalysisQueue(FileEntity fileStatus, BulkLocalRequest bulkLocalRequest) {

        var dossierId = fileStatus.getDossierId();
        var dossier = dossierPersistenceService.getAndValidateDossier(dossierId);
        var fileId = fileStatus.getId();
        int analysisVersion = fileStatus.getAnalysisVersion();

        MessageType searchBulkLocalTerm = MessageType.SEARCH_BULK_LOCAL_TERM;
        log.info("Add file: {} from dossier {} to Analysis queue with MessageType {}", fileId, dossierId, searchBulkLocalTerm);

        var analyseRequest = AnalyzeRequest.builder()
                .analysisNumber(analysisVersion)
                .messageType(searchBulkLocalTerm)
                .dossierId(dossierId)
                .fileId(fileId)
                .dossierTemplateId(dossier.getDossierTemplateId())
                .bulkLocalRequest(bulkLocalRequest)
                .build();

        rabbitTemplate.convertAndSend(MessagingConfiguration.REDACTION_PRIORITY_REQUEST_EXCHANGE, TenantContext.getTenantId(), analyseRequest);

        websocketService.sendAnalysisEvent(dossierId, fileId, AnalyseStatus.BULK_LOCAL_REDACTIONS_PROCESSING, analysisVersion);
    }


    protected void addToAnalysisQueue(String dossierId, String fileId, boolean priority, Set<Integer> sectionsToReanalyse, AnalysisType analysisType) {

        log.info("OOOO: in analysis queue");
        var dossier = dossierPersistenceService.getAndValidateDossier(dossierId);
        var fileEntity = fileStatusPersistenceService.getStatus(fileId);

        if (!fileManagementStorageService.objectExists(dossierId, fileId, FileType.ORIGIN)) {
            addToPreprocessingQueue(dossier.getDossierTemplateId(), dossierId, fileId, fileEntity.getFilename());
            sendReadOnlyAnalysisEvent(dossierId, fileId, fileEntity);
            return;
        }

        if (fileEntity.isExcluded()) {
            log.debug("File {} is excluded", fileEntity.getId());
            setStatusProcessed(fileId);
            return;
        }

        if (settings.isVisualLayoutParsingEnabled() && !fileManagementStorageService.objectExists(dossierId, fileId, FileType.VISUAL_LAYOUT)) {
            log.info("Add file: {} from dossier {} to Visual Layout Parsing queue", fileId, dossierId);
            addToVisualLayoutParsingQueue(dossierId, fileId);
            sendReadOnlyAnalysisEvent(dossierId, fileId, fileEntity);
            return;
        }

        if (settings.isFigureDetectionEnabled() && !fileManagementStorageService.objectExists(dossierId, fileId, FileType.FIGURE)) {
            log.info("Add file: {} from dossier {} to Figure Detection queue", fileId, dossierId);
            addToFigureDetectionRequestQueue(dossierId, fileId);
            sendReadOnlyAnalysisEvent(dossierId, fileId, fileEntity);
            return;
        }

        if (settings.isCvTableParsingEnabled() && !fileManagementStorageService.objectExists(dossierId, fileId, FileType.TABLES)) {
            log.info("Add file: {} from dossier {} to Cv Service queue", fileId, dossierId);
            addToTableParsingRequestQueue(dossierId, fileId);
            sendReadOnlyAnalysisEvent(dossierId, fileId, fileEntity);
            return;
        }

        if (settings.isImageServiceEnabled() && !fileManagementStorageService.objectExists(dossierId, fileId, FileType.IMAGE_INFO)) {
            log.info("Add file: {} from dossier {} to Image queue", fileId, dossierId);
            addToImageQueue(dossierId, fileId);
            sendReadOnlyAnalysisEvent(dossierId, fileId, fileEntity);
            return;
        }

        var fileModel = MagicConverter.convert(fileEntity, FileModel.class, new FileModelMapper());
        fileModel = reanalysisRequiredStatusService.enhanceFileStatusWithAnalysisRequirements(fileModel, true);

        var dossierTemplate = dossier.getDossierTemplate();
        if (dossierTemplate.isOcrByDefault()//
            && fileModel.getOcrEndTime() == null //
            && !fileModel.isSoftOrHardDeleted()) {

            if (ocrIsRunning(fileModel)) {
                return;
            }

            log.info("Add file: {} from dossier {} to OCR queue", fileId, dossierId);
            setStatusOcrQueued(dossierId, fileId, false);
            sendReadOnlyAnalysisEvent(dossierId, fileId, fileEntity);
            return;
        }

        if (!fileManagementStorageService.objectExists(dossierId, fileId, FileType.DOCUMENT_TEXT) && !fileManagementStorageService.objectExists(dossierId,
                                                                                                                                                fileId,
                                                                                                                                                FileType.DOCUMENT_TEXT.name(),
                                                                                                                                                FileType.DOCUMENT_TEXT_OLD.getExtension())) {

            var layoutParsingRequest = layoutParsingRequestFactory.build(dossierTemplate.getId(), dossierId, fileId, priority);
            setStatusFullProcessing(fileId);
            log.info("Add file: {} from dossier {} to layout parsing request queue", fileId, dossierId);
            rabbitTemplate.convertAndSend(LayoutParsingQueueNames.LAYOUT_PARSING_REQUEST_EXCHANGE, TenantContext.getTenantId(), layoutParsingRequest);
            sendReadOnlyAnalysisEvent(dossierId, fileId, fileEntity);
            return;
        }

        if (settings.isNerServiceEnabled() && !fileManagementStorageService.objectExists(dossierId, fileId, FileType.NER_ENTITIES)) {
            log.info("Add file: {} from dossier {} to NER queue", fileId, dossierId);
            addToNerQueue(dossier.getDossierTemplateId(), dossierId, fileId);
            sendReadOnlyAnalysisEvent(dossierId, fileId, fileEntity);
            return;
        }

        if (settings.isLlmNerServiceEnabled() && !fileManagementStorageService.objectExists(dossierId, fileId, FileType.DOCUMENT_CHUNKS)) {
            var chunkingRequest = ChunkingRequestFactory.createChunkingRequest(dossierId,
                                                                               fileId,
                                                                               fileManagementStorageService.objectExists(dossierId, fileId, FileType.DOCUMENT_STRUCTURE));
            setStatusFullProcessing(fileId);
            log.info("Sending request to {} for {}/{}", CHUNKING_REQUEST_EXCHANGE, dossierId, fileId);
            rabbitTemplate.convertAndSend(CHUNKING_REQUEST_EXCHANGE, TenantContext.getTenantId(), chunkingRequest);
            sendReadOnlyAnalysisEvent(dossierId, fileId, fileEntity);
            return;
        }

        boolean forceAnalysis = false;
        if (settings.isLlmNerServiceEnabled()) {
            boolean objectExists = fileManagementStorageService.objectExists(dossierId, fileId, FileType.LLM_NER_ENTITIES);
            long currentVersion = fileModel.getAiCreationVersion();
            long expectedVersion = toAiCreationVersion(dictionaryPersistenceService.getTypesForAiCreation(dossierTemplate.getId()));
            boolean versionsMatch = currentVersion == expectedVersion;

            if (!objectExists || !versionsMatch) {
                log.info("Adding file: {} from dossier {} to LLM NER queue", fileId, dossierId);
                if (addToLLMNerQueue(dossierTemplate.getId(), dossierId, fileId)) {
                    sendReadOnlyAnalysisEvent(dossierId, fileId, fileEntity);
                    return;
                } else {
                    log.info("No entities with enabled AI creation are configured.");
                    if (objectExists) {
                        forceAnalysis = true;
                        fileStatusPersistenceService.setAiCreationVersion(fileId, expectedVersion);
                        fileManagementStorageService.deleteObject(dossierId, fileId, FileType.LLM_NER_ENTITIES);
                        log.info("Deleted old LLM NER entities file due to ai creation version change.");
                    }
                }
            }
        }

        if (settings.isAzureNerServiceEnabled() && !fileManagementStorageService.objectExists(dossierId, fileId, FileType.AZURE_NER_ENTITIES)) {
            log.info("Add file: {} from dossier {} to AZURE NER queue", fileId, dossierId);
            addToAzureNerQueue(dossier.getDossierTemplateId(), dossierId, fileId);
            sendReadOnlyAnalysisEvent(dossierId, fileId, fileEntity);
            return;
        }

        boolean reanalyse = fileModel.isReanalysisRequired() || analysisType.equals(AnalysisType.MANUAL_REDACTION_REANALYZE);
        MessageType messageType = calculateMessageType(reanalyse, fileModel.getProcessingStatus(), fileModel);
        if (analysisType == AnalysisType.FORCE_ANALYSE || forceAnalysis) {
            messageType = MessageType.ANALYSE;
        }

        var analyseRequest = AnalyzeRequest.builder()
                .messageType(messageType)
                .dossierId(dossierId)
                .analysisNumber(fileModel.getNumberOfAnalyses() + 1)
                .sectionsToReanalyse(sectionsToReanalyse)
                .fileId(fileId)
                .manualRedactions(manualRedactionProviderService.getManualRedactions(fileId, ManualChangesQueryOptions.allWithoutDeleted()))
                .componentDefinitions(convertComponentDefinitions(componentDefinitionPersistenceService.findByDossierTemplateIdAndNotSoftDeleted(dossierTemplate.getId())))
                .componentMappings(componentMappingService.getMetaDataByDossierTemplateId(dossierTemplate.getId()))
                .dossierTemplateId(dossier.getDossierTemplateId())
                .lastProcessed(fileModel.getLastProcessed())
                .fileAttributes(convertAttributes(fileEntity.getFileAttributes(), dossier.getDossierTemplateId()))
                .build();

        if (fileEntity.isExcludedFromAutomaticAnalysis() && !analysisType.equals(AnalysisType.MANUAL_REDACTION_REANALYZE) && fileManagementStorageService.objectExists(dossierId,
                                                                                                                                                                       fileId,
                                                                                                                                                                       FileType.IMPORTED_REDACTIONS)) {
            analyseRequest.setMessageType(MessageType.IMPORTED_REDACTIONS_ONLY);
        }
        if (analysisType.equals(AnalysisType.COMPONENTS_ONLY_REANALYZE)) {
            analyseRequest.setMessageType(MessageType.REANALYSE_COMPONENTS_ONLY);
        }

        log.info("Add file: {} from dossier {} to Analysis queue with MessageType {}", fileId, dossierId, messageType);

        // a full processing is supposed to happen when the FIRST manual reanalyze is executed for a file uploaded with imported redactions and disabled automatic analysis
        if (fileEntity.isExcludedFromAutomaticAnalysis()
            && analysisType.equals(AnalysisType.MANUAL_REDACTION_REANALYZE)
            && analyseRequest.getAnalysisNumber() == 2
            && entityLogMongoService.entityLogContainsImportedEntries(dossierId, fileId)) {
            analyseRequest.setMessageType(MessageType.ANALYSE);
        }
        if (messageType.equals(MessageType.REANALYSE)) {
            setStatusProcessing(fileId);
        } else {
            setStatusFullProcessing(fileId);
        }

        if (priority) {
            rabbitTemplate.convertAndSend(MessagingConfiguration.REDACTION_PRIORITY_REQUEST_EXCHANGE, TenantContext.getTenantId(), analyseRequest);
        } else {
            rabbitTemplate.convertAndSend(MessagingConfiguration.REDACTION_REQUEST_EXCHANGE, TenantContext.getTenantId(), analyseRequest);
        }
        sendAnalysisEvent(dossierId, fileId, fileEntity);
    }


    private static boolean ocrIsRunning(FileModel fileModel) {

        return fileModel.getProcessingStatus().equals(ProcessingStatus.OCR_PROCESSING_QUEUED) //
               || fileModel.getProcessingStatus().equals(ProcessingStatus.OCR_PROCESSING);
    }


    private void sendAnalysisEvent(String dossierId, String fileId, FileEntity fileEntity) {

        if (fileEntity.getProcessingStatus().equals(ProcessingStatus.UNPROCESSED)
            || fileEntity.getProcessingStatus().equals(ProcessingStatus.ERROR)
            || fileEntity.getProcessingStatus().equals(ProcessingStatus.REPROCESS)
            || fileEntity.getProcessingStatus().equals(ProcessingStatus.PROCESSED)) {
            websocketService.sendAnalysisEvent(dossierId, fileId, AnalyseStatus.PROCESSING, fileEntity.getNumberOfAnalyses() + 1);
        }
    }


    private void sendReadOnlyAnalysisEvent(String dossierId, String fileId, FileEntity fileEntity) {

        if (fileEntity.getProcessingStatus().equals(ProcessingStatus.UNPROCESSED)
            || fileEntity.getProcessingStatus().equals(ProcessingStatus.ERROR)
            || fileEntity.getProcessingStatus().equals(ProcessingStatus.REPROCESS)
            || fileEntity.getProcessingStatus().equals(ProcessingStatus.PROCESSED)
            || fileEntity.getProcessingStatus().equals(ProcessingStatus.OCR_PROCESSING)) {
            websocketService.sendAnalysisEvent(dossierId, fileId, AnalyseStatus.READ_ONLY_PROCESSING, fileEntity.getNumberOfAnalyses() + 1);
        }
    }


    private void addToVisualLayoutParsingQueue(String dossierId, String fileId) {

        fileStatusPersistenceService.updateProcessingStatus(fileId, ProcessingStatus.VISUAL_LAYOUT_PARSING_ANALYZING);
        rabbitTemplate.convertAndSend(MessagingConfiguration.VISUAL_LAYOUT_PARSING_REQUEST_EXCHANGE,
                                      TenantContext.getTenantId(),
                                      VisualLayoutParsingServiceRequest.builder()
                                              .dossierId(dossierId)
                                              .fileId(fileId)
                                              .targetFileExtension(VisualLayoutParsingServiceRequest.TARGET_FILE_EXTENSION)
                                              .responseFileExtension(VisualLayoutParsingServiceRequest.VISUAL_LAYOUT_PARSING_FILE_EXTENSION)
                                              .build(),
                                      message -> {
                                          message.getMessageProperties().setPriority(1);
                                          log.info("answer from visual layout parsing: " + message);
                                          return message;
                                      });
    }


    @SneakyThrows
    public void addToPreprocessingQueue(String dossierTemplateId, String dossierId, String fileId, String filename) {

        var processUntouchedDocumentRequest = ProcessUntouchedDocumentRequest.builder()
                .dossierTemplateId(dossierTemplateId)
                .highlightExtractionEnabled(currentApplicationTypeProvider.isRedactManager())
                .dossierId(dossierId)
                .fileId(fileId)
                .fileName(filename)
                .build();

        setStatusPreProcessingQueued(fileId);

        rabbitTemplate.convertAndSend(MessagingConfiguration.PREPROCESSING_REQUEST_EXCHANGE, TenantContext.getTenantId(), processUntouchedDocumentRequest);
    }


    public void setStatusProcessed(String fileId) {

        fileStatusPersistenceService.updateProcessingStatus(fileId, ProcessingStatus.PROCESSED);
    }


    public void addToFigureDetectionRequestQueue(String dossierId, String fileId) {

        fileStatusPersistenceService.updateProcessingStatus(fileId, ProcessingStatus.FIGURE_DETECTION_ANALYZING);

        rabbitTemplate.convertAndSend(MessagingConfiguration.CV_ANALYSIS_REQUEST_EXCHANGE,
                                      TenantContext.getTenantId(),
                                      CvAnalysisServiceRequest.builder()
                                              .dossierId(dossierId)
                                              .fileId(fileId)
                                              .operation(CvAnalysisServiceRequest.OPERATION_FIGURE_DETECTION)
                                              .targetFileExtension(CvAnalysisServiceRequest.TARGET_FILE_EXTENSION)
                                              .responseFileExtension(CvAnalysisServiceRequest.FIGURE_DETECTION_FILE_EXTENSION)
                                              .build(),
                                      message -> {
                                          message.getMessageProperties().setPriority(1);
                                          return message;
                                      });
    }


    public void addToTableParsingRequestQueue(String dossierId, String fileId) {

        fileStatusPersistenceService.updateProcessingStatus(fileId, ProcessingStatus.TABLE_PARSING_ANALYZING);

        rabbitTemplate.convertAndSend(MessagingConfiguration.CV_ANALYSIS_REQUEST_EXCHANGE,
                                      TenantContext.getTenantId(),
                                      CvAnalysisServiceRequest.builder()
                                              .dossierId(dossierId)
                                              .fileId(fileId)
                                              .operation(CvAnalysisServiceRequest.OPERATION_TABLE_PARSING)
                                              .targetFileExtension(CvAnalysisServiceRequest.TARGET_FILE_EXTENSION)
                                              .responseFileExtension(CvAnalysisServiceRequest.TABLE_PARSING_FILE_EXTENSION)
                                              .build(),
                                      message -> {
                                          message.getMessageProperties().setPriority(1);
                                          return message;
                                      });
    }


    public void addToImageQueue(String dossierId, String fileId) {

        setStatusImageAnalyzing(fileId);
        rabbitTemplate.convertAndSend(MessagingConfiguration.IMAGE_REQUEST_EXCHANGE,
                                      TenantContext.getTenantId(),
                                      ImageServiceRequest.builder()
                                              .dossierId(dossierId)
                                              .fileId(fileId)
                                              .targetFileExtension(ImageServiceRequest.TARGET_FILE_EXTENSION)
                                              .responseFileExtension(ImageServiceRequest.RESPONSE_FILE_EXTENSION)
                                              .build(),
                                      message -> {
                                          message.getMessageProperties().setPriority(1);
                                          return message;
                                      });
    }


    public void setStatusOcrQueued(String dossierId, String fileId, boolean allPages) {

        FileEntity fileStatus = fileStatusPersistenceService.getStatus(fileId);

        if (fileStatus.isExcluded()) {
            log.debug("File {} is excluded", fileId);
            return;
        }

        updateOCRStartTime(fileId);
        fileStatusPersistenceService.updateProcessingStatus(fileId, ProcessingStatus.OCR_PROCESSING_QUEUED);
        websocketService.sendAnalysisEvent(dossierId, fileId, AnalyseStatus.OCR_PROCESSING, fileStatus.getNumberOfAnalyses() + 1);
        addToOcrQueue(dossierId, fileId, 2, allPages);
    }


    protected void addToNerQueue(String dossierTemplateId, String dossierId, String fileId) {

        setStatusNerAnalyzing(fileId);
        rabbitTemplate.convertAndSend(MessagingConfiguration.ENTITY_REQUEST_EXCHANGE,
                                      TenantContext.getTenantId(),
                                      NerServiceRequest.builder()
                                              .dossierTemplateId(dossierTemplateId)
                                              .dossierId(dossierId)
                                              .fileId(fileId)
                                              .targetFileExtension(NerServiceRequest.TARGET_FILE_EXTENSION)
                                              .responseFileExtension(NerServiceRequest.RESPONSE_FILE_EXTENSION)
                                              .build(),
                                      message -> {
                                          message.getMessageProperties().setPriority(1);
                                          return message;
                                      });
    }


    protected boolean addToLLMNerQueue(String dossierTemplateId, String dossierId, String fileId) {

        List<TypeEntity> relevantTypesForAiCreation = dictionaryPersistenceService.getTypesForAiCreation(dossierTemplateId);
        long aiCreationVersion = toAiCreationVersion(relevantTypesForAiCreation);
        List<EntityAiDescription> entityAiDescriptions = relevantTypesForAiCreation.stream()
                .map(te -> new EntityAiDescription(te.getType(), te.getAiDescription()))
                .toList();

        if (entityAiDescriptions.isEmpty()) {
            return false;
        }

        setStatusNerAnalyzing(fileId);
        boolean protoFilesExist = fileManagementStorageService.objectExists(dossierId, fileId, FileType.DOCUMENT_STRUCTURE);

        BiFunction<FileType, FileType, String> getStorageId = (newFileType, oldFileType) -> protoFilesExist ? //
                StorageIdUtils.getStorageId(dossierId, fileId, newFileType) : StorageIdUtils.getStorageId(dossierId, fileId, newFileType.name(), oldFileType.getExtension());

        String chunksStorageId = StorageIdUtils.getStorageId(dossierId, fileId, FileType.DOCUMENT_CHUNKS);

        String documentPagesStorageId = getStorageId.apply(FileType.DOCUMENT_PAGES, FileType.DOCUMENT_PAGES_OLD);
        String documentStructureStorageId = getStorageId.apply(FileType.DOCUMENT_STRUCTURE, FileType.DOCUMENT_STRUCTURE_OLD);
        String documentTextStorageId = getStorageId.apply(FileType.DOCUMENT_TEXT, FileType.DOCUMENT_TEXT_OLD);
        String documentPositionStorageId = getStorageId.apply(FileType.DOCUMENT_POSITION, FileType.DOCUMENT_POSITION_OLD);

        String resultStorageId = StorageIdUtils.getStorageId(dossierId, fileId, FileType.LLM_NER_ENTITIES);

        rabbitTemplate.convertAndSend(MessagingConfiguration.LLM_NER_REQUEST_EXCHANGE,
                                      TenantContext.getTenantId(),
                                      LlmNerMessage.builder()
                                              .identifier(QueueMessageIdentifierService.buildIdentifier(dossierId, fileId, false))
                                              .entityAiDescriptions(entityAiDescriptions)
                                              .chunksStorageId(chunksStorageId)
                                              .documentPagesStorageId(documentPagesStorageId)
                                              .documentStructureStorageId(documentStructureStorageId)
                                              .documentTextStorageId(documentTextStorageId)
                                              .documentPositionStorageId(documentPositionStorageId)
                                              .resultStorageId(resultStorageId)
                                              .aiCreationVersion(aiCreationVersion)
                                              .build(),
                                      message -> {
                                          message.getMessageProperties().setPriority(1);
                                          return message;
                                      });
        return true;

    }


    private static long toAiCreationVersion(List<TypeEntity> relevantTypesForAiCreation) {

        return relevantTypesForAiCreation.stream()
                .mapToLong(TypeEntity::getAiCreationVersion).sum();
    }


    protected void addToAzureNerQueue(String dossierTemplateId, String dossierId, String fileId) {

        setStatusNerAnalyzing(fileId);
        rabbitTemplate.convertAndSend(MessagingConfiguration.AZURE_ENTITY_REQUEST_EXCHANGE,
                                      TenantContext.getTenantId(),
                                      NerServiceRequest.builder()
                                              .dossierTemplateId(dossierTemplateId)
                                              .dossierId(dossierId)
                                              .fileId(fileId)
                                              .targetFileExtension(NerServiceRequest.TARGET_FILE_EXTENSION)
                                              .responseFileExtension(NerServiceRequest.AZURE_RESPONSE_FILE_EXTENSION)
                                              .build(),
                                      message -> {
                                          message.getMessageProperties().setPriority(1);
                                          return message;
                                      });
    }


    private MessageType calculateMessageType(boolean reanalyse, ProcessingStatus processingStatus, FileModel fileModel) {

        if (ProcessingStatus.NER_ANALYZING.equals(processingStatus)) {
            return MessageType.ANALYSE;
        }

        if (reanalyse) {
            if (fileModel.getLastFileAttributeChange() != null && fileModel.getLastProcessed().isBefore(fileModel.getLastFileAttributeChange())) {
                return MessageType.ANALYSE;
            } else {
                return MessageType.REANALYSE;
            }
        }

        return MessageType.ANALYSE;
    }


    private List<ComponentDefinition> convertComponentDefinitions(List<ComponentDefinitionEntity> componentDefinitionEntities) {

        return componentDefinitionEntities.stream()
                .map(entity -> MagicConverter.convert(entity, ComponentDefinition.class))
                .sorted(Comparator.comparingInt(ComponentDefinition::getRank))
                .collect(Collectors.toList());
    }


    public List<FileAttribute> convertAttributes(List<FileAttributeEntity> fileAttributes, String dossierTemplateId) {

        List<FileAttribute> fileAttributeList = new ArrayList<>();

        var fileAttributeConfigs = fileAttributeConfigPersistenceService.getFileAttributes(dossierTemplateId);

        for (FileAttributeEntity fileAttribute : fileAttributes) {

            var config = fileAttributeConfigs.stream()
                    .filter(fac -> fac.getId().equals(fileAttribute.getFileAttributeId().getFileAttributeConfigId()))
                    .findFirst()
                    .orElseThrow(() -> new InternalServerErrorException("File Attribute config not defined for attribute :" + fileAttribute.getFileAttributeId()));

            var attribute = FileAttribute.builder()
                    .id(fileAttribute.getFileAttributeId().getFileAttributeConfigId())
                    .label(config.getLabel())
                    .placeholder(config.getPlaceholder())
                    .value(fileAttribute.getValue())
                    .build();
            fileAttributeList.add(attribute);
        }

        return fileAttributeList;
    }


    public void setStatusProcessing(String fileId) {

        fileStatusPersistenceService.updateProcessingStatus(fileId, ProcessingStatus.PROCESSING);
    }


    public void setStatusFullProcessing(String fileId) {

        fileStatusPersistenceService.updateProcessingStatus(fileId, ProcessingStatus.FULL_PROCESSING);
    }


    public void setStatusPreProcessingQueued(String fileId) {

        fileStatusPersistenceService.updateProcessingStatus(fileId, ProcessingStatus.PRE_PROCESSING_QUEUED);
    }


    public void setStatusImageAnalyzing(String fileId) {

        fileStatusPersistenceService.updateProcessingStatus(fileId, ProcessingStatus.IMAGE_ANALYZING);
    }


    public void updateOCRStartTime(String fileId) {

        fileStatusPersistenceService.updateOCRStartTime(fileId, OffsetDateTime.now());
    }


    public void addToOcrQueue(String dossierId, String fileId, int priority, boolean allPages) {

        var removeWatermark = dossierTemplatePersistenceService.getDossierTemplate(dossierPersistenceService.getDossierTemplateId(dossierId)).isRemoveWatermark();
        Set<AzureOcrFeature> features = new HashSet<>();
        if (removeWatermark) {
            features.add(AzureOcrFeature.REMOVE_WATERMARKS);
        }
        if (allPages) {
            features.add(AzureOcrFeature.ALL_PAGES);
        }
        if (currentApplicationTypeProvider.isDocuMine()) {
            features.add(AzureOcrFeature.ROTATION_CORRECTION);
            features.add(AzureOcrFeature.FONT_STYLE_DETECTION);
        }

        var request = DocumentRequest.builder()
                .dossierId(dossierId)
                .fileId(fileId)
                .removeWatermark(features.contains(AzureOcrFeature.REMOVE_WATERMARKS)) // needed for legacy OCR-services
                .features(features)
                .build();

        rabbitTemplate.convertAndSend(MessagingConfiguration.OCR_REQUEST_EXCHANGE, TenantContext.getTenantId(), request, message -> {
            message.getMessageProperties().setPriority(priority);
            return message;
        });
    }


    public void setStatusNerAnalyzing(String fileId) {

        fileStatusPersistenceService.updateProcessingStatus(fileId, ProcessingStatus.NER_ANALYZING);
    }


    public void setExcludedPages(String fileId, Set<Integer> excludedPages) {

        fileStatusPersistenceService.setExcludedPages(fileId, excludedPages);
    }


    public void setStatusSuccessful(String dossierId, String fileId, AnalyzeResult analyzeResult) {

        fileStatusPersistenceService.updateProcessingStatus(dossierId,
                                                            fileId,
                                                            analyzeResult.getNumberOfPages(),
                                                            analyzeResult.getDictionaryVersion(),
                                                            analyzeResult.getRulesVersion(),
                                                            analyzeResult.getComponentRulesVersion(),
                                                            analyzeResult.getDateFormatsVersion(),
                                                            analyzeResult.getLegalBasisVersion(),
                                                            analyzeResult.getDuration(),
                                                            analyzeResult.getDossierDictionaryVersion(),
                                                            analyzeResult.getAnalysisVersion(),
                                                            analyzeResult.getAnalysisNumber(),
                                                            analyzeResult.getUsedComponentMappings());
    }


    public void setStatusSuccessful(String fileId, WorkflowStatus newWorkflowStatus, WorkflowStatus oldWorkflowStatus) {

        fileStatusPersistenceService.updateWorkflowStatus(fileId, newWorkflowStatus, false);

        if (oldWorkflowStatus == WorkflowStatus.APPROVED && newWorkflowStatus != WorkflowStatus.APPROVED) {
            fileStatusPersistenceService.clearLastDownload(fileId);
        }
    }


    public void setApprovalStatusSuccessful(String dossierId, String fileId, WorkflowStatus workflowStatus) {

        fileStatusPersistenceService.updateWorkflowStatus(fileId, workflowStatus, true);
        websocketService.sendFileEvent(dossierId, fileId, FileEventType.UPDATE);
    }


    @Transactional
    public void setStatusReprocess(String dossierId, String fileId, boolean priority) {

        setStatusReprocess(dossierId, fileId, priority, Sets.newHashSet(), false, AnalysisType.DEFAULT);
    }


    @Transactional
    public void setStatusReprocess(String dossierId, String fileId, boolean priority, Set<Integer> sectionsToReanalyse, boolean triggeredManually, AnalysisType analysisType) {

        log.info("Reprocessing file: {} from dossier {}", fileId, dossierId);

        FileEntity fileStatus = fileStatusPersistenceService.getStatus(fileId);

        if (fileStatus.isExcluded()) {
            log.debug("File {} is excluded", fileStatus.getId());
            return;
        }

        if (fileStatus.isExcludedFromAutomaticAnalysis() && !triggeredManually) {
            log.debug("File {} is excluded from automatic analysis", fileStatus.getId());
            return;
        }

        fileStatusPersistenceService.updateProcessingStatus(fileId, ProcessingStatus.REPROCESS);
        addToAnalysisQueue(dossierId, fileId, priority, sectionsToReanalyse, analysisType);
    }


    @Transactional
    public void setStatusReprocessForManual(String dossierId, String fileId, boolean priority) {

        setStatusReprocess(dossierId, fileId, priority, Sets.newHashSet(), false, AnalysisType.MANUAL_REDACTION_REANALYZE);
    }


    @Transactional
    public void setStatusReprocessForComponentsOnly(String dossierId, String fileId, boolean priority) {

        setStatusReprocess(dossierId, fileId, priority, Sets.newHashSet(), false, AnalysisType.COMPONENTS_ONLY_REANALYZE);
    }


    @Transactional
    public void setStatusReprocess(String dossierId, String fileId, boolean priority, boolean triggeredManually) {

        setStatusReprocess(dossierId, fileId, priority, Sets.newHashSet(), triggeredManually, priority ? AnalysisType.MANUAL_REDACTION_REANALYZE : AnalysisType.DEFAULT);
    }


    @Transactional
    public void createStatus(String dossierId, String fileId, String uploader, String filename, long size, boolean disableAutomaticAnalysis) {

        log.info("NNNN: Creating status for {}", fileId);
        fileStatusPersistenceService.createStatus(dossierId, fileId, filename, uploader, size, disableAutomaticAnalysis);
        log.info("NNNN: Websocket");
        websocketService.sendFileEvent(dossierId, fileId, FileEventType.CREATE);
        log.info("NNNN: AnalysisQueue");
        addToAnalysisQueue(dossierId, fileId, false, Set.of(), AnalysisType.DEFAULT);
    }


    public void setStatusIndexingSuccessful(String fileId) {

        fileStatusPersistenceService.setUpdateStatusIndexingSuccessful(fileId);
        FileEntity fileStatus = fileStatusPersistenceService.getStatus(fileId);
        websocketService.sendFileEvent(fileStatus.getDossierId(), fileId, FileEventType.UPDATE);
    }


    public void setStatusIndexing(String fileId) {

        fileStatusPersistenceService.updateProcessingStatus(fileId, ProcessingStatus.INDEXING);
    }


    public void setStatusError(String dossierId, String fileId, FileErrorInfo fileErrorInfo) {

        fileStatusPersistenceService.updateStatusErrorInfo(fileId, fileErrorInfo);
        fileStatusPersistenceService.updateProcessingStatus(fileId, ProcessingStatus.ERROR);
        websocketService.sendAnalysisEvent(dossierId, fileId, AnalyseStatus.ERROR, 0);
    }


    public void updateFileModificationDate(String fileId, OffsetDateTime fileManipulationDate) {

        fileStatusPersistenceService.updateFileModificationDate(fileId, fileManipulationDate);
        FileEntity fileStatus = fileStatusPersistenceService.getStatus(fileId);
        websocketService.sendFileEvent(fileStatus.getDossierId(), fileId, FileEventType.UPDATE);
    }


    public void updateHasHighlights(String fileId, boolean hasHighlights) {

        fileStatusPersistenceService.updateHasHighlights(fileId, hasHighlights);
        FileEntity fileStatus = fileStatusPersistenceService.getStatus(fileId);
        websocketService.sendFileEvent(fileStatus.getDossierId(), fileId, FileEventType.UPDATE);
    }


    public void setAssignee(String fileId, String assignee) {

        FileEntity fileStatus = fileStatusPersistenceService.getStatus(fileId);
        String lastReviewer = fileStatus.getLastReviewer();
        String lastApprover = fileStatus.getLastApprover();
        if (WorkflowStatus.UNDER_REVIEW.equals(fileStatus.getWorkflowStatus())) {
            lastReviewer = StringUtils.isNotEmpty(assignee) ? fileStatus.getAssignee() : lastReviewer;
        }
        if (WorkflowStatus.UNDER_APPROVAL.equals(fileStatus.getWorkflowStatus())) {
            lastApprover = StringUtils.isNotEmpty(assignee) ? fileStatus.getAssignee() : lastApprover;
        }

        fileStatusPersistenceService.setAssignee(fileId, assignee, lastReviewer, lastApprover);
        websocketService.sendFileEvent(fileStatus.getDossierId(), fileId, FileEventType.UPDATE);
    }


    @Transactional
    public void toggleExclusion(String dossierId, String fileId, boolean excluded) {

        fileStatusPersistenceService.toggleExclusion(fileId, excluded);
        FileEntity fileStatus = fileStatusPersistenceService.getStatus(fileId);
        websocketService.sendFileEvent(fileStatus.getDossierId(), fileId, FileEventType.UPDATE);

        if (!excluded) {
            setStatusFullReprocess(dossierId, fileId, false, true, false);
        }
    }


    @Transactional
    public void setStatusFullReprocess(String dossierId, String fileId, boolean priority, boolean requiresStructureAnalysis, boolean runOcr) {

        FileEntity fileStatus = fileStatusPersistenceService.getStatus(fileId);

        if (fileStatus.isExcluded()) {
            log.debug("File {} is excluded", fileStatus.getId());
            return;
        }

        if (requiresStructureAnalysis || runOcr) {
            log.info("Delete text and NER entities from file {} in dossier {}", fileId, dossierId);
            fileManagementStorageService.deleteDocumentAndNerObjects(dossierId, fileId);
        }

        if (runOcr) {
            fileStatusPersistenceService.resetOcrStartAndEndDate(fileId);
            setStatusOcrQueued(dossierId, fileId, false);
            return;
        }

        addToAnalysisQueue(dossierId, fileId, priority, Sets.newHashSet(), AnalysisType.DEFAULT);
    }


    @Transactional
    public void toggleAutomaticAnalysis(String dossierId, String fileId, boolean excludedFromAutomaticAnalysis) {

        fileStatusPersistenceService.toggleAutomaticAnalysis(fileId, excludedFromAutomaticAnalysis);
        FileEntity fileStatus = fileStatusPersistenceService.getStatus(fileId);
        websocketService.sendFileEvent(fileStatus.getDossierId(), fileId, FileEventType.UPDATE);

        if (!excludedFromAutomaticAnalysis) {
            setStatusFullReprocess(dossierId, fileId, false, true, false);
        }
    }


    public void setStatusPreProcessing(String fileId, int processingErrorCount) {

        fileStatusPersistenceService.updateProcessingStatus(fileId, ProcessingStatus.PRE_PROCESSING, processingErrorCount);
    }


    public void setStatusOcrProcessing(String fileId, int processingErrorCount) {

        fileStatusPersistenceService.updateProcessingStatus(fileId, ProcessingStatus.OCR_PROCESSING, processingErrorCount);
    }


    @Transactional
    public void overwriteFile(String dossierId, String fileId, String uploader, String filename, boolean keepManualRedactions, boolean disableAutomaticAnalysis) {

        fileManagementStorageService.deleteObject(dossierId, fileId, FileType.ORIGIN);

        fileManagementStorageService.deleteAllObjects(dossierId, fileId);

        if (keepManualRedactions) {
            fileStatusPersistenceService.overwriteFile(fileId, uploader, filename, true, disableAutomaticAnalysis);
        } else {
            fileStatusPersistenceService.overwriteFile(fileId, uploader, filename, false, disableAutomaticAnalysis);
            deleteManualRedactions(dossierId, fileId);
        }

        viewedPagesPersistenceService.deleteForFile(fileId);

        FileEntity fileStatus = fileStatusPersistenceService.getStatus(fileId);
        websocketService.sendFileEvent(fileStatus.getDossierId(), fileId, FileEventType.UPDATE);
        addToAnalysisQueue(dossierId, fileId, false, Set.of(), AnalysisType.DEFAULT);
    }


    private void deleteManualRedactions(String dossierId, String fileId) {

        OffsetDateTime now = OffsetDateTime.now();

        forceRedactionPersistenceService.softDeleteByFileIds(List.of(fileId), now);
        addRedactionPersistenceService.softDeleteByFileIds(List.of(fileId), now);
        removeRedactionPersistenceService.softDeleteByFileIds(List.of(fileId), now);
        recategorizationPersistenceService.softDeleteByFileIds(List.of(fileId), now);
        resizeRedactionPersistenceService.softDeleteByFileIds(List.of(fileId), now);
        legalBasisChangePersistenceService.softDeleteByFileIds(List.of(fileId), now);
        commentPersistenceService.softDeleteCommentsForFiles(List.of(fileId), now);

        fileStatusPersistenceService.updateHasComments(fileId, false);

        componentLogMongoService.deleteComponentLog(dossierId, fileId);
    }


    @Transactional
    public void setStatusAnalyse(String dossierId, String fileId, boolean priority) {

        FileEntity fileStatus = fileStatusPersistenceService.getStatus(fileId);

        if (fileStatus.isExcluded()) {
            log.debug("File {} is excluded", fileStatus.getId());
            return;
        }

        fileStatusPersistenceService.updateProcessingStatus(fileId, ProcessingStatus.ANALYSE);
        addToAnalysisQueue(dossierId, fileId, priority, Sets.newHashSet(), AnalysisType.DEFAULT);
    }


    @Transactional
    public void setStatusForceAnalyse(String dossierId, String fileId, boolean priority) {

        FileEntity fileStatus = fileStatusPersistenceService.getStatus(fileId);

        if (fileStatus.isExcluded()) {
            log.debug("File {} is excluded", fileStatus.getId());
            return;
        }

        fileStatusPersistenceService.updateProcessingStatus(fileId, ProcessingStatus.ANALYSE);
        addToAnalysisQueue(dossierId, fileId, priority, Sets.newHashSet(), AnalysisType.FORCE_ANALYSE);
    }


    public void updateLayoutParserVersionAndProcessedTime(String fileId, String version) {

        fileStatusPersistenceService.updateLayoutParserVersionAndProcessedTime(fileId, version);
    }


    public int countSoftDeletedFiles(String dossierId) {

        return fileStatusPersistenceService.countSoftDeletedFilesPerDossierId(dossierId);
    }


    public boolean hasChangesSince(String dossierId, OffsetDateTime since) {

        return fileStatusPersistenceService.hasChangesSince(dossierId, since);
    }


    public void updateOCRStatus(OCRStatusUpdateResponse response) {

        fileStatusPersistenceService.updateOCRStatus(response);
    }


    public String getFileName(String fileId) {

        return fileStatusPersistenceService.getFilenameById(fileId);
    }


    public void validateFileIsNotDeletedAndNotApproved(String fileId) {

        var file = fileStatusPersistenceService.getStatus(fileId);

        if (file.isSoftOrHardDeleted()) {
            throw new NotFoundException("File does not exist");
        }

        if (file.getWorkflowStatus() == WorkflowStatus.APPROVED) {
            throw new BadRequestException("Cannot analyse an approved file");
        }
    }


    @Transactional
    public void setStatusBulkLocalRedactionsProcessing(String fileId, AddRedactionBulkLocalRequestModel addRedactionBulkLocalRequestModel) {

        FileEntity fileStatus = fileStatusPersistenceService.getStatus(fileId);

        if (fileStatus.isExcluded()) {
            log.debug("File {} is excluded", fileStatus.getId());
            return;
        }

        fileStatusPersistenceService.updateProcessingStatus(fileId, ProcessingStatus.BULK_LOCAL_REDACTIONS_PROCESSING);
        BulkLocalRequest bulkLocalRequest = BulkLocalRequest.builder()
                .searchTerm(addRedactionBulkLocalRequestModel.getValue())
                .caseSensitive(addRedactionBulkLocalRequestModel.isCaseSensitive())
                .type(addRedactionBulkLocalRequestModel.getType())
                .reason(addRedactionBulkLocalRequestModel.getReason())
                .legalBasis(addRedactionBulkLocalRequestModel.getLegalBasis())
                .section(addRedactionBulkLocalRequestModel.getSection())
                .pageNumbers(addRedactionBulkLocalRequestModel.getPageNumbers())
                .comment(addRedactionBulkLocalRequestModel.getComment())
                .userId(KeycloakSecurity.getUserId())
                .build();
        addSearchTermOccurrencesAnalysisRequestToAnalysisQueue(fileStatus, bulkLocalRequest);
    }


    public List<FileModel> findAllByIds(Set<String> fileIds) {

        return fileStatusPersistenceService.findAllByIds(fileIds)
                .stream()
                .map(entity -> MagicConverter.convert(entity, FileModel.class, new FileModelMapper()))
                .collect(Collectors.toList());
    }


    public List<String> getDossierStatusIds(String dossierId, boolean includeDeleted) {

        return fileStatusPersistenceService.findAllByDossierId(dossierId, includeDeleted);
    }


    @Transactional
    public List<FileModel> findAllDossierIdAndIds(String dossierId, Set<String> fileIds) {

        var fileModels = fileStatusPersistenceService.findAllDossierIdAndIds(dossierId, fileIds)
                .stream()
                .map(entity -> MagicConverter.convert(entity, FileModel.class, new FileModelMapper()))
                .toList();

        return reanalysisRequiredStatusService.enhanceFileStatusWithAnalysisRequirements(fileModels);
    }

}
