package com.iqser.red.service.peristence.v1.server.integration.tests;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;

import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.testcontainers.shaded.com.google.common.collect.Lists;

import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.TypeEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.FileEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.CommentService;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierService;
import com.iqser.red.service.persistence.management.v1.processor.service.EntityLogMergeService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileManagementStorageService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.ManualRedactionProviderService;
import com.iqser.red.service.persistence.management.v1.processor.service.manualredactions.PendingEntryFactory;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DictionaryPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierTemplatePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileStatusPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AnalyzeRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.ChangeType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.Engine;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.EntityLog;
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.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.ManualChange;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.ManualRedactionType;
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.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.annotations.entitymapped.IdRemoval;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualForceRedaction;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualLegalBasisChange;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualRecategorization;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualRedactionEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.entitymapped.ManualResizeRedaction;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntryType;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.service.EntityLogMongoService;

@ExtendWith(SpringExtension.class)
public class EntityLogMergeTest {

    final String FILE_ID = "fileId";
    @MockBean
    private FileStatusService fileStatusService;

    @MockBean
    private FileManagementStorageService fileManagementStorageService;

    @MockBean
    private DossierService dossierService;

    @MockBean
    private CommentService commentService;

    @MockBean
    private ManualRedactionProviderService manualRedactionProviderService;

    @MockBean
    private DictionaryPersistenceService dictionaryPersistenceService;

    @MockBean
    private DossierTemplatePersistenceService dossierTemplatePersistenceService;

    @MockBean
    private RabbitTemplate rabbitTemplate;

    @MockBean
    private FileStatusPersistenceService fileStatusPersistenceService;

    @MockBean
    private EntityLogMongoService entityLogMongoService;

    @Captor
    private ArgumentCaptor<AnalyzeRequest> captor;

    private EntityLogMergeService entityLogMergeService;


    @BeforeEach
    public void setUp() {

        entityLogMergeService = new EntityLogMergeService(dictionaryPersistenceService, new PendingEntryFactory(), entityLogMongoService);
    }


    @Test
    public void testGetEntityLogWithUnprocessedRedactions() {

        String fileId = "fileId";
        String dossierId = "dossierId";
        String dossierTemplateId = "dossierTemplateId";

        String entryToAddId = UUID.randomUUID().toString();
        String rectangleToAddId = UUID.randomUUID().toString();
        String entryToRemoveId = UUID.randomUUID().toString();
        String entryToResizeId = UUID.randomUUID().toString();
        String entryLegalBasisId = UUID.randomUUID().toString();
        String forceRedactionId = UUID.randomUUID().toString();
        String entryToRecategorizeId = UUID.randomUUID().toString();

        ManualRedactions manualRedactions = provideManualRedactions(entryToAddId,
                                                                    entryToRemoveId,
                                                                    entryToResizeId,
                                                                    entryLegalBasisId,
                                                                    forceRedactionId,
                                                                    fileId,
                                                                    rectangleToAddId,
                                                                    entryToRecategorizeId);

        var entityLog = provideEntityLog(entryToRemoveId, entryToResizeId, entryLegalBasisId, forceRedactionId, entryToRecategorizeId, false);

        when(manualRedactionProviderService.getManualRedactions(any(), any())).thenReturn(manualRedactions);
        when(fileStatusService.getStatus(fileId)).thenReturn(FileModel.builder().excluded(false).dossierStatusId(dossierTemplateId).id(fileId).build());
        when(fileManagementStorageService.getEntityLog(dossierId, fileId)).thenReturn(entityLog);
        when(dossierService.getDossierById(dossierId)).thenReturn(DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());
        when(dictionaryPersistenceService.getType(anyString())).thenReturn(TypeEntity.builder().isHint(false).build());
        when(fileStatusPersistenceService.getStatus(fileId)).thenReturn(FileEntity.builder().id(fileId).fileAttributes(Collections.emptyList()).build());
        when(fileStatusService.convertAttributes(any(), anyString())).thenReturn(Collections.emptyList());

        EntityLog response = entityLogMergeService.mergeEntityLog(manualRedactions, entityLog, DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());

        assertNotNull(response);
        assertFalse(response.getEntityLogEntry().isEmpty());

        var optionalEntityLogEntry = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry1 -> entityLogEntry1.getId().equals(entryToAddId))
                .findFirst();
        assertTrue(optionalEntityLogEntry.isPresent());
        var entityLogEntry = optionalEntityLogEntry.get();
        assertEquals(entityLogEntry.getType(), "manual");
        assertEquals(entityLogEntry.getEntryType(), EntryType.ENTITY);
        assertEquals(entityLogEntry.getState(), EntryState.APPLIED);
        assertEquals(entityLogEntry.getValue(), "Test");
        assertEquals(entityLogEntry.getReason(), "Reason");
        assertTrue(entityLogEntry.getEngines().contains(Engine.MANUAL));
        assertEquals(entityLogEntry.getManualChanges()
                             .get(0).getManualRedactionType(), ManualRedactionType.ADD);

        var optionalRemoveEntryLogEntry = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry1 -> entityLogEntry1.getId().equals(entryToRemoveId))
                .findFirst();
        assertTrue(optionalRemoveEntryLogEntry.isPresent());
        var removeEntryLogEntry = optionalRemoveEntryLogEntry.get();
        assertEquals(removeEntryLogEntry.getEntryType(), EntryType.ENTITY);
        assertEquals(removeEntryLogEntry.getState(), EntryState.IGNORED);
        assertEquals(removeEntryLogEntry.getManualChanges()
                             .get(0).getManualRedactionType(), ManualRedactionType.REMOVE);
        assertEquals(removeEntryLogEntry.getChanges()
                             .get(0).getType(), ChangeType.IGNORED);
        assertFalse(removeEntryLogEntry.getEngines().contains(Engine.MANUAL));

        var optionalResizeEntryLogEntry = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry1 -> entityLogEntry1.getId().equals(entryToResizeId))
                .findFirst();
        assertTrue(optionalResizeEntryLogEntry.isPresent());
        var resizeEntryLogEntry = optionalResizeEntryLogEntry.get();
        assertEquals(resizeEntryLogEntry.getPositions()
                             .get(0).getRectangle()[0], 2);
        assertEquals(resizeEntryLogEntry.getPositions()
                             .get(0).getRectangle()[1], 2);
        assertEquals(resizeEntryLogEntry.getPositions()
                             .get(0).getRectangle()[2], 2);
        assertEquals(resizeEntryLogEntry.getPositions()
                             .get(0).getRectangle()[3], 2);
        assertEquals(resizeEntryLogEntry.getPositions()
                             .get(0).getPageNumber(), 1);
        assertEquals(resizeEntryLogEntry.getEntryType(), EntryType.ENTITY);
        assertEquals(resizeEntryLogEntry.getState(), EntryState.APPLIED);
        assertEquals(resizeEntryLogEntry.getManualChanges()
                             .get(0).getManualRedactionType(), ManualRedactionType.RESIZE);
        assertEquals(resizeEntryLogEntry.getChanges()
                             .get(0).getType(), ChangeType.RESIZED);
        assertEquals(resizeEntryLogEntry.getChanges()
                             .get(0).getPropertyChanges()
                             .get("value"), "Darth Vader -> Random");
        assertEquals(resizeEntryLogEntry.getChanges()
                             .get(0).getPropertyChanges()
                             .get("positions"), "[1.0, 1.0, 1.0, 1.0, 1] -> [2.0, 2.0, 2.0, 2.0, 1]");
//        assertTrue(resizeEntryLogEntry.getEngines().contains(Engine.MANUAL));

        var optionalLegalBasisEntryLogEntry = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry1 -> entityLogEntry1.getId().equals(entryLegalBasisId))
                .findFirst();
        assertTrue(optionalLegalBasisEntryLogEntry.isPresent());
        var legalBasisEntryLogEntry = optionalLegalBasisEntryLogEntry.get();
        assertEquals(legalBasisEntryLogEntry.getLegalBasis(), "New legal basis");
        assertEquals(legalBasisEntryLogEntry.getEntryType(), EntryType.ENTITY);
        assertEquals(legalBasisEntryLogEntry.getState(), EntryState.APPLIED);
        assertEquals(legalBasisEntryLogEntry.getManualChanges()
                             .get(0).getManualRedactionType(), ManualRedactionType.LEGAL_BASIS_CHANGE);
        assertEquals(legalBasisEntryLogEntry.getChanges()
                             .get(0).getType(), ChangeType.LEGAL_BASIS_CHANGE);
        assertEquals(legalBasisEntryLogEntry.getChanges()
                             .get(0).getPropertyChanges()
                             .get("legalBasis"), "legalBasis -> New legal basis");
//        assertTrue(legalBasisEntryLogEntry.getEngines().contains(Engine.MANUAL));

        var optionalForceRedactionEntryLogEntry = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry1 -> entityLogEntry1.getId().equals(forceRedactionId))
                .findFirst();
        assertTrue(optionalForceRedactionEntryLogEntry.isPresent());
        var forceRedactionEntryLogEntry = optionalForceRedactionEntryLogEntry.get();
        assertEquals(forceRedactionEntryLogEntry.getLegalBasis(), "Force");
        assertEquals(forceRedactionEntryLogEntry.getEntryType(), EntryType.ENTITY);
        assertEquals(forceRedactionEntryLogEntry.getState(), EntryState.APPLIED);
        assertEquals(forceRedactionEntryLogEntry.getManualChanges()
                             .get(0).getManualRedactionType(), ManualRedactionType.FORCE);
        assertEquals(forceRedactionEntryLogEntry.getChanges()
                             .get(0).getType(), ChangeType.FORCE_REDACT);
        assertEquals(forceRedactionEntryLogEntry.getChanges()
                             .get(0).getPropertyChanges()
                             .get("legalBasis"), "null -> Force");
        assertEquals(forceRedactionEntryLogEntry.getChanges()
                             .get(0).getPropertyChanges()
                             .get("state"), "SKIPPED -> APPLIED");
//        assertTrue(forceRedactionEntryLogEntry.getEngines().contains(Engine.MANUAL));

        var optionalRectangleEntryLogEntry = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry1 -> entityLogEntry1.getId().equals(rectangleToAddId))
                .findFirst();
        assertTrue(optionalRectangleEntryLogEntry.isPresent());
        var rectangleEntryLogEntry = optionalRectangleEntryLogEntry.get();
        assertEquals(rectangleEntryLogEntry.getType(), "manual");
        assertEquals(rectangleEntryLogEntry.getEntryType(), EntryType.AREA);
        assertEquals(rectangleEntryLogEntry.getState(), EntryState.APPLIED);
        assertEquals(rectangleEntryLogEntry.getValue(), "Test2");
        assertEquals(rectangleEntryLogEntry.getReason(), "Rectangle");
        assertEquals(rectangleEntryLogEntry.getManualChanges()
                             .get(0).getManualRedactionType(), ManualRedactionType.ADD);
        assertTrue(rectangleEntryLogEntry.getEngines().contains(Engine.MANUAL));

        var optionalRecategorizationEntryLogEntry = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry1 -> entityLogEntry1.getId().equals(entryToRecategorizeId))
                .findFirst();
        var recategorizationEntryLogEntry = optionalRecategorizationEntryLogEntry.get();
        assertEquals(recategorizationEntryLogEntry.getLegalBasis(), "New legal basis");
        assertEquals(recategorizationEntryLogEntry.getEntryType(), EntryType.ENTITY);
        assertEquals(recategorizationEntryLogEntry.getType(), "PII");
        assertEquals(recategorizationEntryLogEntry.getState(), EntryState.APPLIED);
        assertEquals(recategorizationEntryLogEntry.getManualChanges()
                             .get(0).getManualRedactionType(), ManualRedactionType.RECATEGORIZE);
        assertEquals(recategorizationEntryLogEntry.getChanges()
                             .get(0).getType(), ChangeType.RECATEGORIZE);
        assertEquals(recategorizationEntryLogEntry.getChanges()
                             .get(0).getPropertyChanges()
                             .get("legalBasis"), "legalBasis -> New legal basis");
        assertEquals(recategorizationEntryLogEntry.getChanges()
                             .get(0).getPropertyChanges()
                             .get("type"), "CBI_author -> PII");
//        assertTrue(recategorizationEntryLogEntry.getEngines().contains(Engine.MANUAL));
    }


    @Test
    public void testMergeEntityLogWithManualChangesOnDictRedaction() {

        String fileId = "fileId";
        String dossierId = "dossierId";
        String dossierTemplateId = "dossierTemplateId";

        String rectangleToAddId = UUID.randomUUID().toString();
        String entryToRemoveId = UUID.randomUUID().toString();
        String entryToResizeId = UUID.randomUUID().toString();
        String entryLegalBasisId = UUID.randomUUID().toString();
        String forceRedactionId = UUID.randomUUID().toString();
        String entryToRecategorizeId = UUID.randomUUID().toString();

        ManualRedactions manualRedactions = provideManualRedactions(entryLegalBasisId,
                                                                    entryToRemoveId,
                                                                    entryToResizeId,
                                                                    entryLegalBasisId,
                                                                    forceRedactionId,
                                                                    fileId,
                                                                    rectangleToAddId,
                                                                    entryToRecategorizeId);

        var entityLog = provideEntityLog(entryToRemoveId, entryToResizeId, entryLegalBasisId, forceRedactionId, entryToRecategorizeId, true);

        when(manualRedactionProviderService.getManualRedactions(any(), any())).thenReturn(manualRedactions);
        when(fileStatusService.getStatus(fileId)).thenReturn(FileModel.builder().excluded(false).dossierStatusId(dossierTemplateId).id(fileId).build());
        when(fileManagementStorageService.getEntityLog(dossierId, fileId)).thenReturn(entityLog);
        when(dossierService.getDossierById(dossierId)).thenReturn(DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());
        when(dictionaryPersistenceService.getType(anyString())).thenReturn(TypeEntity.builder().isHint(false).build());
        when(fileStatusPersistenceService.getStatus(fileId)).thenReturn(FileEntity.builder().id(fileId).fileAttributes(Collections.emptyList()).build());
        when(fileStatusService.convertAttributes(any(), anyString())).thenReturn(Collections.emptyList());

        EntityLog response = entityLogMergeService.mergeEntityLog(manualRedactions, entityLog, DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());

        assertNotNull(response);
        assertFalse(response.getEntityLogEntry().isEmpty());

        var optionalResizeEntryLogEntry = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry1 -> entityLogEntry1.getId().equals(entryToResizeId))
                .findFirst();
        assertTrue(optionalResizeEntryLogEntry.isPresent());
        assertEquals(EntryType.ENTITY, optionalResizeEntryLogEntry.get().getEntryType());

        var legalBasisEntries = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry1 -> entityLogEntry1.getId().equals(entryLegalBasisId))
                .toList();
        assertEquals(2, legalBasisEntries.size());
        assertEquals(EntryState.REMOVED, legalBasisEntries.get(0).getState());
        assertEquals(EntryState.APPLIED, legalBasisEntries.get(1).getState());

        var optionalForceRedactionEntryLogEntry = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry1 -> entityLogEntry1.getId().equals(forceRedactionId))
                .findFirst();
        assertTrue(optionalForceRedactionEntryLogEntry.isPresent());
        assertEquals(EntryState.APPLIED, optionalForceRedactionEntryLogEntry.get().getState());

        var optionalRemovedEntryLogEntry = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry1 -> entityLogEntry1.getId().equals(entryToRemoveId))
                .findFirst();
        assertTrue(optionalRemovedEntryLogEntry.isPresent());
        assertEquals(EntryState.IGNORED, optionalRemovedEntryLogEntry.get().getState());
    }


    @Test
    public void testUnprocessedDictChangesAreDirectlyAfterOriginEntry() {

        String fileId = "fileId";
        String dossierId = "dossierId";
        String dossierTemplateId = "dossierTemplateId";

        String entryToRemoveId = UUID.randomUUID().toString();
        String entryToResizeId = UUID.randomUUID().toString();
        String entryLegalBasisId = UUID.randomUUID().toString();
        String forceRedactionId = UUID.randomUUID().toString();
        String entryToRecategorizeId = UUID.randomUUID().toString();

        ManualRedactions manualRedactions = new ManualRedactions();
        List<Rectangle> positions = new ArrayList<>();
        positions.add(new Rectangle(2, 2, 2, 2, 1));
        manualRedactions.setResizeRedactions(Set.of(ManualResizeRedaction.builder()
                                                            .fileId("fileId")
                                                            .value("Random")
                                                            .annotationId(entryToResizeId)
                                                            .positions(positions)
                                                            .requestDate(OffsetDateTime.now())
                                                            .updateDictionary(true)
                                                            .user("User")
                                                            .fileId("file")
                                                            .build()));

        var entityLog = provideEntityLog(entryToRemoveId, entryToResizeId, entryLegalBasisId, forceRedactionId, entryToRecategorizeId, false);

        when(manualRedactionProviderService.getManualRedactions(any(), any())).thenReturn(manualRedactions);
        when(fileStatusService.getStatus(fileId)).thenReturn(FileModel.builder().excluded(false).dossierStatusId(dossierTemplateId).id(fileId).build());
        when(fileManagementStorageService.getEntityLog(dossierId, fileId)).thenReturn(entityLog);
        when(dossierService.getDossierById(dossierId)).thenReturn(DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());
        when(dictionaryPersistenceService.getType(anyString())).thenReturn(TypeEntity.builder().isHint(false).build());
        when(fileStatusPersistenceService.getStatus(fileId)).thenReturn(FileEntity.builder().id(fileId).fileAttributes(Collections.emptyList()).build());
        when(fileStatusService.convertAttributes(any(), anyString())).thenReturn(Collections.emptyList());

        EntityLog response = entityLogMergeService.mergeEntityLog(manualRedactions, entityLog, DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());

        var resizedEntry = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry1 -> entityLogEntry1.getId().equals(entryToResizeId))
                .findFirst()
                .get();
        int index = response.getEntityLogEntry().indexOf(resizedEntry);
        assertEquals(response.getEntityLogEntry()
                             .get(index + 1).getId(), resizedEntry.getId());
        assertEquals(response.getEntityLogEntry()
                             .get(index + 1).getState(), EntryState.PENDING);
    }


    //dict entry, resize local
    @Test
    public void testMergeEntityLogWithManualResizeBasedOnDictRedaction() {

        String dossierId = "dossierId";
        String dossierTemplateId = "dossierTemplateId";

        String entryToRemoveId = UUID.randomUUID().toString();
        String dictEntryToResizeId = UUID.randomUUID().toString();
        String entryLegalBasisId = UUID.randomUUID().toString();
        String forceRedactionId = UUID.randomUUID().toString();
        String entryToRecategorizeId = UUID.randomUUID().toString();

        String localId = UUID.randomUUID().toString();

        ManualRedactions manualRedactions = ManualRedactions.builder()
                .entriesToAdd(Set.of(provideManualAdd(localId, "Darth Vader", dictEntryToResizeId)))
                .resizeRedactions(Set.of(provideManualResize(localId, "Darth")))
                .build();

        var entityLog = provideEntityLog(entryToRemoveId, dictEntryToResizeId, entryLegalBasisId, forceRedactionId, entryToRecategorizeId, true);

        when(manualRedactionProviderService.getManualRedactions(any(), any())).thenReturn(manualRedactions);
        when(fileStatusService.getStatus(FILE_ID)).thenReturn(FileModel.builder().excluded(false).dossierStatusId(dossierTemplateId).id(FILE_ID).build());
        when(fileManagementStorageService.getEntityLog(dossierId, FILE_ID)).thenReturn(entityLog);
        when(dossierService.getDossierById(dossierId)).thenReturn(DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());
        when(dictionaryPersistenceService.getType(anyString())).thenReturn(TypeEntity.builder().isHint(false).build());
        when(fileStatusPersistenceService.getStatus(FILE_ID)).thenReturn(FileEntity.builder().id(FILE_ID).fileAttributes(Collections.emptyList()).build());
        when(fileStatusService.convertAttributes(any(), anyString())).thenReturn(Collections.emptyList());

        EntityLog response = entityLogMergeService.mergeEntityLog(manualRedactions, entityLog, DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());

        assertNotNull(response);
        assertFalse(response.getEntityLogEntry().isEmpty());

        var optionalResizeEntryLogEntry = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry1 -> entityLogEntry1.getId().equals(dictEntryToResizeId))
                .findFirst();
        assertTrue(optionalResizeEntryLogEntry.isPresent());
        assertEquals(EntryType.ENTITY, optionalResizeEntryLogEntry.get().getEntryType());
        assertEquals(EntryState.REMOVED, optionalResizeEntryLogEntry.get().getState());

        var optionalResizeEntryLogEntry2 = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry -> entityLogEntry.getId().equals(localId))
                .findFirst();
        assertTrue(optionalResizeEntryLogEntry2.isPresent());
        assertEquals(EntryType.ENTITY, optionalResizeEntryLogEntry2.get().getEntryType());
        assertEquals(EntryState.APPLIED, optionalResizeEntryLogEntry2.get().getState());
        assertFalse(optionalResizeEntryLogEntry.get().getId().equals(optionalResizeEntryLogEntry2.get().getId()));
    }


    @Test
    public void testMergeEntityLogWithManualRecatBasedOnDictRedaction() {

        String dossierId = "dossierId";
        String dossierTemplateId = "dossierTemplateId";

        String entryToRemoveId = UUID.randomUUID().toString();
        String dictEntryToResizeId = UUID.randomUUID().toString();
        String entryLegalBasisId = UUID.randomUUID().toString();
        String forceRedactionId = UUID.randomUUID().toString();
        String entryToRecategorizeId = UUID.randomUUID().toString();

        String localId = UUID.randomUUID().toString();

        ManualRedactions manualRedactions = ManualRedactions.builder()
                .entriesToAdd(Set.of(provideManualAdd(localId, "Darth Vader", entryToRecategorizeId)))
                .recategorizations(Set.of(provideManualRecat(localId, "Darth Vader")))
                .build();

        var entityLog = provideEntityLog(entryToRemoveId, dictEntryToResizeId, entryLegalBasisId, forceRedactionId, entryToRecategorizeId, true);

        when(manualRedactionProviderService.getManualRedactions(any(), any())).thenReturn(manualRedactions);
        when(fileStatusService.getStatus(FILE_ID)).thenReturn(FileModel.builder().excluded(false).dossierStatusId(dossierTemplateId).id(FILE_ID).build());
        when(fileManagementStorageService.getEntityLog(dossierId, FILE_ID)).thenReturn(entityLog);
        when(dossierService.getDossierById(dossierId)).thenReturn(DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());
        when(dictionaryPersistenceService.getType(anyString())).thenReturn(TypeEntity.builder().isHint(false).build());
        when(fileStatusPersistenceService.getStatus(FILE_ID)).thenReturn(FileEntity.builder().id(FILE_ID).fileAttributes(Collections.emptyList()).build());
        when(fileStatusService.convertAttributes(any(), anyString())).thenReturn(Collections.emptyList());

        EntityLog response = entityLogMergeService.mergeEntityLog(manualRedactions, entityLog, DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());

        assertNotNull(response);
        assertFalse(response.getEntityLogEntry().isEmpty());

        var optionalRecatEntryLogEntry = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry1 -> entityLogEntry1.getId().equals(entryToRecategorizeId))
                .findFirst();
        assertTrue(optionalRecatEntryLogEntry.isPresent());
        assertEquals(EntryType.ENTITY, optionalRecatEntryLogEntry.get().getEntryType());
        assertEquals(EntryState.REMOVED, optionalRecatEntryLogEntry.get().getState());

        var optionalRecatEntryLogEntry2 = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry -> entityLogEntry.getId().equals(localId))
                .findFirst();
        assertTrue(optionalRecatEntryLogEntry2.isPresent());
        assertEquals(EntryType.ENTITY, optionalRecatEntryLogEntry2.get().getEntryType());
        assertEquals(EntryState.APPLIED, optionalRecatEntryLogEntry2.get().getState());
        assertFalse(optionalRecatEntryLogEntry.get().getId().equals(optionalRecatEntryLogEntry2.get().getId()));
    }


    //dict entry, resize local, remove local
    @Test
    public void testMergeEntityLogWithManualResizeAndRemoveChangesOnDictRedaction() {

        String dossierId = "dossierId";
        String dossierTemplateId = "dossierTemplateId";

        String entryToRemoveId = UUID.randomUUID().toString();
        String dictEntryToResizeId = UUID.randomUUID().toString();
        String entryLegalBasisId = UUID.randomUUID().toString();
        String forceRedactionId = UUID.randomUUID().toString();
        String entryToRecategorizeId = UUID.randomUUID().toString();

        String localId = UUID.randomUUID().toString();

        ManualRedactions manualRedactions = ManualRedactions.builder()
                .entriesToAdd(Set.of(provideManualAdd(localId, "Darth Vader", dictEntryToResizeId)))
                .resizeRedactions(Set.of(provideManualResize(localId, "Darth")))
                .idsToRemove(Set.of(IdRemoval.builder().annotationId(localId).requestDate(OffsetDateTime.now()).user("user").fileId(FILE_ID).build()))
                .build();

        var entityLog = provideEntityLog(entryToRemoveId, dictEntryToResizeId, entryLegalBasisId, forceRedactionId, entryToRecategorizeId, true);

        when(manualRedactionProviderService.getManualRedactions(any(), any())).thenReturn(manualRedactions);
        when(fileStatusService.getStatus(FILE_ID)).thenReturn(FileModel.builder().excluded(false).dossierStatusId(dossierTemplateId).id(FILE_ID).build());
        when(fileManagementStorageService.getEntityLog(dossierId, FILE_ID)).thenReturn(entityLog);
        when(dossierService.getDossierById(dossierId)).thenReturn(DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());
        when(dictionaryPersistenceService.getType(anyString())).thenReturn(TypeEntity.builder().isHint(false).build());
        when(fileStatusPersistenceService.getStatus(FILE_ID)).thenReturn(FileEntity.builder().id(FILE_ID).fileAttributes(Collections.emptyList()).build());
        when(fileStatusService.convertAttributes(any(), anyString())).thenReturn(Collections.emptyList());

        EntityLog response = entityLogMergeService.mergeEntityLog(manualRedactions, entityLog, DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());

        assertNotNull(response);
        assertFalse(response.getEntityLogEntry().isEmpty());

        var optionalResizeEntryLogEntry = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry1 -> entityLogEntry1.getId().equals(dictEntryToResizeId))
                .findFirst();
        assertTrue(optionalResizeEntryLogEntry.isPresent());
        assertEquals(EntryType.ENTITY, optionalResizeEntryLogEntry.get().getEntryType());
        assertEquals(EntryState.APPLIED, optionalResizeEntryLogEntry.get().getState());

        var optionalResizeEntryLogEntry2 = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry -> entityLogEntry.getId().equals(localId))
                .findFirst();
        assertTrue(optionalResizeEntryLogEntry2.isPresent());
        assertEquals(EntryType.ENTITY, optionalResizeEntryLogEntry2.get().getEntryType());
        assertEquals(EntryState.REMOVED, optionalResizeEntryLogEntry2.get().getState());
        assertFalse(optionalResizeEntryLogEntry.get().getId().equals(optionalResizeEntryLogEntry2.get().getId()));

    }


    @Test
    void testMergeEntityLogWithManualAddAndRemoveChanges() {

        String dossierId = "dossierId";
        String dossierTemplateId = "dossierTemplateId";

        String entryToRemoveId = UUID.randomUUID().toString();
        String dictEntryToResizeId = UUID.randomUUID().toString();
        String entryLegalBasisId = UUID.randomUUID().toString();
        String forceRedactionId = UUID.randomUUID().toString();
        String entryToRecategorizeId = UUID.randomUUID().toString();

        String localId = UUID.randomUUID().toString();

        ManualRedactions manualRedactions = ManualRedactions.builder()
                .entriesToAdd(Set.of(provideManualAdd(localId, "Darth Vader", null)))
                .idsToRemove(Set.of(IdRemoval.builder().annotationId(localId).requestDate(OffsetDateTime.now()).user("user").fileId(FILE_ID).build()))
                .build();

        var entityLog = provideEntityLog(entryToRemoveId, dictEntryToResizeId, entryLegalBasisId, forceRedactionId, entryToRecategorizeId, true);

        when(manualRedactionProviderService.getManualRedactions(any(), any())).thenReturn(manualRedactions);
        when(fileStatusService.getStatus(FILE_ID)).thenReturn(FileModel.builder().excluded(false).dossierStatusId(dossierTemplateId).id(FILE_ID).build());
        when(fileManagementStorageService.getEntityLog(dossierId, FILE_ID)).thenReturn(entityLog);
        when(dossierService.getDossierById(dossierId)).thenReturn(DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());
        when(dictionaryPersistenceService.getType(anyString())).thenReturn(TypeEntity.builder().isHint(false).build());
        when(fileStatusPersistenceService.getStatus(FILE_ID)).thenReturn(FileEntity.builder().id(FILE_ID).fileAttributes(Collections.emptyList()).build());
        when(fileStatusService.convertAttributes(any(), anyString())).thenReturn(Collections.emptyList());

        EntityLog response = entityLogMergeService.mergeEntityLog(manualRedactions, entityLog, DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());

        assertNotNull(response);
        assertFalse(response.getEntityLogEntry().isEmpty());

        var optionalEntityLogEntry = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry -> entityLogEntry.getId().equals(localId))
                .findFirst();
        assertTrue(optionalEntityLogEntry.isPresent());
        assertEquals(EntryType.ENTITY, optionalEntityLogEntry.get().getEntryType());
        assertEquals(EntryState.REMOVED, optionalEntityLogEntry.get().getState());

    }


    @Test
    void testMergeEntityLogWithManualAddAndResizeAndRemoveChanges() {

        String dossierId = "dossierId";
        String dossierTemplateId = "dossierTemplateId";

        String entryToRemoveId = UUID.randomUUID().toString();
        String dictEntryToResizeId = UUID.randomUUID().toString();
        String entryLegalBasisId = UUID.randomUUID().toString();
        String forceRedactionId = UUID.randomUUID().toString();
        String entryToRecategorizeId = UUID.randomUUID().toString();

        String localId = UUID.randomUUID().toString();

        ManualRedactions manualRedactions = ManualRedactions.builder()
                .entriesToAdd(Set.of(provideManualAdd(localId, "Darth Vader", dictEntryToResizeId)))
                .resizeRedactions(Set.of(provideManualResize(localId, "Darth")))
                .idsToRemove(Set.of(IdRemoval.builder().annotationId(localId).requestDate(OffsetDateTime.now()).user("user").fileId(FILE_ID).build()))
                .build();

        var entityLog = provideEntityLog(entryToRemoveId, dictEntryToResizeId, entryLegalBasisId, forceRedactionId, entryToRecategorizeId, true);

        when(manualRedactionProviderService.getManualRedactions(any(), any())).thenReturn(manualRedactions);
        when(fileStatusService.getStatus(FILE_ID)).thenReturn(FileModel.builder().excluded(false).dossierStatusId(dossierTemplateId).id(FILE_ID).build());
        when(fileManagementStorageService.getEntityLog(dossierId, FILE_ID)).thenReturn(entityLog);
        when(dossierService.getDossierById(dossierId)).thenReturn(DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());
        when(dictionaryPersistenceService.getType(anyString())).thenReturn(TypeEntity.builder().isHint(false).build());
        when(fileStatusPersistenceService.getStatus(FILE_ID)).thenReturn(FileEntity.builder().id(FILE_ID).fileAttributes(Collections.emptyList()).build());
        when(fileStatusService.convertAttributes(any(), anyString())).thenReturn(Collections.emptyList());

        EntityLog response = entityLogMergeService.mergeEntityLog(manualRedactions, entityLog, DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());

        assertNotNull(response);
        assertFalse(response.getEntityLogEntry().isEmpty());

        var optionalEntityLogEntry = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry -> entityLogEntry.getId().equals(localId))
                .findFirst();
        assertTrue(optionalEntityLogEntry.isPresent());
        assertEquals(EntryType.ENTITY, optionalEntityLogEntry.get().getEntryType());
        assertEquals(EntryState.REMOVED, optionalEntityLogEntry.get().getState());

    }


    @Test
    public void testForceAndRecategorizeImage() {

        String dossierId = "dossierId";
        String dossierTemplateId = "dossierTemplateId";

        String entryId = UUID.randomUUID().toString();

        ManualRedactions manualRedactions = ManualRedactions.builder()
                .recategorizations(Set.of(ManualRecategorization.builder()
                                                  .legalBasis("1")
                                                  .type("manual")
                                                  .annotationId(entryId)
                                                  .fileId("file")
                                                  .requestDate(OffsetDateTime.now())
                                                  .user("user")
                                                  .build()))
                .build();

        List<ManualChange> manualChanges = new ArrayList<>();
        manualChanges.add(ManualChange.builder()
                                  .manualRedactionType(ManualRedactionType.FORCE)
                                  .processedDate(OffsetDateTime.now())
                                  .propertyChanges(Collections.emptyMap())
                                  .userId("user")
                                  .requestedDate(OffsetDateTime.now())
                                  .build());
        var entityLog = new EntityLog(1,
                                      1,
                                      Lists.newArrayList(EntityLogEntry.builder()
                                                                 .id(entryId)
                                                                 .type("manual")
                                                                 .value("Image:Other")
                                                                 .entryType(EntryType.IMAGE)
                                                                 .state(EntryState.APPLIED)
                                                                 .dictionaryEntry(false)
                                                                 .positions(List.of(new Position(1, 1, 1, 1, 1)))
                                                                 .manualChanges(manualChanges)
                                                                 .build()),
                                      Collections.emptyList(),
                                      0,
                                      0,
                                      0,
                                      0);

        when(manualRedactionProviderService.getManualRedactions(any(), any())).thenReturn(manualRedactions);
        when(fileStatusService.getStatus(FILE_ID)).thenReturn(FileModel.builder().excluded(false).dossierStatusId(dossierTemplateId).id(FILE_ID).build());
        when(fileManagementStorageService.getEntityLog(dossierId, FILE_ID)).thenReturn(entityLog);
        when(dossierService.getDossierById(dossierId)).thenReturn(DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());
        when(dictionaryPersistenceService.getType(anyString())).thenReturn(TypeEntity.builder().isHint(false).build());
        when(fileStatusPersistenceService.getStatus(FILE_ID)).thenReturn(FileEntity.builder().id(FILE_ID).fileAttributes(Collections.emptyList()).build());
        when(fileStatusService.convertAttributes(any(), anyString())).thenReturn(Collections.emptyList());

        EntityLog response = entityLogMergeService.mergeEntityLog(manualRedactions, entityLog, DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());

        assertNotNull(response);
        assertFalse(response.getEntityLogEntry().isEmpty());

        var optionalEntityLogEntry = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry -> entityLogEntry.getId().equals(entryId))
                .findFirst();
        assertTrue(optionalEntityLogEntry.isPresent());
        assertEquals(EntryType.IMAGE, optionalEntityLogEntry.get().getEntryType());
        assertEquals(EntryState.APPLIED, optionalEntityLogEntry.get().getState());
    }


    @Test
    public void testIgnoreType() {

        String dossierId = "dossierId";
        String dossierTemplateId = "dossierTemplateId";

        String entryId = UUID.randomUUID().toString();

        ManualRedactions manualRedactions = ManualRedactions.builder()
                .idsToRemove(Set.of(IdRemoval.builder()
                                                  .annotationId(entryId)
                                                  .fileId("file")
                                                  .requestDate(OffsetDateTime.now())
                                                  .user("user")
                                                  .build()))
                .build();

        var entityLog = new EntityLog(1,
                                      1,
                                      Lists.newArrayList(EntityLogEntry.builder()
                                                                 .id(entryId)
                                                                 .type("CBI_author")
                                                                 .value("value")
                                                                 .entryType(EntryType.ENTITY)
                                                                 .state(EntryState.APPLIED)
                                                                 .dictionaryEntry(true)
                                                                 .positions(List.of(new Position(1, 1, 1, 1, 1)))
                                                                 .build()),
                                      Collections.emptyList(),
                                      0,
                                      0,
                                      0,
                                      0);

        when(manualRedactionProviderService.getManualRedactions(any(), any())).thenReturn(manualRedactions);
        when(fileStatusService.getStatus(FILE_ID)).thenReturn(FileModel.builder().excluded(false).dossierStatusId(dossierTemplateId).id(FILE_ID).build());
        when(fileManagementStorageService.getEntityLog(dossierId, FILE_ID)).thenReturn(entityLog);
        when(dossierService.getDossierById(dossierId)).thenReturn(DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());
        when(dictionaryPersistenceService.getType(anyString())).thenReturn(TypeEntity.builder().isHint(false).build());
        when(fileStatusPersistenceService.getStatus(FILE_ID)).thenReturn(FileEntity.builder().id(FILE_ID).fileAttributes(Collections.emptyList()).build());
        when(fileStatusService.convertAttributes(any(), anyString())).thenReturn(Collections.emptyList());

        EntityLog response = entityLogMergeService.mergeEntityLog(manualRedactions, entityLog, DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());

        assertNotNull(response);
        assertFalse(response.getEntityLogEntry().isEmpty());

        var optionalEntityLogEntry = response.getEntityLogEntry()
                .stream()
                .filter(entityLogEntry -> entityLogEntry.getId().equals(entryId))
                .findFirst();
        assertTrue(optionalEntityLogEntry.isPresent());
        assertEquals(optionalEntityLogEntry.get().getChanges().size(), 1);
        assertEquals(optionalEntityLogEntry.get().getChanges()
                             .get(0).getType(), ChangeType.IGNORED);
    }


    private EntityLog provideEntityLog(String entryToRemoveId,
                                       String entryToResizeId,
                                       String entryLegalBasisId,
                                       String forceRedactionId,
                                       String entryToRecategorizeId,
                                       boolean dictEntry) {

        List<Position> positions = new ArrayList<>();
        positions.add(new Position(1, 1, 1, 1, 1));
        return new EntityLog(1,
                             1,
                             Lists.newArrayList(EntityLogEntry.builder()
                                                        .id(entryToRemoveId)
                                                        .type("manual")
                                                        .value("Luke Skywalker")
                                                        .entryType(EntryType.ENTITY)
                                                        .state(EntryState.APPLIED)
                                                        .dictionaryEntry(dictEntry)
                                                        .build(),
                                                EntityLogEntry.builder()
                                                        .id(entryToResizeId)
                                                        .type("manual")
                                                        .value("Darth Vader")
                                                        .entryType(EntryType.ENTITY)
                                                        .state(EntryState.APPLIED)
                                                        .dictionaryEntry(dictEntry)
                                                        .positions(positions)
                                                        .build(),
                                                EntityLogEntry.builder()
                                                        .id(entryLegalBasisId)
                                                        .type("manual")
                                                        .value("Darth Luke")
                                                        .entryType(EntryType.ENTITY)
                                                        .state(EntryState.APPLIED)
                                                        .dictionaryEntry(dictEntry)
                                                        .positions(positions)
                                                        .legalBasis("legalBasis")
                                                        .build(),
                                                EntityLogEntry.builder()
                                                        .id(forceRedactionId)
                                                        .type("manual")
                                                        .value("Darth Luke")
                                                        .entryType(EntryType.ENTITY)
                                                        .state(EntryState.SKIPPED)
                                                        .dictionaryEntry(dictEntry)
                                                        .positions(positions)
                                                        .build(),
                                                EntityLogEntry.builder()
                                                        .id(entryToRecategorizeId)
                                                        .type("manual")
                                                        .value("Darth Luke")
                                                        .entryType(EntryType.ENTITY)
                                                        .state(EntryState.APPLIED)
                                                        .dictionaryEntry(dictEntry)
                                                        .positions(positions)
                                                        .legalBasis("legalBasis")
                                                        .type("CBI_author")
                                                        .build()),
                             Collections.emptyList(),
                             0,
                             0,
                             0,
                             0);
    }


    private ManualRedactions provideManualRedactions(String entryToAddId,
                                                     String entryToRemoveId,
                                                     String entryToResizeId,
                                                     String entryLegalBasisId,
                                                     String forceRedactionId,
                                                     String fileId,
                                                     String rectangleToAddId,
                                                     String entryToRecategorizeId) {

        List<Rectangle> positions = new ArrayList<>();
        positions.add(new Rectangle(2, 2, 2, 2, 1));
        return ManualRedactions.builder()
                .entriesToAdd(Set.of(ManualRedactionEntry.builder()
                                             .positions(List.of(new Rectangle(1f, 2f, 3f, 4f, 1)))
                                             .annotationId(entryToAddId)
                                             .value("Test")
                                             .reason("Reason")
                                             .addToDictionary(false)
                                             .addToDossierDictionary(false)
                                             .fileId(fileId)
                                             .rectangle(false)
                                             .fileId("file")
                                             .requestDate(OffsetDateTime.now())
                                             .dictionaryEntryType(DictionaryEntryType.ENTRY)
                                             .type("manual")
                                             .user("User")
                                             .build(),
                                     ManualRedactionEntry.builder()
                                             .positions(List.of(new Rectangle(5f, 6f, 7f, 8f, 1)))
                                             .annotationId(rectangleToAddId)
                                             .value("Test2")
                                             .reason("Rectangle")
                                             .addToDictionary(false)
                                             .addToDossierDictionary(false)
                                             .fileId(fileId)
                                             .rectangle(true)
                                             .requestDate(OffsetDateTime.now())
                                             .type("manual")
                                             .user("User")
                                             .fileId("file")
                                             .build()))
                .idsToRemove(Set.of(IdRemoval.builder().annotationId(entryToRemoveId).requestDate(OffsetDateTime.now()).user("user").fileId("file").build()))
                .resizeRedactions(Set.of(ManualResizeRedaction.builder()
                                                 .fileId(fileId)
                                                 .value("Random")
                                                 .annotationId(entryToResizeId)
                                                 .positions(positions)
                                                 .requestDate(OffsetDateTime.now())
                                                 .updateDictionary(false)
                                                 .addToAllDossiers(false)
                                                 .user("User")
                                                 .fileId("file")
                                                 .build()))
                .legalBasisChanges(Set.of(ManualLegalBasisChange.builder()
                                                  .annotationId(entryLegalBasisId)
                                                  .value("Random")
                                                  .legalBasis("New legal basis")
                                                  .user("User")
                                                  .section("Section")
                                                  .fileId("file")
                                                  .requestDate(OffsetDateTime.now())
                                                  .build()))
                .forceRedactions(Set.of(ManualForceRedaction.builder()
                                                .annotationId(forceRedactionId)
                                                .fileId(fileId)
                                                .legalBasis("Force")
                                                .requestDate(OffsetDateTime.now())
                                                .user("User")
                                                .fileId("file")
                                                .build()))
                .recategorizations(Set.of(ManualRecategorization.builder()
                                                  .annotationId(entryToRecategorizeId)
                                                  .fileId(fileId)
                                                  .legalBasis("New legal basis")
                                                  .requestDate(OffsetDateTime.now())
                                                  .user("User")
                                                  .type("PII")
                                                  .build()))
                .build();
    }


    private ManualRedactionEntry provideManualAdd(String entryToAddId, String valueToAdd, String sourceId) {

        return ManualRedactionEntry.builder()
                .positions(List.of(new Rectangle(1f, 2f, 3f, 4f, 1)))
                .annotationId(entryToAddId)
                .value(valueToAdd)
                .reason("Reason")
                .addToDictionary(false)
                .addToDossierDictionary(false)
                .fileId(FILE_ID)
                .rectangle(false)
                .requestDate(OffsetDateTime.now())
                .dictionaryEntryType(DictionaryEntryType.ENTRY)
                .type("manual")
                .user("User")
                .sourceId(sourceId)
                .build();
    }


    private ManualResizeRedaction provideManualResize(String entryToResizeId, String resizedValue) {

        List<Rectangle> positions = new ArrayList<>();
        positions.add(new Rectangle(2, 2, 2, 2, 1));
        return ManualResizeRedaction.builder()
                .fileId(FILE_ID)
                .value(resizedValue)
                .annotationId(entryToResizeId)
                .positions(positions)
                .requestDate(OffsetDateTime.now())
                .updateDictionary(false)
                .addToAllDossiers(false)
                .user("User")
                .fileId("file")
                .build();
    }

    private ManualRecategorization provideManualRecat(String entryToRecatId, String value) {

        return ManualRecategorization.builder()
                .fileId(FILE_ID)
                .annotationId(entryToRecatId)
                .value(value)
                .requestDate(OffsetDateTime.now())
                .section("")
                .addToDictionary(false)
                .addToAllDossiers(false)
                .user("User")
                .fileId("file")
                .build();
    }


    @Test
    public void testRecategorizeWithoutCreatingOverlap() {

        String fileId = "fileId";
        String dossierId = "dossierId";
        String dossierTemplateId = "dossierTemplateId";

        String entryId = UUID.randomUUID().toString();
        String dictId = UUID.randomUUID().toString();

        ManualRedactionEntry add = provideManualAdd(entryId, "Sensitive information", dictId);
        add.setType("CBI_author");
        ManualRedactions manualRedactions = ManualRedactions.builder()
                .entriesToAdd(Set.of(add))
                .recategorizations(Set.of(ManualRecategorization.builder()
                                                  .annotationId(entryId)
                                                  .fileId(fileId)
                                                  .legalBasis("New legal basis")
                                                  .type("PII")
                                                  .requestDate(OffsetDateTime.now())
                                                  .user("User")
                                                  .build()))
                .build();

        List<Position> positions = new ArrayList<>();
        positions.add(new Position(1, 1, 1, 1, 1));

        EntityLogEntry existingEntry = EntityLogEntry.builder()
                .id(dictId)
                .type("CBI_author")
                .value("Sensitive Information")
                .entryType(EntryType.ENTITY)
                .state(EntryState.APPLIED)
                .dictionaryEntry(true)
                .positions(positions)
                .legalBasis("Initial legal basis")
                .build();

        EntityLog entityLog = new EntityLog(1, 1, Lists.newArrayList(existingEntry), Collections.emptyList(), 0, 0, 0, 0);

        when(manualRedactionProviderService.getManualRedactions(any(), any())).thenReturn(manualRedactions);
        when(fileStatusService.getStatus(fileId)).thenReturn(FileModel.builder().excluded(false).dossierStatusId(dossierTemplateId).id(fileId).build());
        when(fileManagementStorageService.getEntityLog(dossierId, fileId)).thenReturn(entityLog);
        when(dossierService.getDossierById(dossierId)).thenReturn(DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());
        when(dictionaryPersistenceService.getType(anyString())).thenReturn(TypeEntity.builder().isHint(false).build());
        when(fileStatusPersistenceService.getStatus(fileId)).thenReturn(FileEntity.builder().id(fileId).fileAttributes(Collections.emptyList()).build());
        when(fileStatusService.convertAttributes(any(), anyString())).thenReturn(Collections.emptyList());

        EntityLog response = entityLogMergeService.mergeEntityLog(manualRedactions, entityLog, DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());

        assertNotNull(response, "The merged EntityLog should not be null.");
        assertFalse(response.getEntityLogEntry().isEmpty(), "The merged EntityLog should contain entries.");

        var updatedEntryOpt = response.getEntityLogEntry()
                .stream()
                .filter(e -> e.getId().equals(entryId))
                .findFirst();

        assertTrue(updatedEntryOpt.isPresent(), "The recategorized entry should be present in the EntityLog.");

        EntityLogEntry updatedEntry = updatedEntryOpt.get();

        assertEquals("PII", updatedEntry.getType(), "The entry type should be updated to PII.");

        assertEquals("New legal basis", updatedEntry.getLegalBasis(), "The legal basis should be updated.");

        assertEquals(EntryState.APPLIED, updatedEntry.getState(), "The entry state should remain APPLIED.");

        assertEquals(1,
                     response.getEntityLogEntry()
                             .stream()
                             .filter(entityLogEntry -> !entityLogEntry.getState().equals(EntryState.REMOVED))
                             .count(),
                     "There should be only one entry in the EntityLog after recategorization.");

    }


    @Test
    public void testForceRedactSkippedCBIAddressAndLocalRemoveWithoutCreatingDuplicate() {

        String fileId = "fileId";
        String dossierId = "dossierId";
        String dossierTemplateId = "dossierTemplateId";

        String entryId = UUID.randomUUID().toString();
        String dictId = UUID.randomUUID().toString();

        ManualRedactionEntry add = provideManualAdd(entryId, "123 Main St", dictId);
        add.setType("CBI_address");
        ManualRedactions manualRedactions = ManualRedactions.builder()
                .entriesToAdd(Set.of(add))
                .forceRedactions(Set.of(ManualForceRedaction.builder()
                                                .annotationId(entryId)
                                                .fileId(fileId)
                                                .legalBasis("Force")
                                                .user("User")
                                                .requestDate(OffsetDateTime.now())
                                                .build()))
                .idsToRemove(Set.of(IdRemoval.builder().annotationId(entryId).fileId(fileId).user("User").requestDate(OffsetDateTime.now()).build()))
                .build();

        List<Position> positions = new ArrayList<>();
        positions.add(new Position(1, 1, 1, 1, 1));

        EntityLogEntry existingEntry = EntityLogEntry.builder()
                .id(dictId)
                .type("CBI_address")
                .value("123 Main St")
                .entryType(EntryType.ENTITY)
                .state(EntryState.SKIPPED)
                .dictionaryEntry(true)
                .positions(positions)
                .legalBasis("Initial legal basis")
                .build();

        EntityLog entityLog = new EntityLog(1, 1, Lists.newArrayList(existingEntry), Collections.emptyList(), 0, 0, 0, 0);
        when(manualRedactionProviderService.getManualRedactions(any(), any())).thenReturn(manualRedactions);
        when(fileStatusService.getStatus(fileId)).thenReturn(FileModel.builder().excluded(false).dossierStatusId(dossierTemplateId).id(fileId).build());
        when(fileManagementStorageService.getEntityLog(dossierId, fileId)).thenReturn(entityLog);
        when(dossierService.getDossierById(dossierId)).thenReturn(DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());
        when(dictionaryPersistenceService.getType(anyString())).thenReturn(TypeEntity.builder().isHint(false).build());
        when(fileStatusPersistenceService.getStatus(fileId)).thenReturn(FileEntity.builder().id(fileId).fileAttributes(Collections.emptyList()).build());
        when(fileStatusService.convertAttributes(any(), anyString())).thenReturn(Collections.emptyList());

        EntityLog response = entityLogMergeService.mergeEntityLog(manualRedactions, entityLog, DossierEntity.builder().dossierTemplateId(dossierTemplateId).build());

        assertNotNull(response, "The merged EntityLog should not be null.");
        assertFalse(response.getEntityLogEntry().isEmpty(), "The merged EntityLog should contain entries.");

        List<EntityLogEntry> relatedEntries = response.getEntityLogEntry()
                .stream()
                .filter(e -> e.getId().equals(entryId))
                .toList();

        assertEquals(1, relatedEntries.size(), "There should be only one entry for the entryId after force redaction and local removal.");

        EntityLogEntry updatedEntry = relatedEntries.get(0);

        assertEquals("CBI_address", updatedEntry.getType(), "The entry type should remain CBI_address after force redaction and local removal.");

        long skippedAnnotations = response.getEntityLogEntry()
                .stream()
                .filter(e -> e.getType().equals("manual") && e.getEntryType() == EntryType.AREA && e.getState() == EntryState.SKIPPED)
                .count();

        assertEquals(0, skippedAnnotations, "No additional skipped annotations (Cross Marks) should be present.");

        var nonRemovedEntries = response.getEntityLogEntry()
                .stream()
                .filter(e -> !e.getId().equals(entryId) && !e.getState().equals(EntryState.REMOVED))
                .toList();

        assertEquals(1, nonRemovedEntries.size(), "There should be only one entry in the EntityLog after force and local remove.");
    }

}

