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

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;

import com.iqser.red.service.peristence.v1.server.integration.client.DictionaryClient;
import com.iqser.red.service.peristence.v1.server.integration.client.DossierClient;
import com.iqser.red.service.peristence.v1.server.integration.client.DossierTemplateClient;
import com.iqser.red.service.peristence.v1.server.integration.service.DossierTemplateTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.service.DossierTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.service.TypeProvider;
import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.DictionaryEntryEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.TypeEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.DictionaryManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.DictionaryService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.TypeRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.dictionaryentry.EntryRepository;
import com.iqser.red.service.persistence.service.v1.api.external.model.UpdateEntries;
import com.iqser.red.service.persistence.service.v1.api.shared.model.CreateTypeValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.TypeValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.UpdateTypeValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dictionary.Dictionary;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dictionary.DictionaryDifference;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dictionary.DictionaryDifferencePerType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dictionary.DictionaryDifferenceResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntryType;
import com.knecon.fforesight.tenantcommons.TenantContext;

import feign.FeignException;

public class DictionaryTest extends AbstractPersistenceServerServiceTest {

    @Autowired
    private TypeProvider typeProvider;

    @Autowired
    private DossierTemplateTesterAndProvider dossierTemplateTesterAndProvider;

    @Autowired
    private DossierTesterAndProvider dossierTesterAndProvider;

    @Autowired
    private DictionaryClient dictionaryClient;

    @Autowired
    private DictionaryManagementService dictionaryManagementService;

    @Autowired
    private DossierTemplateClient dossierTemplateClient;

    @Autowired
    private DossierClient dossierClient;

    @Autowired
    private TypeRepository typeRepository;

    @Autowired
    private EntryRepository entryRepository;

    private final List<String> testList1 = List.of("William c. Spare", "Charalampos", "Carina Wilson", "PATRICIA A. SHEEHY", "William C. Spare", "carlsen", "Patricia A. Sheehy");
    private final List<String> testList2 = List.of("William C. Spare", "Charalampos", "Carina Wilson", "Patricia A. Sheehy", "William c. Spare", "carlsen", "PATRICIA A. SHEEHY");


    @BeforeEach
    public void createDossierRedactionDictionary() {

        TenantContext.setTenantId("redaction");
    }


    @Test
    public void testAddTypesWithSameRank() {

        var dossier = dossierTesterAndProvider.provideTestDossier();

        var dossierTemplate = dossierTemplateClient.getDossierTemplate(dossier.getDossierTemplateId());

        var returnedtype1 = dictionaryClient.addType(CreateTypeValue.builder()
                                                             .type("test1")
                                                             .label("test1")
                                                             .hexColor("#fcba03")
                                                             .rank(100)
                                                             .hint(false)
                                                             .caseInsensitive(false)
                                                             .recommendation(false)
                                                             .addToDictionaryAction(true)
                                                             .dossierTemplateId(dossierTemplate.getId())
                                                             .dossierDictionaryOnly(false)
                                                             .build());
        assertThat(returnedtype1.getRank()).isEqualTo(100);

        Assertions.assertThrows(FeignException.Conflict.class,
                                () -> dictionaryClient.addType(CreateTypeValue.builder()
                                                                       .type("test2")
                                                                       .label("test2")
                                                                       .hexColor("#fcba03")
                                                                       .rank(100)
                                                                       .hint(false)
                                                                       .caseInsensitive(false)
                                                                       .recommendation(false)
                                                                       .addToDictionaryAction(true)
                                                                       .dossierTemplateId(dossierTemplate.getId())
                                                                       .dossierDictionaryOnly(false)
                                                                       .build()));

        var returnedtype2 = dictionaryClient.addType(CreateTypeValue.builder()
                                                             .type("test2")
                                                             .label("test2")
                                                             .hexColor("#fcba13")
                                                             .rank(50)
                                                             .hint(false)
                                                             .addToDictionaryAction(true)
                                                             .dossierTemplateId(dossierTemplate.getId())
                                                             .dossierDictionaryOnly(false)
                                                             .build());
        assertThat(returnedtype2.getRank()).isEqualTo(50);
        Assertions.assertThrows(FeignException.Conflict.class, () -> {
            var request = new UpdateTypeValue();
            BeanUtils.copyProperties(returnedtype1, request);
            request.setRank(50);
            dictionaryClient.updateType(returnedtype1.getType(), returnedtype1.getDossierTemplateId(), request);
        });

    }


    @Test
    public void testAddEntriesToDossierTemplateDictionaryWithDossierDictionaryOnlyFlag() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var type = typeProvider.testAndProvideType(dossierTemplate, null, "type1", true);
        assertThat(type.isDossierDictionaryOnly()).isTrue();

        var entries = List.of("word1", "word2");
        var falsePositives = List.of("false_positive1", "false_positive");
        var falseRecommendations = List.of("false_recommendation1", "false_recommendation2");
        Assertions.assertThrows(FeignException.Forbidden.class,
                                () -> dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), entries, false, null, DictionaryEntryType.ENTRY));
        Assertions.assertThrows(FeignException.Forbidden.class,
                                () -> dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), falsePositives, false, null, DictionaryEntryType.FALSE_POSITIVE));
        Assertions.assertThrows(FeignException.Forbidden.class,
                                () -> dictionaryClient.addEntry(type.getType(),
                                                                type.getDossierTemplateId(),
                                                                falseRecommendations,
                                                                false,
                                                                null,
                                                                DictionaryEntryType.FALSE_RECOMMENDATION));

        var loadedType1 = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);
        assertThat(loadedType1).isNotNull();
        assertThat(loadedType1.getEntries()).isEmpty();
        assertThat(loadedType1.getFalsePositiveEntries()).isEmpty();
        assertThat(loadedType1.getFalseRecommendationEntries()).isEmpty();

        Assertions.assertThrows(FeignException.Forbidden.class,
                                () -> dictionaryClient.deleteEntries(type.getType(), type.getDossierTemplateId(), entries, null, DictionaryEntryType.ENTRY));
        Assertions.assertThrows(FeignException.Forbidden.class,
                                () -> dictionaryClient.deleteEntries(type.getType(), type.getDossierTemplateId(), falsePositives, null, DictionaryEntryType.FALSE_POSITIVE));
        Assertions.assertThrows(FeignException.Forbidden.class,
                                () -> dictionaryClient.deleteEntries(type.getType(),
                                                                     type.getDossierTemplateId(),
                                                                     falseRecommendations,
                                                                     null,
                                                                     DictionaryEntryType.FALSE_RECOMMENDATION));
        Assertions.assertThrows(FeignException.Forbidden.class,
                                () -> dictionaryClient.deleteEntry(type.getType(), type.getDossierTemplateId(), entries.get(0), null, DictionaryEntryType.ENTRY));
        Assertions.assertThrows(FeignException.Forbidden.class,
                                () -> dictionaryClient.deleteEntry(type.getType(), type.getDossierTemplateId(), falsePositives.get(0), null, DictionaryEntryType.FALSE_POSITIVE));
        Assertions.assertThrows(FeignException.Forbidden.class,
                                () -> dictionaryClient.deleteEntry(type.getType(),
                                                                   type.getDossierTemplateId(),
                                                                   falseRecommendations.get(0),
                                                                   null,
                                                                   DictionaryEntryType.FALSE_RECOMMENDATION));
    }


    @Test
    public void testAddFalsePositiveAndFalseRecommendation() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var type = typeProvider.testAndProvideType(dossierTemplate);
        assertThat(type.getRecommendationHexColor()).isEqualTo("#aaaaaa");
        assertThat(type.getSkippedHexColor()).isEqualTo("#aaaaaa");
        assertThat(type.isDossierDictionaryOnly()).isFalse();

        dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), List.of("word1", "word2"), false, null, DictionaryEntryType.ENTRY);
        dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), List.of("false_positive1", "false_positive"), false, null, DictionaryEntryType.FALSE_POSITIVE);
        dictionaryClient.addEntry(type.getType(),
                                  type.getDossierTemplateId(),
                                  List.of("false_recommendation1", "false_recommendation2"),
                                  false,
                                  null,
                                  DictionaryEntryType.FALSE_RECOMMENDATION);

        var loadedType1 = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);

        assertThat(loadedType1.getEntries()).hasSize(2);
        assertThat(loadedType1.getFalsePositiveEntries()).hasSize(2);
        assertThat(loadedType1.getFalseRecommendationEntries()).hasSize(2);

    }


    @Test
    public void testSkippedColor() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var type = typeProvider.testAndProvideType(dossierTemplate);
        assertThat(type.getRecommendationHexColor()).isEqualTo("#aaaaaa");
        assertThat(type.getSkippedHexColor()).isEqualTo("#aaaaaa");

        var request = new UpdateTypeValue();
        BeanUtils.copyProperties(type, request);
        type.setSkippedHexColor(null);
        dictionaryClient.updateType(type.getType(), type.getDossierTemplateId(), request);

        var loadedType = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);
        assertThat(loadedType.getSkippedHexColor()).isNotNull();
    }


    @Test
    public void testAddEntriesWithStopWord() {

        var dossier = dossierTesterAndProvider.provideTestDossier();
        createDossierRedactionsDictionary(dossier.getDossierTemplateId());
        var typeId = "dossier_redaction";
        var entries = new ArrayList<String>();
        entries.add("age");
        entries.add("page");
        try {
            dictionaryClient.addEntry(typeId, dossier.getDossierTemplateId(), entries, false, dossier.getId(), DictionaryEntryType.ENTRY);
        } catch (FeignException e) {
            assertThat(e.status()).isEqualTo(400);
        }
    }


    @Test
    public void testAddEntriesWithStopWord2() {

        var dossier = dossierTesterAndProvider.provideTestDossier();
        createDossierRedactionsDictionary(dossier.getDossierTemplateId());
        var type = "dossier_redaction";

        var entries = new ArrayList<String>();
        entries.add("age");
        entries.add("p");

        try {
            dictionaryClient.addEntry(type, dossier.getDossierTemplateId(), entries, false, dossier.getId(), DictionaryEntryType.ENTRY);
        } catch (FeignException e) {
            assertThat(e.status()).isEqualTo(400);
        }
        entries.remove(1);
        entries.add("5");
        try {
            dictionaryClient.addEntry(type, dossier.getDossierTemplateId(), entries, false, dossier.getId(), DictionaryEntryType.ENTRY);
        } catch (FeignException e) {
            assertThat(e.status()).isEqualTo(400);
        }
        entries.remove(1);
        entries.add("12");
        try {
            dictionaryClient.addEntry(type, dossier.getDossierTemplateId(), entries, false, dossier.getId(), DictionaryEntryType.ENTRY);
        } catch (FeignException e) {
            assertThat(e.status()).isEqualTo(400);
        }
        entries.remove(1);
        entries.add(";");
        try {
            dictionaryClient.addEntry(type, dossier.getDossierTemplateId(), entries, false, dossier.getId(), DictionaryEntryType.ENTRY);
        } catch (FeignException e) {
            assertThat(e.status()).isEqualTo(400);
        }
        entries.remove(1);
        entries.add("設");
        dictionaryClient.addEntry(type, dossier.getDossierTemplateId(), entries, false, dossier.getId(), DictionaryEntryType.ENTRY);
    }


    @Test
    public void testAddWordMultipleTimes() {

        // Arrange
        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var type = typeProvider.testAndProvideType(dossierTemplate);
        var word = "Luke Skywalker";

        // Act & Assert: Add same word multiple times; Only one should exist
        var entries = new ArrayList<String>();
        entries.add(word);
        entries.add(word);
        dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), entries, false, null, DictionaryEntryType.ENTRY);

        var dictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);

        assertThat(dictionary.getEntries()).hasSize(1);
        assertThat(dictionary.getEntries()
                           .iterator().next()).isEqualTo(word);

        // Act & Assert: Add same word again; Only one should exist
        entries = new ArrayList<>();
        entries.add(word);
        dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), entries, false, null, DictionaryEntryType.ENTRY);

        dictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);
        assertThat(dictionary.getEntries()).hasSize(1);
        assertThat(dictionary.getEntries()
                           .iterator().next()).isEqualTo(word);

        // Act & Assert: Add same word multiple times again; Only one should exist
        entries = new ArrayList<>();
        entries.add(word);
        entries.add(word);
        entries.add(word);
        entries.add(word);
        dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), entries, false, null, DictionaryEntryType.ENTRY);

        dictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);
        assertThat(dictionary.getEntries()).hasSize(1);
        assertThat(dictionary.getEntries()
                           .iterator().next()).isEqualTo(word);
        assertThat((dictionary.isDossierDictionaryOnly())).isFalse();

        // Act & Assert: Delete word; Should have 'deleted' flag
        entries = new ArrayList<>();
        entries.add(word);
        dictionaryClient.deleteEntries(type.getType(), type.getDossierTemplateId(), entries, null, DictionaryEntryType.ENTRY);

        dictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);
        assertThat(dictionary.getEntries()).hasSize(0);
    }


    @Test
    public void testAddDifferentWordMultipleTimes() {

        // Arrange
        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var type = typeProvider.testAndProvideType(dossierTemplate);
        var word1 = "Luke Skywalker";
        var word2 = "Anakin Skywalker";
        var word3 = "Yoda";

        // Act & Assert: Add different words; All three should exist
        var entries = new ArrayList<String>();
        entries.add(word1);
        entries.add(word2);
        entries.add(word3);
        dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), entries, false, null, DictionaryEntryType.ENTRY);

        var dictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);

        assertThat(dictionary.getEntries()).hasSize(3);
        assertThat(dictionary.getEntries()).contains(word1, word2, word3);

        // Act & Assert: Add same word again; No duplicate should exist
        entries = new ArrayList<>();
        entries.add(word1);
        dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), entries, false, null, DictionaryEntryType.ENTRY);

        dictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);

        assertThat(dictionary.getEntries()).hasSize(3);
        assertThat(dictionary.getEntries()).contains(word1, word2, word3);

        // Act & Assert: Add words multiple times again; No duplicate should exist
        entries = new ArrayList<>();
        entries.add(word1);
        entries.add(word2);
        entries.add(word1);
        entries.add(word1);
        entries.add(word2);
        entries.add(word3);
        dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), entries, false, null, DictionaryEntryType.ENTRY);

        dictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);

        assertThat(dictionary.getEntries()).hasSize(3);
        assertThat(dictionary.getEntries()).contains(word1, word2, word3);
    }


    @Test
    public void testCreateDossierDictionaryForTwoDossiers() {

        var dossier = dossierTesterAndProvider.provideTestDossier();

        var dossierTemplate = dossierTemplateClient.getDossierTemplate(dossier.getDossierTemplateId());
        var dossier2 = dossierTesterAndProvider.provideTestDossier(dossierTemplate, "dossier2");

        var returnedtype1 = dictionaryClient.addType(CreateTypeValue.builder()
                                                             .type("dossier_redaction_1")
                                                             .label("Dossier Redactions")
                                                             .hexColor("#fcba03")
                                                             .rank(100)
                                                             .hint(false)
                                                             .caseInsensitive(false)
                                                             .recommendation(false)
                                                             .description("Something")
                                                             .addToDictionaryAction(false)
                                                             .dossierTemplateId(dossier.getDossierTemplateId())
                                                             .dossierDictionaryOnly(true)
                                                             .build());
        assertThat(returnedtype1.isDossierDictionaryOnly()).isTrue();

        assertThat(dictionaryClient.getAllTypes(dossier.getDossierTemplateId(), null, false).getTypes()
                           .stream()
                           .filter(t -> !t.isSystemManaged())
                           .collect(Collectors.toList()).size()).isEqualTo(1);
        assertThat(dictionaryClient.getAllTypes(dossier.getDossierTemplateId(), dossier.getId(), false).getTypes()
                           .stream()
                           .filter(t -> !t.isSystemManaged())
                           .collect(Collectors.toList()).size()).isEqualTo(2);
        var dictionary = dictionaryClient.getDictionaryForType(returnedtype1.getType(), dossier.getDossierTemplateId(), dossier2.getId());
        assertThat(dictionaryClient.getAllTypes(dossier.getDossierTemplateId(), dossier2.getId(), false).getTypes()
                           .stream()
                           .filter(t -> !t.isSystemManaged())
                           .collect(Collectors.toList()).size()).isEqualTo(2);
    }


    @Test
    public void testDeleteAndRecreateDictionary() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();

        var type = CreateTypeValue.builder()
                .type("dossier_redaction")
                .label("Dossier Redactions")
                .hexColor("#fcba03")
                .rank(100)
                .description("Something")
                .hasDictionary(true)
                .addToDictionaryAction(false)
                .dossierTemplateId(dossierTemplate.getId())
                .build();

        var createdType = dictionaryClient.addType(type);

        var word1 = "Luke Skywalker";
        var word2 = "Anakin Skywalker";
        var word3 = "Yoda";

        // Act & Assert: Add different words; All three should exist
        var entries = new ArrayList<String>();
        entries.add(word1);
        entries.add(word2);
        entries.add(word3);
        dictionaryClient.addEntry(createdType.getType(), createdType.getDossierTemplateId(), entries, false, null, DictionaryEntryType.ENTRY);

        var dictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);

        var actualEntries = dictionary.getEntries();
        assertThat(actualEntries.size()).isEqualTo(3);
        assertThat(actualEntries).usingComparator(new ListContentWithoutOrderAndWithoutDuplicatesComparator<>()).isEqualTo(entries);

        dictionaryClient.deleteType(createdType.getType(), createdType.getDossierTemplateId());
        assertThat(dictionaryClient.getAllTypes(createdType.getDossierTemplateId(), null, false).getTypes()
                           .stream()
                           .filter(t -> !t.isSystemManaged())
                           .collect(Collectors.toList()).size()).isEqualTo(0);

        dictionaryClient.addType(type);

        dictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);

        actualEntries = dictionary.getEntries();
        assertThat(actualEntries.size()).isEqualTo(0);
    }


    @Test
    public void testAddEntries() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();

        var createdType = createDossierRedactionsDictionary(dossierTemplate.getId());

        var word1 = "Luke Skywalker";
        var word2 = "Anakin Skywalker";
        var word3 = "Yoda";

        // Act & Assert: Add different words; All three should exist
        var entries = new ArrayList<String>();
        entries.add(word1);
        entries.add(word2);
        entries.add(word3);
        dictionaryClient.addEntry(createdType.getType(), createdType.getDossierTemplateId(), entries, false, null, DictionaryEntryType.ENTRY);

        var word4 = "Padme";
        var word5 = "Obiwan";
        entries.clear();
        entries.add(word4);
        entries.add(word5);
        dictionaryClient.addEntry(createdType.getType(), createdType.getDossierTemplateId(), entries, true, null, DictionaryEntryType.ENTRY);

        entries.add(word1);
        entries.add(word2);
        entries.add(word3);
        dictionaryClient.addEntry(createdType.getType(), createdType.getDossierTemplateId(), entries, false, null, DictionaryEntryType.ENTRY);

        var dictionary = dictionaryClient.getDictionaryForType(createdType.getType(), createdType.getDossierTemplateId(), null);

        assertThat(dictionary.getEntries().size()).isEqualTo(5);
    }


    @Test
    public void testUpdateGlobalEntries() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var dossier = dossierTesterAndProvider.provideTestDossier(dossierTemplate, "dossier");
        var createdType = createDossierRedactionsDictionary(dossierTemplate.getId());

        var word1 = "Luke Skywalker";
        var word2 = "Anakin Skywalker";
        var word3 = "Yoda";

        var entriesToAdd = new ArrayList<String>();
        entriesToAdd.add(word1);
        entriesToAdd.add(word2);
        entriesToAdd.add(word3);
        dictionaryClient.addEntry(createdType.getType(), createdType.getDossierTemplateId(), entriesToAdd, false, dossier.getId(), DictionaryEntryType.ENTRY);

        var dictionary = dictionaryClient.getDictionaryForType(createdType.getType(), createdType.getDossierTemplateId(), dossier.getId());
        assertThat(dictionary.getEntries().size()).isEqualTo(3);

        var word4 = "Padme";
        var word5 = "Obiwan";
        entriesToAdd.clear();
        entriesToAdd.add(word4);
        entriesToAdd.add(word5);
        var entriesToDelete = new ArrayList<String>();
        entriesToDelete.add(word1);
        entriesToDelete.add(word2);
        UpdateEntries updateEntries = new UpdateEntries(entriesToAdd, entriesToDelete);
        dictionaryClient.updateEntries(createdType.getType(), createdType.getDossierTemplateId(), updateEntries, dossier.getId(), DictionaryEntryType.ENTRY);

        dictionary = dictionaryClient.getDictionaryForType(createdType.getType(), createdType.getDossierTemplateId(), dossier.getId());
        assertThat(dictionary.getEntries().size()).isEqualTo(3);
        assertTrue(dictionary.getEntries()
                           .stream()
                           .anyMatch(entry -> entry.equals("Yoda")));
        assertTrue(dictionary.getEntries()
                           .stream()
                           .anyMatch(entry -> entry.equals("Padme")));
        assertTrue(dictionary.getEntries()
                           .stream()
                           .anyMatch(entry -> entry.equals("Obiwan")));
        assertTrue(dictionary.getEntries()
                           .stream()
                           .noneMatch(entry -> entry.equals("Luke Skywalker")));
        assertTrue(dictionary.getEntries()
                           .stream()
                           .noneMatch(entry -> entry.equals("Anakin Skywalker")));
    }


    @Test
    public void testUpdateDossierEntries() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var createdType = createDossierRedactionsDictionary(dossierTemplate.getId());

        var word1 = "Luke Skywalker";
        var word2 = "Anakin Skywalker";
        var word3 = "Yoda";

        var entriesToAdd = new ArrayList<String>();
        entriesToAdd.add(word1);
        entriesToAdd.add(word2);
        entriesToAdd.add(word3);
        dictionaryClient.addEntry(createdType.getType(), createdType.getDossierTemplateId(), entriesToAdd, false, null, DictionaryEntryType.ENTRY);

        var dictionary = dictionaryClient.getDictionaryForType(createdType.getType(), createdType.getDossierTemplateId(), null);
        assertThat(dictionary.getEntries().size()).isEqualTo(3);

        var word4 = "Padme";
        var word5 = "Obiwan";
        entriesToAdd.clear();
        entriesToAdd.add(word4);
        entriesToAdd.add(word5);
        var entriesToDelete = new ArrayList<String>();
        entriesToDelete.add(word1);
        entriesToDelete.add(word2);
        UpdateEntries updateEntries = new UpdateEntries(entriesToAdd, entriesToDelete);
        dictionaryClient.updateEntries(createdType.getType(), createdType.getDossierTemplateId(), updateEntries, null, DictionaryEntryType.ENTRY);

        dictionary = dictionaryClient.getDictionaryForType(createdType.getType(), createdType.getDossierTemplateId(), null);
        assertThat(dictionary.getEntries().size()).isEqualTo(3);
        assertTrue(dictionary.getEntries()
                           .stream()
                           .anyMatch(entry -> entry.equals("Yoda")));
        assertTrue(dictionary.getEntries()
                           .stream()
                           .anyMatch(entry -> entry.equals("Padme")));
        assertTrue(dictionary.getEntries()
                           .stream()
                           .anyMatch(entry -> entry.equals("Obiwan")));
        assertTrue(dictionary.getEntries()
                           .stream()
                           .noneMatch(entry -> entry.equals("Luke Skywalker")));
        assertTrue(dictionary.getEntries()
                           .stream()
                           .noneMatch(entry -> entry.equals("Anakin Skywalker")));
    }


    @Test
    public void testUpdateDossierEntriesWithEmptyFields() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var createdType = createDossierRedactionsDictionary(dossierTemplate.getId());

        var entriesToAdd = new ArrayList<String>();
        var entriesToDelete = new ArrayList<String>();

        UpdateEntries updateEntries = new UpdateEntries(null, entriesToDelete);
        UpdateEntries finalUpdateEntries = updateEntries;
        Assertions.assertDoesNotThrow(() -> dictionaryClient.updateEntries(createdType.getType(),
                                                                           createdType.getDossierTemplateId(),
                                                                           finalUpdateEntries,
                                                                           null,
                                                                           DictionaryEntryType.ENTRY));

        updateEntries = new UpdateEntries(entriesToAdd, null);
        UpdateEntries finalUpdateEntries1 = updateEntries;
        Assertions.assertDoesNotThrow(() -> dictionaryClient.updateEntries(createdType.getType(),
                                                                           createdType.getDossierTemplateId(),
                                                                           finalUpdateEntries1,
                                                                           null,
                                                                           DictionaryEntryType.ENTRY));
    }


    @Test
    public void testDossierTemplateAndDossierMissmatched() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var dossierTemplate1 = dossierTemplateTesterAndProvider.provideTestTemplate("dossierTemplate1");
        var dossier = dossierTesterAndProvider.provideTestDossier(dossierTemplate, "Dossier");
        var type = typeProvider.testAndProvideType(dossierTemplate);

        Assertions.assertThrows(FeignException.BadRequest.class, () -> dictionaryClient.getAllTypes(dossierTemplate1.getId(), dossier.getId(), false));

        Assertions.assertThrows(FeignException.BadRequest.class, () -> dictionaryClient.getDictionaryForType(type.getType(), dossierTemplate1.getId(), dossier.getId()));

    }


    @Test
    public void testGetMergedDictionaries() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var dossier = dossierTesterAndProvider.provideTestDossier(dossierTemplate, "Dossier");
        var type = typeProvider.testAndProvideType(dossierTemplate);
        assertThat(type.getRecommendationHexColor()).isEqualTo("#aaaaaa");
        assertThat(type.getSkippedHexColor()).isEqualTo("#aaaaaa");
        assertThat(type.isDossierDictionaryOnly()).isFalse();

        dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), List.of("word1", "word2", "word3"), false, null, DictionaryEntryType.ENTRY);
        dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), List.of("false_positive1", "false_positive"), false, null, DictionaryEntryType.FALSE_POSITIVE);
        dictionaryClient.addEntry(type.getType(),
                                  type.getDossierTemplateId(),
                                  List.of("false_recommendation1", "false_recommendation2"),
                                  false,
                                  null,
                                  DictionaryEntryType.FALSE_RECOMMENDATION);

        var loadedType1 = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);

        assertThat(loadedType1.getEntries()).hasSize(3);
        assertThat(loadedType1.getFalsePositiveEntries()).hasSize(2);
        assertThat(loadedType1.getFalseRecommendationEntries()).hasSize(2);

        dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), List.of("word1", "word4"), false, dossier.getId(), DictionaryEntryType.ENTRY);
        dictionaryClient.deleteEntries(type.getType(), type.getDossierTemplateId(), List.of("word2"), dossier.getId(), DictionaryEntryType.ENTRY);
        dictionaryClient.addEntry(type.getType(),
                                  type.getDossierTemplateId(),
                                  List.of("false_positive1", "false_positive3"),
                                  false,
                                  dossier.getId(),
                                  DictionaryEntryType.FALSE_POSITIVE);
        dictionaryClient.addEntry(type.getType(),
                                  type.getDossierTemplateId(),
                                  List.of("false_recommendation3", "false_recommendation4"),
                                  false,
                                  dossier.getId(),
                                  DictionaryEntryType.FALSE_RECOMMENDATION);
        Assertions.assertThrows(FeignException.Unauthorized.class, () -> dictionaryClient.getMergedDictionaries(type.getType() + ";", dossierTemplate.getId(), dossier.getId()));

        Dictionary mergedDict = dictionaryClient.getMergedDictionaries(type.getType(), dossierTemplate.getId(), dossier.getId());
        assertThat(mergedDict).isNotNull();
        assertThat(mergedDict.getEntries().size()).isEqualTo(3);
        assertThat(mergedDict.getFalsePositiveEntries().size()).isEqualTo(3);
        assertThat(mergedDict.getFalseRecommendationEntries().size()).isEqualTo(4);
    }


    @Test
    public void testGetDictionaryForType() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var dossier = dossierTesterAndProvider.provideTestDossier(dossierTemplate, "Dossier");
        var type = typeProvider.testAndProvideType(dossierTemplate);

        dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), List.of("word1", "word2", "word3"), false, null, DictionaryEntryType.ENTRY);
        dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), List.of("false_positive1", "false_positive"), false, null, DictionaryEntryType.FALSE_POSITIVE);
        dictionaryClient.addEntry(type.getType(),
                                  type.getDossierTemplateId(),
                                  List.of("false_recommendation1", "false_recommendation2"),
                                  false,
                                  null,
                                  DictionaryEntryType.FALSE_RECOMMENDATION);

        var loadedType1 = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);

        assertThat(loadedType1.getEntries()).hasSize(3);
        assertThat(loadedType1.getFalsePositiveEntries()).hasSize(2);
        assertThat(loadedType1.getFalseRecommendationEntries()).hasSize(2);

        dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), List.of("word1", "word4"), false, dossier.getId(), DictionaryEntryType.ENTRY);
        dictionaryClient.deleteEntries(type.getType(), type.getDossierTemplateId(), List.of("word2"), dossier.getId(), DictionaryEntryType.ENTRY);
        dictionaryClient.addEntry(type.getType(),
                                  type.getDossierTemplateId(),
                                  List.of("false_positive1", "false_positive3"),
                                  false,
                                  dossier.getId(),
                                  DictionaryEntryType.FALSE_POSITIVE);
        dictionaryClient.addEntry(type.getType(),
                                  type.getDossierTemplateId(),
                                  List.of("false_recommendation3", "false_recommendation4"),
                                  false,
                                  dossier.getId(),
                                  DictionaryEntryType.FALSE_RECOMMENDATION);

        var loadedType2 = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), dossier.getId());

        assertThat(loadedType2.getEntries()).hasSize(2);
        assertThat(loadedType2.getFalsePositiveEntries()).hasSize(2);
        assertThat(loadedType2.getFalseRecommendationEntries()).hasSize(2);

//        var result = dictionaryClient.downloadDictionary(type.getType(), dossierTemplate.getDossierTemplateId(), dossier.getDossierId(), DictionaryEntryType.ENTRY);
//        assertThat(result.getStatusCode().value()).isEqualTo(HttpStatus.OK.value());
    }


    @Test
    public void testSortedEntries() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var dossier = dossierTesterAndProvider.provideTestDossier(dossierTemplate, "Dossier");
        var type = typeProvider.testAndProvideType(dossierTemplate);
        assertThat(type.getRecommendationHexColor()).isEqualTo("#aaaaaa");
        assertThat(type.getSkippedHexColor()).isEqualTo("#aaaaaa");
        assertThat(type.isDossierDictionaryOnly()).isFalse();

        dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), testList1, false, null, DictionaryEntryType.ENTRY);
        dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), testList2, false, null, DictionaryEntryType.FALSE_POSITIVE);
        dictionaryClient.addEntry(type.getType(),
                                  type.getDossierTemplateId(),
                                  List.of("false_recommendation1", "afalse_recommendation2"),
                                  false,
                                  null,
                                  DictionaryEntryType.FALSE_RECOMMENDATION);

        var loadedType1 = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);

        var entries = loadedType1.getEntries();
        assertThat(entries).hasSize(7);
        assertThat(entries.get(0)).isEqualTo("Carina Wilson");
        assertThat(entries.get(1)).isEqualTo("carlsen");
        assertThat(entries.get(2)).isEqualTo("Charalampos");
        assertThat(entries.get(3)).isEqualTo("PATRICIA A. SHEEHY");
        assertThat(entries.get(4)).isEqualTo("Patricia A. Sheehy");
        assertThat(entries.get(5)).isEqualTo("William C. Spare");
        assertThat(entries.get(6)).isEqualTo("William c. Spare");
//        assertThat(entries).isEqualTo(loadedType1.getFalsePositiveEntries());

        var falsePositives = loadedType1.getEntries();
        assertThat(falsePositives).hasSize(7);
        assertThat(falsePositives.get(0)).isEqualTo("Carina Wilson");
        assertThat(falsePositives.get(1)).isEqualTo("carlsen");
        assertThat(falsePositives.get(2)).isEqualTo("Charalampos");
        assertThat(falsePositives.get(3)).isEqualTo("PATRICIA A. SHEEHY");
        assertThat(falsePositives.get(4)).isEqualTo("Patricia A. Sheehy");
        assertThat(falsePositives.get(5)).isEqualTo("William C. Spare");
        assertThat(falsePositives.get(6)).isEqualTo("William c. Spare");
        assertThat(loadedType1.getFalseRecommendationEntries()).hasSize(2);
        assertThat(loadedType1.getFalseRecommendationEntries()
                           .get(0)).isEqualTo("afalse_recommendation2");
        assertThat(loadedType1.getFalseRecommendationEntries()
                           .get(1)).isEqualTo("false_recommendation1");
    }


    @Test
    public void testSortedEntriesTest() {

        var testList1 = List.of("William c. Spare", "Charalampos", "Carina Wilson", "PATRICIA A. SHEEHY", "William C. Spare", "carlsen", "Patricia A. Sheehy");
        var testList2 = List.of("William C. Spare", "Charalampos", "Carina Wilson", "Patricia A. Sheehy", "William c. Spare", "carlsen", "PATRICIA A. SHEEHY");
        System.out.println("Test list 1:                                                    " + testList1);
        var sortedList1 = testList1.stream()
                .sorted()
                .sorted(Comparator.comparing(String::toLowerCase))
                .toList();
        var sortedList12 = testList1.stream()
                .sorted()
                .toList();
        var sortedList11 = testList1.stream()
                .sorted(String::compareToIgnoreCase)
                .toList();
        var sortedList13 = testList1.stream()
                .sorted(DictionaryService.entryComparator)
                .toList();

        var sortedList2 = testList2.stream()
                .sorted()
                .sorted(Comparator.comparing(String::toLowerCase))
                .toList();
        var sortedList22 = testList2.stream()
                .sorted()
                .toList();
        var sortedList21 = testList2.stream()
                .sorted(String::compareToIgnoreCase)
                .toList();
        var sortedList23 = testList2.stream()
                .sorted(DictionaryService.entryComparator)
                .toList();
        System.out.println("Test list 2:                                                    " + testList2);
        System.out.println("With sorted()sorted(Comparator.comparing(String::toLowerCase)): " + sortedList1);
        System.out.println("With sorted()sorted(Comparator.comparing(String::toLowerCase)): " + sortedList2);
        System.out.println("Just sorted():                                                  " + sortedList12);
        System.out.println("Just sorted():                                                  " + sortedList22);
        System.out.println("sorted(String:: compareToIgnoreCase):                           " + sortedList11);
        System.out.println("sorted(String:: compareToIgnoreCase):                           " + sortedList21);
        System.out.println("sorted(custom):                                                 " + sortedList13);
        System.out.println("sorted(custom):                                                 " + sortedList23);

        assertThat(sortedList1).isEqualTo(sortedList2);
        assertThat(sortedList13).isEqualTo(sortedList23);
        assertThat(sortedList12).isEqualTo(sortedList22); // the result are equal but not in the desired order
        assertThat(sortedList11).isNotEqualTo(sortedList21);
    }


    @Test
    public void testCreateAndDeleteLargeDictionary() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();

        var type = CreateTypeValue.builder()
                .type("dossier_redaction")
                .label("Dossier Redactions")
                .hexColor("#fcba03")
                .rank(100)
                .description("Something")
                .hasDictionary(true)
                .addToDictionaryAction(false)
                .dossierTemplateId(dossierTemplate.getId())
                .build();

        var createdType = dictionaryClient.addType(type);

        var entries = createDummyEntries(10_000);

        dictionaryClient.addEntry(createdType.getType(), createdType.getDossierTemplateId(), entries, false, null, DictionaryEntryType.ENTRY);

        var dictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);

        var actualEntries = dictionary.getEntries();
        assertThat(actualEntries.size()).isEqualTo(entries.size());
        assertThat(actualEntries).usingComparator(new ListContentWithoutOrderAndWithoutDuplicatesComparator<>()).isEqualTo(entries);

        dictionaryClient.deleteEntries(type.getType(), dossierTemplate.getId(), entries, null, null);

        dictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);

        actualEntries = dictionary.getEntries();
        assertThat(actualEntries.size()).isEqualTo(0);
    }


    @Test
    public void testAddDictionaryEntryWithCarriageReturn() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var type = typeProvider.testAndProvideType(dossierTemplate);
        var word = "Luke Skywalker ";
        var wordWithCarriageReturn = "Luke Skywalker\r";

        var entries = new ArrayList<String>();
        entries.add(wordWithCarriageReturn);
        dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), entries, false, null, DictionaryEntryType.ENTRY);

        var dictionary = dictionaryClient.getDictionaryForType(type.getType(), type.getDossierTemplateId(), null);

        assertThat(dictionary.getEntries()).hasSize(1);
        assertThat(dictionary.getEntries()
                           .get(0)).isEqualTo(word);
    }


    @Test
    public void testCalculateDictionaryDifference() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var dossier = dossierTesterAndProvider.provideTestDossier(dossierTemplate, "testDossier");
        var type = typeProvider.testAndProvideType(dossierTemplate, "type1", 100);
        var type2 = typeProvider.testAndProvideType(dossierTemplate, "type2", 101);

        // Add entries for the dossier template
        dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), List.of("entry1", "entry2"), false, null, DictionaryEntryType.ENTRY);
        dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), List.of("falsePositive1", "falsePositive2"), false, null, DictionaryEntryType.FALSE_POSITIVE);
        dictionaryClient.addEntry(type.getType(),
                                  type.getDossierTemplateId(),
                                  List.of("falseRecommendation1", "falseRecommendation2"),
                                  false,
                                  null,
                                  DictionaryEntryType.FALSE_RECOMMENDATION);
        dictionaryClient.addEntry(type2.getType(), type2.getDossierTemplateId(), List.of("entry1", "entry2"), false, null, DictionaryEntryType.ENTRY);
        dictionaryClient.addEntry(type2.getType(), type2.getDossierTemplateId(), List.of("falsePositive1", "falsePositive2"), false, null, DictionaryEntryType.FALSE_POSITIVE);
        dictionaryClient.addEntry(type2.getType(), type2.getDossierTemplateId(), List.of("random"), false, null, DictionaryEntryType.FALSE_RECOMMENDATION);

        // Add entries for the dossier
        dictionaryClient.deleteEntries(type.getType(), type.getDossierTemplateId(), List.of("entry1"), dossier.getId(), DictionaryEntryType.ENTRY);
        dictionaryClient.addEntry(type.getType(),
                                  type.getDossierTemplateId(),
                                  List.of("falsePositive1", "falsePositive2", "falsePositive3"),
                                  false,
                                  dossier.getId(),
                                  DictionaryEntryType.FALSE_POSITIVE);
        dictionaryClient.addEntry(type.getType(),
                                  type.getDossierTemplateId(),
                                  List.of("falseRecommendation1", "falseRecommendation2"),
                                  false,
                                  dossier.getId(),
                                  DictionaryEntryType.FALSE_RECOMMENDATION);
        dictionaryClient.addEntry(type2.getType(), type2.getDossierTemplateId(), List.of("entry1", "entry2"), false, dossier.getId(), DictionaryEntryType.ENTRY);
        dictionaryClient.addEntry(type2.getType(),
                                  type2.getDossierTemplateId(),
                                  List.of("falsePositive2", "falsePositive3"),
                                  false,
                                  dossier.getId(),
                                  DictionaryEntryType.FALSE_POSITIVE);
        dictionaryClient.deleteEntries(type2.getType(), type2.getDossierTemplateId(), List.of("falsePositive1"), dossier.getId(), DictionaryEntryType.FALSE_POSITIVE);
        dictionaryClient.addEntry(type2.getType(),
                                  type2.getDossierTemplateId(),
                                  List.of("falseRecommendation1", "falseRecommendation2"),
                                  false,
                                  dossier.getId(),
                                  DictionaryEntryType.FALSE_RECOMMENDATION);
        dictionaryClient.deleteEntries(type2.getType(), type2.getDossierTemplateId(), List.of("random"), dossier.getId(), DictionaryEntryType.FALSE_RECOMMENDATION);

        DictionaryDifferenceResponse dictionaryDifferenceResponse = dictionaryClient.getDictionaryDifference(dossierTemplate.getId(), Set.of(type.getType(), type2.getType()));
        Assertions.assertNotNull(dictionaryDifferenceResponse);
        assertEquals(dictionaryDifferenceResponse.getDossierTemplateId(), dossierTemplate.getId());
        assertFalse(dictionaryDifferenceResponse.getDictionaryDifferences().isEmpty());
        DictionaryDifference dictionaryDifference = dictionaryDifferenceResponse.getDictionaryDifferences()
                .get(0);
        assertEquals(dictionaryDifference.getDossierId(), dossier.getId());
        assertFalse(dictionaryDifference.getDictionaryDifferencePerTypes().isEmpty());
        DictionaryDifferencePerType dictionaryDifferencePerType1 = dictionaryDifference.getDictionaryDifferencePerTypes()
                .stream()
                .filter(d -> d.getType().equals("type1"))
                .findFirst()
                .get();
        DictionaryDifferencePerType dictionaryDifferencePerType2 = dictionaryDifference.getDictionaryDifferencePerTypes()
                .stream()
                .filter(d -> d.getType().equals("type2"))
                .findFirst()
                .get();

        assertTrue(dictionaryDifferencePerType1.getDictionaryEntriesDifference().getAddedEntries().isEmpty());
        assertTrue(dictionaryDifferencePerType1.getDictionaryEntriesDifference().getRemovedEntries().contains("entry1"));

        assertTrue(dictionaryDifferencePerType1.getDictionaryFalsePositivesDifference().getAddedFalsePositives().contains("falsePositive3"));
        assertTrue(dictionaryDifferencePerType1.getDictionaryFalsePositivesDifference().getRemovedFalsePositives().isEmpty());

        assertTrue(dictionaryDifferencePerType1.getDictionaryFalseRecommendationsDifference().getAddedFalseRecommendations().isEmpty());
        assertTrue(dictionaryDifferencePerType1.getDictionaryFalseRecommendationsDifference().getRemovedFalseRecommendations().isEmpty());

        assertTrue(dictionaryDifferencePerType2.getDictionaryEntriesDifference().getAddedEntries().isEmpty());
        assertTrue(dictionaryDifferencePerType2.getDictionaryEntriesDifference().getRemovedEntries().isEmpty());

        assertTrue(dictionaryDifferencePerType2.getDictionaryFalsePositivesDifference().getAddedFalsePositives().contains("falsePositive3"));
        assertTrue(dictionaryDifferencePerType2.getDictionaryFalsePositivesDifference().getRemovedFalsePositives().contains("falsePositive1"));

        assertTrue(dictionaryDifferencePerType2.getDictionaryFalseRecommendationsDifference().getAddedFalseRecommendations().contains("falseRecommendation1"));
        assertTrue(dictionaryDifferencePerType2.getDictionaryFalseRecommendationsDifference().getAddedFalseRecommendations().contains("falseRecommendation2"));
        assertTrue(dictionaryDifferencePerType2.getDictionaryFalseRecommendationsDifference().getRemovedFalseRecommendations().contains("random"));
    }


    @Test
    public void testCreateDossierOnTheFlyForDTTypeDeleted() {

        var dossier = dossierTesterAndProvider.provideTestDossier();

        var dossierTemplate = dossierTemplateClient.getDossierTemplate(dossier.getDossierTemplateId());

        var dtType = dictionaryClient.addType(CreateTypeValue.builder()
                                                      .type("test1")
                                                      .label("test1")
                                                      .hexColor("#fcba03")
                                                      .rank(100)
                                                      .hint(false)
                                                      .hasDictionary(false)
                                                      .caseInsensitive(false)
                                                      .recommendation(false)
                                                      .addToDictionaryAction(true)
                                                      .dossierTemplateId(dossierTemplate.getId())
                                                      .dossierDictionaryOnly(false)
                                                      .build());
        assertThat(dtType.getRank()).isEqualTo(100);
        assertThat(dtType.isDossierDictionaryOnly()).isFalse();
        assertThat(dictionaryClient.getAllTypes(dossierTemplate.getId(), null, true).getTypes()
                           .stream()
                           .filter(t -> !t.isSystemManaged())
                           .collect(Collectors.toList()).size()).isEqualTo(1);

        dictionaryClient.deleteType(dtType.getType(), dtType.getDossierTemplateId());
        assertThat(dictionaryClient.getAllTypes(dossierTemplate.getId(), null, false).getTypes()
                           .stream()
                           .filter(t -> !t.isSystemManaged())
                           .collect(Collectors.toList()).size()).isEqualTo(0);
        assertThat(dictionaryClient.getAllTypes(dossierTemplate.getId(), null, true).getTypes()
                           .stream()
                           .filter(t -> !t.isSystemManaged())
                           .collect(Collectors.toList()).size()).isEqualTo(1);

        var dtType2 = dictionaryClient.addType(CreateTypeValue.builder()
                                                       .type("test2")
                                                       .label("test2")
                                                       .hexColor("#fcba03")
                                                       .rank(100)
                                                       .hint(false)
                                                       .caseInsensitive(false)
                                                       .recommendation(false)
                                                       .addToDictionaryAction(true)
                                                       .dossierTemplateId(dossierTemplate.getId())
                                                       .dossierDictionaryOnly(false)
                                                       .build());
        assertThat(dictionaryClient.getAllTypes(dossierTemplate.getId(), null, false).getTypes()
                           .stream()
                           .filter(t -> !t.isSystemManaged())
                           .collect(Collectors.toList()).size()).isEqualTo(1);
        assertThat(dictionaryClient.getAllTypes(dossierTemplate.getId(), null, true).getTypes()
                           .stream()
                           .filter(t -> !t.isSystemManaged())
                           .collect(Collectors.toList()).size()).isEqualTo(2);

        assertThat(dtType2.getRank()).isEqualTo(dtType.getRank());
        assertThat(dictionaryClient.getAllTypes(dossierTemplate.getId(), dossier.getId(), false).getTypes()
                           .stream()
                           .filter(t -> !t.isSystemManaged())
                           .collect(Collectors.toList()).size()).isEqualTo(2);
        assertThat(dictionaryClient.getAllTypes(dossierTemplate.getId(), dossier.getId(), true).getTypes()
                           .stream()
                           .filter(t -> !t.isSystemManaged())
                           .collect(Collectors.toList()).size()).isEqualTo(3);

    }


    @SuppressWarnings("SameParameterValue")
    private List<String> createDummyEntries(int numberOfEntries) {

        return IntStream.range(0, numberOfEntries).mapToObj(i -> "someWord" + i)
                .toList();
    }


    @Test
    public void testAddEntriesWithIsImportFlag() {

        var dossier = dossierTesterAndProvider.provideTestDossier();

        var dossierTemplate = dossierTemplateClient.getDossierTemplate(dossier.getDossierTemplateId());

        var dtType = dictionaryClient.addType(CreateTypeValue.builder()
                                                      .type("test1")
                                                      .label("test1")
                                                      .hexColor("#fcba03")
                                                      .rank(100)
                                                      .hint(false)
                                                      .caseInsensitive(false)
                                                      .recommendation(false)
                                                      .addToDictionaryAction(true)
                                                      .dossierTemplateId(dossierTemplate.getId())
                                                      .dossierDictionaryOnly(false)
                                                      .build());

        List<TypeValue> types = dictionaryClient.getAllTypes(dossierTemplate.getId(), null, true).getTypes()
                .stream()
                .filter(t -> !t.isSystemManaged())
                .collect(Collectors.toList());
        assertThat(types.size()).isEqualTo(1);

        String dictionaryEntry = "entry1";
        dictionaryManagementService.addEntries(dtType.getTypeId(), List.of(dictionaryEntry), false, false, DictionaryEntryType.ENTRY, true);
        var dictionaryEntries = dictionaryManagementService.getAllEntriesInDossierTemplate(dtType.getTypeId(), dictionaryEntry, DictionaryEntryType.ENTRY);

        assertThat(types.get(0).isHasDictionary()).isFalse();
        assertThat(dictionaryEntries.get(0).getValue()).isEqualTo(dictionaryEntry);
    }


    @Test
    public void testAddEntriesWithoutIsImportFlag() {

        var dossier = dossierTesterAndProvider.provideTestDossier();

        var dossierTemplate = dossierTemplateClient.getDossierTemplate(dossier.getDossierTemplateId());

        var dtType = dictionaryClient.addType(CreateTypeValue.builder()
                                                      .type("test1")
                                                      .label("test1")
                                                      .hexColor("#fcba03")
                                                      .rank(100)
                                                      .hint(false)
                                                      .caseInsensitive(false)
                                                      .recommendation(false)
                                                      .addToDictionaryAction(true)
                                                      .dossierTemplateId(dossierTemplate.getId())
                                                      .dossierDictionaryOnly(false)
                                                      .build());
        assertThat(dtType.getRank()).isEqualTo(100);
        assertThat(dtType.isDossierDictionaryOnly()).isFalse();
        assertThat(dictionaryClient.getAllTypes(dossierTemplate.getId(), null, true).getTypes()
                           .stream()
                           .filter(t -> !t.isSystemManaged())
                           .collect(Collectors.toList()).size()).isEqualTo(1);

        assertThatThrownBy(() -> dictionaryManagementService.addEntries(dtType.getTypeId(),
                                                                        List.of("entry1", "entry2"),
                                                                        false,
                                                                        false,
                                                                        DictionaryEntryType.ENTRY,
                                                                        false)).hasMessageContaining("Entity type does not have a dictionary");
    }


    @Test
    void testDeleteDossierThenDeleteType() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();

        var dossier = dossierTesterAndProvider.provideTestDossier(dossierTemplate, "TestDossier");

        CreateTypeValue createTypeValue = CreateTypeValue.builder()
                .type("test_type")
                .label("Test Type")
                .hexColor("#fcba03")
                .rank(100)
                .hint(false)
                .caseInsensitive(false)
                .recommendation(false)
                .addToDictionaryAction(true)
                .dossierTemplateId(dossierTemplate.getId())
                .dossierDictionaryOnly(false)
                .build();

        var type = dictionaryClient.addType(createTypeValue);

        List<TypeValue> customTypes = dictionaryClient.getAllTypes(dossierTemplate.getId(), dossier.getId(), false).getTypes()
                .stream()
                .filter(t -> !t.isSystemManaged())
                .toList();

        assertEquals(2, customTypes.size());

        dossierClient.deleteDossier(dossier.getId());

        dictionaryClient.deleteType(type.getType(), type.getDossierTemplateId());

        var types = dictionaryClient.getAllTypes(dossierTemplate.getId(), dossier.getId(), false).getTypes()
                .stream()
                .filter(t -> !t.isSystemManaged())
                .toList();

        assertThat(types).isEmpty();

        dictionaryClient.addType(createTypeValue);

        customTypes = dictionaryClient.getAllTypes(dossierTemplate.getId(), dossier.getId(), false).getTypes()
                .stream()
                .filter(t -> !t.isSystemManaged())
                .toList();

        assertEquals(2, customTypes.size());
    }


    @Test
    void testRecreateTypeAndCheckMergedDictionary() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var dossier = dossierTesterAndProvider.provideTestDossier(dossierTemplate, "TestDossier");

        var type = typeProvider.testAndProvideType(dossierTemplate, null, "type1", false);

        List<String> templateEntries = List.of("aaa", "bbb");
        dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), templateEntries, false, null, DictionaryEntryType.ENTRY);
        var templateDictionary = dictionaryClient.getDictionaryForType(type.getType(), dossierTemplate.getId(), null);
        assertThat(templateDictionary.getEntries()).containsExactlyInAnyOrderElementsOf(templateEntries);

        List<String> dossierEntries = List.of("ccc");
        dictionaryClient.addEntry(type.getType(), type.getDossierTemplateId(), dossierEntries, false, dossier.getId(), DictionaryEntryType.ENTRY);
        var dossierDictionary = dictionaryClient.getDictionaryForType(type.getType(), dossierTemplate.getId(), dossier.getId());
        assertThat(dossierDictionary.getEntries()).containsExactlyInAnyOrderElementsOf(dossierEntries);

        Dictionary mergedDictionary = dictionaryClient.getMergedDictionaries(type.getType(), type.getDossierTemplateId(), dossier.getId());
        assertThat(mergedDictionary).isNotNull();
        List<String> allEntries = new ArrayList<>(templateEntries);
        allEntries.addAll(dossierEntries);
        assertThat(mergedDictionary.getEntries()).containsExactlyInAnyOrderElementsOf(allEntries);

        List<String> newDossierEntries = List.of("entry d");
        UpdateEntries updateEntries = new UpdateEntries(newDossierEntries, allEntries);
        dictionaryClient.updateEntries(type.getType(), type.getDossierTemplateId(), updateEntries, dossier.getId(), DictionaryEntryType.ENTRY);

        List<DictionaryEntryEntity> deleted = entryRepository.findAll()
                .stream()
                .filter(DictionaryEntryEntity::isDeleted)
                .toList();
        assertEquals(3, deleted.size());

        var updatedDossierDictionary = dictionaryClient.getDictionaryForType(type.getType(), dossierTemplate.getId(), dossier.getId());
        assertThat(updatedDossierDictionary.getEntries()).containsExactlyInAnyOrderElementsOf(newDossierEntries);

        // deletion
        dictionaryClient.deleteType(type.getType(), type.getDossierTemplateId());
        Assertions.assertThrows(FeignException.NotFound.class, () -> dictionaryClient.getDictionaryForType(type.getType(), dossierTemplate.getId(), null));

        List<TypeEntity> deletedType1Entities = typeRepository.findAllTypesByDossierTemplateIdOrDossierId(dossierTemplate.getId(), dossier.getId())
                .stream()
                .filter(typeEntity -> typeEntity.getType().equals("type1") && typeEntity.isDeleted())
                .toList();

        assertEquals(2, deletedType1Entities.size());

        deleted = entryRepository.findAll()
                .stream()
                .filter(DictionaryEntryEntity::isDeleted)
                .toList();
        assertEquals(2, deleted.size());

        // recreation
        var type2 = typeProvider.testAndProvideType(dossierTemplate, null, "type1", false);

        dictionaryClient.addEntry(type2.getType(), type2.getDossierTemplateId(), templateEntries, false, null, DictionaryEntryType.ENTRY);

        deleted = entryRepository.findAll()
                .stream()
                .filter(DictionaryEntryEntity::isDeleted)
                .toList();
        assertEquals(0, deleted.size());

        deletedType1Entities = typeRepository.findAllTypesByDossierTemplateIdOrDossierId(dossierTemplate.getId(), dossier.getId())
                .stream()
                .filter(typeEntity -> typeEntity.getType().equals("type1") && typeEntity.isDeleted())
                .toList();

        assertEquals(1, deletedType1Entities.size());

        // problematic call because it creates/update entities on a get
        dictionaryClient.getAllTypes(dossierTemplate.getId(), dossier.getId(), false);

        deletedType1Entities = typeRepository.findAllTypesByDossierTemplateIdOrDossierId(dossierTemplate.getId(), dossier.getId())
                .stream()
                .filter(typeEntity -> typeEntity.getType().equals("type1") && typeEntity.isDeleted())
                .toList();

        assertEquals(0, deletedType1Entities.size());

        Dictionary mergedDictionary2 = dictionaryClient.getMergedDictionaries(type2.getType(), type2.getDossierTemplateId(), dossier.getId());
        assertThat(mergedDictionary2).isNotNull();
        assertThat(mergedDictionary2.getEntries()).containsExactlyInAnyOrderElementsOf(templateEntries);
        assertThat(mergedDictionary2.getEntries()).doesNotContain("entry d");
    }


    @Test
    public void testChangeFlagsSuccessful() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var dossier1 = dossierTesterAndProvider.provideTestDossier(dossierTemplate, "Dossier1");

        var type1 = dictionaryClient.addType(CreateTypeValue.builder()
                                                     .type("test_change_flags_true")
                                                     .label("Test Change Flags True")
                                                     .hexColor("#123456")
                                                     .rank(100)
                                                     .hint(false)
                                                     .hasDictionary(true)
                                                     .caseInsensitive(false)
                                                     .recommendation(false)
                                                     .addToDictionaryAction(false)
                                                     .dossierTemplateId(dossierTemplate.getId())
                                                     .dossierDictionaryOnly(true)
                                                     .build());
        dictionaryClient.getAllTypes(dossierTemplate.getId(), dossier1.getId(), false);


        dictionaryClient.changeFlags(type1.getType(), dossierTemplate.getId(), dossier1.getId(), true);

        String compositeTypeId1 = type1.getType() + ":" + dossierTemplate.getId() + ":" + dossier1.getId();
        var updatedTypeEntity1 = typeRepository.findById(compositeTypeId1)
                .orElseThrow(() -> new AssertionError("Type entity not found in repository"));

        assertThat(updatedTypeEntity1.isAddToDictionaryAction()).isTrue();
    }


    @Test
    public void testChangeFlagsUnsuccessful() {

        var dossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        var dossier2 = dossierTesterAndProvider.provideTestDossier(dossierTemplate, "Dossier2");

        var type2 = dictionaryClient.addType(CreateTypeValue.builder()
                                                     .type("test_change_flags_false")
                                                     .label("Test Change Flags False")
                                                     .hexColor("#654321")
                                                     .rank(101)
                                                     .hint(false)
                                                     .hasDictionary(true)
                                                     .caseInsensitive(false)
                                                     .recommendation(false)
                                                     .addToDictionaryAction(false)
                                                     .dossierTemplateId(dossierTemplate.getId())
                                                     .dossierDictionaryOnly(false)
                                                     .build());
        dictionaryClient.getAllTypes(dossierTemplate.getId(), dossier2.getId(), false);


        assertThatThrownBy(() -> dictionaryClient.changeFlags(type2.getType(), dossierTemplate.getId(), dossier2.getId(), true))
                .isInstanceOf(FeignException.BadRequest.class)
                .hasMessageContaining("The addToDictionaryAction flag can only be adjusted for dossierDictionaryOnly-types.");

        String compositeTypeId2 = type2.getType() + ":" + dossierTemplate.getId() + ":" + dossier2.getId();
        var typeEntity2 = typeRepository.findById(compositeTypeId2)
                .orElseThrow(() -> new AssertionError("Type entity not found in repository"));

        assertThat(typeEntity2.isAddToDictionaryAction()).isFalse();
    }


    private static final class ListContentWithoutOrderAndWithoutDuplicatesComparator<T> implements Comparator<List<? extends T>> {

        @SuppressWarnings("SuspiciousMethodCalls")
        @Override
        public int compare(List<? extends T> l1, List<? extends T> l2) {

            return l1.containsAll(l2) ? 0 : -1;
        }

    }


    private TypeValue createDossierRedactionsDictionary(String dossierTemplateId) {

        return dictionaryClient.addType(CreateTypeValue.builder()
                                                .type("dossier_redaction")
                                                .label("Dossier Redaction")
                                                .addToDictionaryAction(true)
                                                .hasDictionary(true)
                                                .hint(false)
                                                .rank(100)
                                                .dossierTemplateId(dossierTemplateId)
                                                .hexColor("#FFFFFF")
                                                .recommendationHexColor("#FFFFFF")
                                                .build());
    }

}
