package com.iqser.red.persistence.service.v1.external.api.impl.controller;

import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.ADD_COMMENT;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.DELETE_COMMENT;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.DELETE_MANUAL_REDACTION;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.DO_MANUAL_REDACTION;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_MANUAL_REDACTIONS;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.mapper.EntityLogResponseMapper;
import com.iqser.red.service.persistence.management.v1.processor.model.ManualChangesQueryOptions;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.CommentService;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.ManualRedactionService;
import com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.ManualRedactionUndoService;
import com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.PendingEntryFactory;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DictionaryPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.TypeIdUtils;
import com.iqser.red.service.persistence.service.v1.api.external.resource.ManualRedactionResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.CommentResponse;
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.EntityLogEntryResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntryState;
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.AnnotationComments;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.Comment;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.CommentRequest;
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.ManualRedactionResponse;
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.Rectangle;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
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.AddCommentRequestModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.manual.AddRedactionBulkLocalRequestModel;
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.RecategorizationBulkLocalRequestModel;
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.RemoveRedactionBulkLocalRequestModel;
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.keycloakcommons.security.KeycloakSecurity;

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

@Slf4j
@RestController
@RequiredArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class ManualRedactionController implements ManualRedactionResource {

    static final String FILE_ID = "fileId";
    static final String DOSSIER_ID = "dossierId";
    static final String ANNOTATION_ID = "annotationId";

    ManualRedactionService manualRedactionService;
    ManualRedactionUndoService manualRedactionUndoService;
    DossierManagementService dossierManagementService;
    AuditPersistenceService auditPersistenceService;
    AccessControlService accessControlService;
    CommentService commentService;
    FileStatusManagementService fileStatusManagementService;
    FileStatusService fileStatusService;
    EntityLogMongoService entityLogMongoService;
    PendingEntryFactory pendingEntryFactory;
    DictionaryPersistenceService dictionaryPersistenceService;

    EntityLogController entityLogController;
    EntityLogResponseMapper mapper = EntityLogResponseMapper.INSTANCE;


    @PreAuthorize("hasAuthority('" + DELETE_MANUAL_REDACTION + "')")
    public void undo(@PathVariable(DOSSIER_ID) String dossierId,
                     @PathVariable(FILE_ID) String fileId,
                     @RequestBody Set<String> annotationIds,
                     @RequestParam(value = "includeOnlyUnprocessed", required = false, defaultValue = FALSE) boolean includeOnlyUnprocessed,
                     @RequestParam(value = "includeOnlyLocal", required = false, defaultValue = FALSE) boolean includeOnlyLocal) {

        accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
        accessControlService.verifyFileIsNotApproved(dossierId, fileId);
        accessControlService.verifyUserIsApprover(dossierId);
        manualRedactionUndoService.undo(dossierId, fileId, annotationIds, includeOnlyUnprocessed, includeOnlyLocal);

    }


    @PreAuthorize("hasAuthority('" + DELETE_COMMENT + "')")
    public void undoComment(@PathVariable(DOSSIER_ID) String dossierId,
                            @PathVariable(FILE_ID) String fileId,
                            @PathVariable(ANNOTATION_ID) String annotationId,
                            @PathVariable(COMMENT_ID) String commentId) {

        accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
        accessControlService.verifyFileIsNotApproved(dossierId, fileId);
        accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId);

        auditPersistenceService.audit(AuditRequest.builder()
                                              .userId(KeycloakSecurity.getUserId())
                                              .objectId(fileId)
                                              .category(AuditCategory.DOCUMENT.name())
                                              .message("Comment was removed.")
                                              .details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId))
                                              .build());
        commentService.deleteComment(fileId, List.of(Long.valueOf(commentId)));

    }


    @PreAuthorize("hasAuthority('" + READ_MANUAL_REDACTIONS + "')")
    public ManualRedactions getManualRedactions(@PathVariable(DOSSIER_ID) String dossierId,
                                                @PathVariable(FILE_ID) String fileId,
                                                @RequestParam(value = "unprocessed", required = false, defaultValue = FALSE) boolean unprocessed,
                                                @RequestParam(value = "includeDictChanges", required = false, defaultValue = TRUE) boolean includeDictChanges) {

        accessControlService.checkDossierExistenceAndViewPermissionsToDossier(dossierId);
        accessControlService.validateFileResourceExistence(fileId);

        return manualRedactionService.getManualRedactions(fileId,
                                                          ManualChangesQueryOptions.builder().includeOnlyUnprocessed(unprocessed).includeDictChanges(includeDictChanges).build());
    }


    @PreAuthorize("hasAuthority('" + READ_MANUAL_REDACTIONS + "')")
    public AnnotationComments getComments(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @PathVariable(ANNOTATION_ID) String annotationId) {

        dossierManagementService.getDossierById(dossierId, false, false);
        accessControlService.checkViewPermissionsToDossier(dossierId);
        fileStatusManagementService.getFileStatus(fileId, false);

        List<Comment> comments = commentService.getComments(fileId, annotationId);
        return new AnnotationComments(comments);
    }


    @PreAuthorize("hasAuthority('" + ADD_COMMENT + "')")
    public CommentResponse addComment(@PathVariable(DOSSIER_ID) String dossierId,
                                      @PathVariable(FILE_ID) String fileId,
                                      @PathVariable(ANNOTATION_ID) String annotationId,
                                      @RequestBody AddCommentRequestModel addCommentRequest) {

        accessControlService.checkAccessPermissionsToDossier(dossierId);
        accessControlService.verifyFileIsNotApproved(dossierId, fileId);
        accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId);

        var response = commentService.addComment(fileId, annotationId, CommentRequest.builder().user(KeycloakSecurity.getUserId()).text(addCommentRequest.getText()).build());

        auditPersistenceService.audit(AuditRequest.builder()
                                              .userId(KeycloakSecurity.getUserId())
                                              .objectId(fileId)
                                              .category(AuditCategory.DOCUMENT.name())
                                              .message("Comment was added.")
                                              .details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, annotationId))
                                              .build());

        return new CommentResponse(String.valueOf(response.getId()));
    }


    @PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
    public ManualRedactionResponse addRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
                                                    @PathVariable(FILE_ID) String fileId,
                                                    @RequestBody Set<AddRedactionRequestModel> addRedactionRequests) {

        var dossier = dossierManagementService.getDossierById(dossierId, false, false);
        verifyAccessForDossier(dossierId,
                               fileId,
                               addRedactionRequests.stream()
                                       .anyMatch(AddRedactionRequestModel::isAddToAllDossiers));

        List<ManualAnnotationResponse> responseList = manualRedactionService.addAddRedaction(dossierId, fileId, addRedactionRequests, dossier);
        responseList.forEach(response -> auditPersistenceService.audit(AuditRequest.builder()
                                                                               .userId(KeycloakSecurity.getUserId())
                                                                               .objectId(fileId)
                                                                               .category(AuditCategory.DOCUMENT.name())
                                                                               .message("Manual annotation was added.")
                                                                               .details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, response.getAnnotationId()))
                                                                               .build()));

        return ManualRedactionResponse.builder().manualAnnotationResponses(responseList).build();
    }


    @PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
    public ManualRedactionResponse addRedactionBulkLocal(String dossierId, String fileId, AddRedactionBulkLocalRequestModel addRedactionRequest) {

        verifyAccess(dossierId, fileId);

        // check if type exists before sending the request to redaction service
        var dossier = dossierManagementService.getDossierById(dossierId, false, false);
        dictionaryPersistenceService.getType(TypeIdUtils.toTypeId(addRedactionRequest.getType(), dossier.getDossierTemplateId()));

        if (!addRedactionRequest.isRectangle()) {
            fileStatusService.setStatusBulkLocalRedactionsProcessing(fileId, addRedactionRequest);

            EntityLogEntry entityLogEntry = pendingEntryFactory.buildAddRedactionBulkLocalEntry(addRedactionRequest);

            return ManualRedactionResponse.builder()
                    .manualAnnotationResponses(List.of(ManualAnnotationResponse.builder()
                                                               .annotationId(manualRedactionService.getNewAnnotationId())
                                                               .entityLogEntry(entityLogEntry)
                                                               .build()))
                    .build();
        } else {
            Set<AddRedactionRequestModel> addRedactionRequestModels = addRedactionRequest.getPageNumbers()
                    .stream()
                    .map(page -> AddRedactionRequestModel.builder()
                            .type(addRedactionRequest.getType())
                            .value(addRedactionRequest.getValue())
                            .reason(addRedactionRequest.getReason())
                            .legalBasis(addRedactionRequest.getLegalBasis())
                            .positions(addRedactionRequest.getPositions()
                                               .stream()
                                               .map(rectangle -> Rectangle.createRectangle(rectangle.getTopLeftX(),
                                                                                           rectangle.getTopLeftY(),
                                                                                           rectangle.getWidth(),
                                                                                           rectangle.getHeight(),
                                                                                           page))
                                               .toList())
                            .comment(addRedactionRequest.getComment() != null ? AddCommentRequestModel.builder().text(addRedactionRequest.getComment()).build() : null)
                            .section(addRedactionRequest.getSection())
                            .rectangle(true)
                            .build())
                    .collect(Collectors.toSet());
            return addRedactionBulk(dossierId, fileId, addRedactionRequestModels);
        }
    }


    @PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
    public ManualRedactionResponse removeRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
                                                       @PathVariable(FILE_ID) String fileId,
                                                       @RequestBody Set<RemoveRedactionRequestModel> removeRedactionRequests) {

        var dossier = dossierManagementService.getDossierById(dossierId, false, false);
        verifyAccessForDossier(dossierId,
                               fileId,
                               removeRedactionRequests.stream()
                                       .anyMatch(RemoveRedactionRequestModel::isRemoveFromAllDossiers));

        List<ManualAnnotationResponse> responseList = manualRedactionService.addRemoveRedaction(dossierId, fileId, removeRedactionRequests, dossier.getDossierTemplateId(), true);

        responseList.forEach(response -> auditPersistenceService.audit(AuditRequest.builder()
                                                                               .userId(KeycloakSecurity.getUserId())
                                                                               .objectId(fileId)
                                                                               .category(AuditCategory.DOCUMENT.name())
                                                                               .message("Annotation was manually removed")
                                                                               .details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, response.getAnnotationId()))
                                                                               .build()));

        return ManualRedactionResponse.builder().manualAnnotationResponses(responseList).build();
    }


    @PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
    public ManualRedactionResponse removeRedactionBulkLocal(String dossierId, String fileId, RemoveRedactionBulkLocalRequestModel removeRedactionRequest) {

        verifyAccess(dossierId, fileId);
        verifyRequest(removeRedactionRequest.isRectangle(), removeRedactionRequest.getPosition(), removeRedactionRequest.getValue());

        Set<RemoveRedactionRequestModel> removeRedactionRequestModels;
        FileModel status = fileStatusService.getStatus(fileId);

        Set<EntityLogEntry> entries;
        if (!status.isExcludedFromAutomaticAnalysis()) {
            entries = getFilteredEntityLogEntries(dossierId,
                                                  fileId,
                                                  removeRedactionRequest.isRectangle(),
                                                  removeRedactionRequest.getValue(),
                                                  removeRedactionRequest.isCaseSensitive(),
                                                  removeRedactionRequest.getOriginTypes(),
                                                  removeRedactionRequest.getOriginLegalBases(),
                                                  removeRedactionRequest.getPageNumbers(),
                                                  removeRedactionRequest.getPosition());

        } else {

            entries = new HashSet<>(mapper.fromLogEntryResponses(getFilteredEntityLogResponses(dossierId,
                                                                                               fileId,
                                                                                               removeRedactionRequest.isRectangle(),
                                                                                               removeRedactionRequest.getValue(),
                                                                                               removeRedactionRequest.isCaseSensitive(),
                                                                                               removeRedactionRequest.getOriginTypes(),
                                                                                               removeRedactionRequest.getOriginLegalBases(),
                                                                                               removeRedactionRequest.getPageNumbers(),
                                                                                               removeRedactionRequest.getPosition())));
        }

        removeRedactionRequestModels = entries.stream()
                .map(entityLogEntry -> RemoveRedactionRequestModel.builder().annotationId(entityLogEntry.getId()).comment(removeRedactionRequest.getComment()).build())
                .collect(Collectors.toSet());

        return removeRedactionBulk(dossierId, fileId, removeRedactionRequestModels);
    }


    @PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
    public ManualRedactionResponse forceRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
                                                      @PathVariable(FILE_ID) String fileId,
                                                      @RequestBody Set<ForceRedactionRequestModel> forceRedactionRequests) {

        verifyAccessAndDossierExistence(dossierId, fileId);

        List<ManualAnnotationResponse> responseList = manualRedactionService.addForceRedaction(dossierId, fileId, forceRedactionRequests);

        responseList.forEach(response -> auditPersistenceService.audit(AuditRequest.builder()
                                                                               .userId(KeycloakSecurity.getUserId())
                                                                               .objectId(fileId)
                                                                               .category(AuditCategory.DOCUMENT.name())
                                                                               .message("Skipped annotation was forced to be applied")
                                                                               .details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, response.getAnnotationId()))
                                                                               .build()));

        return ManualRedactionResponse.builder().manualAnnotationResponses(responseList).build();
    }


    @PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
    public ManualRedactionResponse legalBasisChangeBulk(@PathVariable(DOSSIER_ID) String dossierId,
                                                        @PathVariable(FILE_ID) String fileId,
                                                        @RequestBody Set<LegalBasisChangeRequestModel> legalBasisChangeRequests) {

        verifyAccessAndDossierExistence(dossierId, fileId);

        List<ManualAnnotationResponse> responseList = manualRedactionService.addLegalBasisChange(dossierId, fileId, legalBasisChangeRequests);

        responseList.forEach(response -> auditPersistenceService.audit(AuditRequest.builder()
                                                                               .userId(KeycloakSecurity.getUserId())
                                                                               .objectId(fileId)
                                                                               .category(AuditCategory.DOCUMENT.name())
                                                                               .message("Legal basis reason was changed")
                                                                               .details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, response.getAnnotationId()))
                                                                               .build()));

        return ManualRedactionResponse.builder().manualAnnotationResponses(responseList).build();
    }


    @PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
    public ManualRedactionResponse recategorizeBulk(@PathVariable(DOSSIER_ID) String dossierId,
                                                    @PathVariable(FILE_ID) String fileId,
                                                    @RequestBody Set<RecategorizationRequestModel> recategorizationRequests) {

        var dossier = dossierManagementService.getDossierById(dossierId, false, false);
        verifyAccess(dossierId, fileId);

        List<ManualAnnotationResponse> responseList = manualRedactionService.addRecategorization(dossierId, fileId, dossier, recategorizationRequests, true);

        responseList.forEach(response -> auditPersistenceService.audit(AuditRequest.builder()
                                                                               .userId(KeycloakSecurity.getUserId())
                                                                               .objectId(fileId)
                                                                               .category(AuditCategory.DOCUMENT.name())
                                                                               .message("Entity was recategorized.")
                                                                               .details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, response.getAnnotationId()))
                                                                               .build()));

        return ManualRedactionResponse.builder().manualAnnotationResponses(responseList).build();
    }


    @PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
    public ManualRedactionResponse recategorizeBulkLocal(String dossierId, String fileId, RecategorizationBulkLocalRequestModel recategorizationRequest) {

        verifyAccess(dossierId, fileId);
        verifyRequest(recategorizationRequest.isRectangle(), recategorizationRequest.getPosition(), recategorizationRequest.getValue());
        Set<RecategorizationRequestModel> recategorizationRequestModels;
        FileModel status = fileStatusService.getStatus(fileId);

        Set<EntityLogEntry> entries;
        if (!status.isExcludedFromAutomaticAnalysis()) {
            entries = getFilteredEntityLogEntries(dossierId,
                                                  fileId,
                                                  recategorizationRequest.isRectangle(),
                                                  recategorizationRequest.getValue(),
                                                  recategorizationRequest.isCaseSensitive(),
                                                  recategorizationRequest.getOriginTypes(),
                                                  recategorizationRequest.getOriginLegalBases(),
                                                  recategorizationRequest.getPageNumbers(),
                                                  recategorizationRequest.getPosition());

        } else {

            entries = new HashSet<>(mapper.fromLogEntryResponses(getFilteredEntityLogResponses(dossierId,
                                                                                               fileId,
                                                                                               recategorizationRequest.isRectangle(),
                                                                                               recategorizationRequest.getValue(),
                                                                                               recategorizationRequest.isCaseSensitive(),
                                                                                               recategorizationRequest.getOriginTypes(),
                                                                                               recategorizationRequest.getOriginLegalBases(),
                                                                                               recategorizationRequest.getPageNumbers(),
                                                                                               recategorizationRequest.getPosition())));
        }

        recategorizationRequestModels = entries.stream()
                .map(entry -> RecategorizationRequestModel.builder()
                        .annotationId(entry.getId())
                        .type(recategorizationRequest.isRectangle() ? entry.getType() : recategorizationRequest.getType())
                        .legalBasis(recategorizationRequest.getLegalBasis())
                        .section(recategorizationRequest.getSection())
                        .value(recategorizationRequest.isRectangle() ? recategorizationRequest.getValue() : entry.getValue())
                        .comment(recategorizationRequest.getComment())
                        .build())
                .collect(Collectors.toSet());

        return recategorizeBulk(dossierId, fileId, recategorizationRequestModels);
    }


    @PreAuthorize("hasAuthority('" + DO_MANUAL_REDACTION + "')")
    public ManualRedactionResponse resizeRedactionBulk(@PathVariable(DOSSIER_ID) String dossierId,
                                                       @PathVariable(FILE_ID) String fileId,
                                                       @RequestBody Set<ResizeRedactionRequestModel> resizeRedactionRequests) {

        verifyAccessAndDossierExistence(dossierId, fileId);

        List<ManualAnnotationResponse> responseList = manualRedactionService.addResizeRedaction(dossierId, fileId, resizeRedactionRequests, true);

        responseList.forEach(response -> auditPersistenceService.audit(AuditRequest.builder()
                                                                               .userId(KeycloakSecurity.getUserId())
                                                                               .objectId(fileId)
                                                                               .category(AuditCategory.DOCUMENT.name())
                                                                               .message("Skipped annotation was resized to be redacted")
                                                                               .details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, ANNOTATION_ID, response.getAnnotationId()))
                                                                               .build()));

        return ManualRedactionResponse.builder().manualAnnotationResponses(responseList).build();
    }


    private Set<EntityLogEntry> getFilteredEntityLogEntries(String dossierId,
                                                            String fileId,
                                                            boolean rectangle,
                                                            String value,
                                                            boolean caseSensitive,
                                                            Set<String> originTypes,
                                                            Set<String> originLegalBases,
                                                            Set<Integer> pageNumbers,
                                                            Position position) {

        Set<EntityLogEntry> entries;
        if (!rectangle) {
            entries = entityLogMongoService.findEntriesByValueWithFilters(dossierId, fileId, value, caseSensitive, originTypes, originLegalBases, pageNumbers);
        } else {
            entries = entityLogMongoService.findEntriesByMatchingFullPositionWithFilters(dossierId, fileId, position.getRectangle(), originTypes, originLegalBases, pageNumbers);
        }
        return entries;
    }


    private List<EntityLogEntryResponse> getFilteredEntityLogResponses(String dossierId,
                                                                       String fileId,
                                                                       boolean rectangle,
                                                                       String value,
                                                                       boolean caseSensitive,
                                                                       Set<String> originTypes,
                                                                       Set<String> originLegalBases,
                                                                       Set<Integer> pageNumbers,
                                                                       Position position) {

        List<EntityLogEntryResponse> entityLogEntryResponses = entityLogController.getEntityLog(dossierId, fileId, Collections.emptyList(), true).getEntityLogEntry()
                .stream()
                .filter(entityLogEntryResponse -> !entityLogEntryResponse.getState().equals(EntryState.PENDING))
                .collect(Collectors.toList());

        if (!rectangle) {
            entityLogEntryResponses = filterEntriesByValueWithFilters(entityLogEntryResponses, value, caseSensitive, originTypes, originLegalBases, pageNumbers);
        } else {
            entityLogEntryResponses = filterEntriesByMatchingPositionWithFilters(entityLogEntryResponses, position.getRectangle(), originTypes, originLegalBases, pageNumbers);
        }

        return entityLogEntryResponses;
    }


    public List<EntityLogEntryResponse> filterEntriesByValueWithFilters(List<EntityLogEntryResponse> entries,
                                                                        String value,
                                                                        boolean caseSensitive,
                                                                        Set<String> originTypes,
                                                                        Set<String> originLegalBases,
                                                                        Set<Integer> pageNumbers) {

        return entries.stream()
                .filter(entry -> !entry.getEntryType().equals(EntryType.AREA))
                .filter(entry -> filterByValue(entry, value, caseSensitive))
                .filter(entry -> filterByOriginType(entry, originTypes))
                .filter(entry -> filterByLegalBases(entry, originLegalBases))
                .filter(entry -> filterByPageNumbers(entry, pageNumbers))
                .collect(Collectors.toList());
    }


    public List<EntityLogEntryResponse> filterEntriesByMatchingPositionWithFilters(List<EntityLogEntryResponse> entries,
                                                                                   float[] rectangle,
                                                                                   Set<String> originTypes,
                                                                                   Set<String> originLegalBases,
                                                                                   Set<Integer> pageNumbers) {

        return entries.stream()
                .filter(entry -> entry.getEntryType().equals(EntryType.AREA))
                .filter(entry -> filterByRectangle(entry, rectangle))
                .filter(entry -> filterByOriginType(entry, originTypes))
                .filter(entry -> filterByLegalBases(entry, originLegalBases))
                .filter(entry -> filterByPageNumbers(entry, pageNumbers))
                .collect(Collectors.toList());
    }


    private boolean filterByValue(EntityLogEntryResponse entry, String value, boolean caseSensitive) {

        if (caseSensitive) {
            return entry.getValue().equals(value);
        } else {
            return entry.getValue().equalsIgnoreCase(value);
        }
    }


    private boolean filterByOriginType(EntityLogEntryResponse entry, Set<String> originTypes) {

        return originTypes == null || originTypes.isEmpty() || originTypes.contains(entry.getType());
    }


    private boolean filterByLegalBases(EntityLogEntryResponse entry, Set<String> originLegalBases) {

        return originLegalBases == null || originLegalBases.isEmpty() || originLegalBases.contains(entry.getLegalBasis());
    }


    private boolean filterByPageNumbers(EntityLogEntryResponse entry, Set<Integer> pageNumbers) {

        if (pageNumbers == null || pageNumbers.isEmpty()) {
            return true;
        }
        return entry.getPositions()
                .stream()
                .anyMatch(pos -> pageNumbers.contains(pos.getPageNumber()));
    }


    private boolean filterByRectangle(EntityLogEntryResponse entry, float[] rectangle) {

        if (rectangle == null || rectangle.length == 0) {
            return true;
        }
        return entry.getPositions()
                .stream()
                .anyMatch(pos -> Arrays.equals(pos.getRectangle(), rectangle));
    }


    private void verifyRequest(boolean isRectangle, Position position, String value) {

        if (isRectangle && position == null) {
            throw new BadRequestException("Position must be set for rectangle annotations.");
        }

        if (!isRectangle && value == null) {
            throw new BadRequestException("Value must be set.");
        }

    }


    private void verifyAccessForDossier(String dossierId, String fileId, boolean allDossiersAffected) {

        accessControlService.checkAccessPermissionsToDossier(dossierId);
        accessControlService.verifyFileIsNotApproved(dossierId, fileId);
        if (allDossiersAffected) {
            accessControlService.verifyUserIsApprover(dossierId);
        } else {
            accessControlService.verifyUserIsMemberOrApprover(dossierId);
        }
    }


    private void verifyAccess(String dossierId, String fileId) {

        accessControlService.checkAccessPermissionsToDossier(dossierId);
        accessControlService.verifyFileIsNotApproved(dossierId, fileId);
        accessControlService.verifyUserIsMemberOrApprover(dossierId);
    }


    private void verifyAccessAndDossierExistence(String dossierId, String fileId) {

        accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
        accessControlService.verifyFileIsNotApproved(dossierId, fileId);
        accessControlService.verifyUserIsMemberOrApprover(dossierId);
    }

}
