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

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.*;
import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils;

import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.LegalBasisEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.RuleSetEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierTemplateEntity;
import com.iqser.red.service.persistence.management.v1.processor.migration.migrations.V30TechnicalNameRuleFileMigration;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.LegalBasisMappingPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.MigrationPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.RulesPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.DossierTemplateRepository;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType;

public class TechnicalNameRuleFileMigrationTest {

    private static final String TEMPLATE_ID_1 = "template1";
    private static final String TEMPLATE_ID_2 = "template2";

    private static final String RULES_WITH_REASONS = "rule \"CBI.0.4: Redact CBI Authors (vertebrate Study)\"\n"
                                                     + "    when\n"
                                                     + "        FileAttribute(label == \"Vertebrate Study\", value soundslike \"Yes\" || value.toLowerCase() == \"y\")\n"
                                                     + "        $entity: TextEntity(type() == \"CBI_author\", dictionaryEntry)\n"
                                                     + "    then\n"
                                                     + "        $entity.redact(\"CBI.0.4\", \"Author found\", \"Article 39(e)(2) of Regulation (EC) No 178/2002\");\n"
                                                     + "    end\n"
                                                     + "\n"
                                                     + "rule \"CBI.0.3: Redact CBI Authors (non vertebrate Study)\"\n"
                                                     + "    when\n"
                                                     + "        not FileAttribute(label == \"Vertebrate Study\", value soundslike \"Yes\" || value.toLowerCase() == \"y\")\n"
                                                     + "        $entity: TextEntity(type() == \"CBI_author\", dictionaryEntry)\n"
                                                     + "    then\n"
                                                     + "        $entity.redact(\"CBI.0.3\", \"Author found\", \"Article 39(e)(3) of Regulation (EC) No 178/2002\");\n"
                                                     + "    end";

    private static final String RULES_WITH_SPECIAL_CHARACTERS = "rule \"Special Characters Rule\"\n"
                                                                + "    when\n"
                                                                + "        // conditions\n"
                                                                + "    then\n"
                                                                + "        // actions\n"
                                                                + "        $entity.redact(\"RuleCode\", \"Description\", \"Article 63(2)(a) of Regulation (EC) No 1107/2009 (making reference to Article 39 of Regulation EC No 178/2002)\");\n"
                                                                + "    end";

    private static final String RULES_WITH_NO_REASONS = "rule \"Some Rule\"\n" + "    when\n" + "        // conditions\n" + "    then\n" + "        // actions\n" + "    end";

    private static final String RULES_WITH_PARTIAL_MATCH = "rule \"Partial Match Rule\"\n"
                                                           + "    when\n"
                                                           + "        // conditions\n"
                                                           + "    then\n"
                                                           + "        // actions\n"
                                                           + "        $entity.redact(\"RuleCode\", \"Description\", \"Some Article 39(e)(2) of Regulation (EC) No 178/2002 Additional Text\");\n"
                                                           + "    end";

    private static final String RULES_WITH_IDENTICAL_REASON_AND_TECHNICAL_NAME = "rule \"Identical Reason and Technical Name Rule\"\n"
                                                                                 + "    when\n"
                                                                                 + "        // conditions\n"
                                                                                 + "    then\n"
                                                                                 + "        // actions\n"
                                                                                 + "        $entity.redact(\"RuleCode\", \"Description\", \"same_name\");\n"
                                                                                 + "    end";

    private static final String RULES_WITH_MULTIPLE_TEMPLATES = "rule \"Composition Rule\"\n"
                                                                + "    when\n"
                                                                + "        // conditions\n"
                                                                + "    then\n"
                                                                + "        // actions\n"
                                                                + "        $entity.redact(\"RuleCode\", \"Description\", \"Article 63(2)(d) of Regulation (EC) No 1107/2009\");\n"
                                                                + "    end";

    private static final String RULES_WITH_NULL_VALUES = "rule \"Null Values Rule\"\n"
                                                         + "    when\n"
                                                         + "        // conditions\n"
                                                         + "    then\n"
                                                         + "        // actions\n"
                                                         + "        $entity.redact(\"RuleCode\", \"Description\", \"Article 39(e)(2) of Regulation (EC) No 178/2002\");\n"
                                                         + "    end";

    private static final String RULES_WITH_PARTIAL_VALID_MAPPINGS = "rule \"Partial Valid Mappings Rule\"\n"
                                                                    + "    when\n"
                                                                    + "        // conditions\n"
                                                                    + "    then\n"
                                                                    + "        // actions\n"
                                                                    + "        $entity.redact(\"RuleCode\", \"Description\", \"Article 39(e)(2) of Regulation (EC) No 178/2002\");\n"
                                                                    + "        $entity.redact(\"RuleCode\", \"Description\", \"Some Other Reason\");\n"
                                                                    + "    end";

    @Mock
    private DossierTemplateRepository dossierTemplateRepository;

    @Mock
    private RulesPersistenceService rulesPersistenceService;

    @Mock
    private LegalBasisMappingPersistenceService legalBasisMappingPersistenceService;

    @Mock
    private MigrationPersistenceService migrationPersistenceService;

    @InjectMocks
    private V30TechnicalNameRuleFileMigration migration;


    @BeforeEach
    public void setup() {

        MockitoAnnotations.openMocks(this);
        migration.setMigrationPersistenceService(migrationPersistenceService);
        when(migrationPersistenceService.getLatestProcessedVersion()).thenReturn(0L);
        when(migrationPersistenceService.isProcessed(anyLong(), anyLong())).thenReturn(false);
    }


    private DossierTemplateEntity createDossierTemplate(String id) {

        DossierTemplateEntity dossierTemplate = new DossierTemplateEntity();
        dossierTemplate.setId(id);
        return dossierTemplate;
    }


    private LegalBasisEntity createLegalBasis(String reason, String technicalName) {

        return LegalBasisEntity.builder().reason(reason).technicalName(technicalName).build();
    }


    private RuleSetEntity createRuleSet(String content) {

        RuleSetEntity ruleSet = new RuleSetEntity();
        ruleSet.setValue(content);
        return ruleSet;
    }


    @Test
    public void testMigrationReplacesLegalBasisReasonsWithTechnicalNames() {

        DossierTemplateEntity dossierTemplate = createDossierTemplate(TEMPLATE_ID_1);
        when(dossierTemplateRepository.findAll()).thenReturn(Collections.singletonList(dossierTemplate));

        List<LegalBasisEntity> mappings = Arrays.asList(createLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002",
                                                                         "vertebrate_study_personal_data_geolocation_article_39e2"),
                                                        createLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002", "personal_data_geolocation_article_39e3"));
        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_1)).thenReturn(mappings);

        RuleSetEntity ruleSet = createRuleSet(RULES_WITH_REASONS);
        when(rulesPersistenceService.getRules(TEMPLATE_ID_1, RuleFileType.ENTITY)).thenReturn(Optional.of(ruleSet));

        migration.run();

        ArgumentCaptor<String> rulesCaptor = ArgumentCaptor.forClass(String.class);
        verify(rulesPersistenceService).setRules(rulesCaptor.capture(), eq(TEMPLATE_ID_1), eq(RuleFileType.ENTITY));

        String updatedRules = rulesCaptor.getValue();
        assertAll(() -> assertTrue(updatedRules.contains("vertebrate_study_personal_data_geolocation_article_39e2")),
                  () -> assertTrue(updatedRules.contains("personal_data_geolocation_article_39e3")),
                  () -> assertFalse(updatedRules.contains("Article 39(e)(2) of Regulation (EC) No 178/2002")),
                  () -> assertFalse(updatedRules.contains("Article 39(e)(3) of Regulation (EC) No 178/2002")));
    }


    @Test
    public void testMigrationWithNoLegalBasisMappings() {

        DossierTemplateEntity dossierTemplate = createDossierTemplate(TEMPLATE_ID_1);
        when(dossierTemplateRepository.findAll()).thenReturn(Collections.singletonList(dossierTemplate));

        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_1)).thenReturn(Collections.emptyList());

        migration.run();

        verifyNoInteractions(rulesPersistenceService);
    }


    @Test
    public void testMigrationWithNoRuleSet() {

        DossierTemplateEntity dossierTemplate = createDossierTemplate(TEMPLATE_ID_1);
        when(dossierTemplateRepository.findAll()).thenReturn(Collections.singletonList(dossierTemplate));

        LegalBasisEntity lb1 = createLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002", "vertebrate_study_personal_data_geolocation_article_39e2");
        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_1)).thenReturn(Collections.singletonList(lb1));

        when(rulesPersistenceService.getRules(TEMPLATE_ID_1, RuleFileType.ENTITY)).thenReturn(Optional.empty());

        migration.run();

        verify(rulesPersistenceService, never()).setRules(anyString(), anyString(), any(RuleFileType.class));
    }


    @Test
    public void testMigrationWithNoReplacementsNeeded() {

        DossierTemplateEntity dossierTemplate = createDossierTemplate(TEMPLATE_ID_1);
        when(dossierTemplateRepository.findAll()).thenReturn(Collections.singletonList(dossierTemplate));

        LegalBasisEntity lb1 = createLegalBasis("Nonexistent Reason", "nonexistent_technical_name");
        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_1)).thenReturn(Collections.singletonList(lb1));

        RuleSetEntity ruleSet = createRuleSet(RULES_WITH_NO_REASONS);
        when(rulesPersistenceService.getRules(TEMPLATE_ID_1, RuleFileType.ENTITY)).thenReturn(Optional.of(ruleSet));

        migration.run();

        verify(rulesPersistenceService, never()).setRules(anyString(), anyString(), any(RuleFileType.class));
    }


    @Test
    public void testMigrationHandlesSpecialCharactersInReasons() {

        DossierTemplateEntity dossierTemplate = createDossierTemplate(TEMPLATE_ID_1);
        when(dossierTemplateRepository.findAll()).thenReturn(Collections.singletonList(dossierTemplate));

        LegalBasisEntity lb1 = createLegalBasis("Article 63(2)(a) of Regulation (EC) No 1107/2009 (making reference to Article 39 of Regulation EC No 178/2002)",
                                                "manufacturing_production_process");
        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_1)).thenReturn(Collections.singletonList(lb1));

        RuleSetEntity ruleSet = createRuleSet(RULES_WITH_SPECIAL_CHARACTERS);
        when(rulesPersistenceService.getRules(TEMPLATE_ID_1, RuleFileType.ENTITY)).thenReturn(Optional.of(ruleSet));

        migration.run();

        ArgumentCaptor<String> rulesCaptor = ArgumentCaptor.forClass(String.class);
        verify(rulesPersistenceService).setRules(rulesCaptor.capture(), eq(TEMPLATE_ID_1), eq(RuleFileType.ENTITY));

        String updatedRules = rulesCaptor.getValue();
        assertAll(() -> assertTrue(updatedRules.contains("manufacturing_production_process")),
                  () -> assertFalse(updatedRules.contains("Article 63(2)(a) of Regulation (EC) No 1107/2009 (making reference to Article 39 of Regulation EC No 178/2002)")));
    }


    @Test
    public void testMigrationDoesNotReplacePartialMatches() {

        DossierTemplateEntity dossierTemplate = createDossierTemplate(TEMPLATE_ID_1);
        when(dossierTemplateRepository.findAll()).thenReturn(Collections.singletonList(dossierTemplate));

        LegalBasisEntity lb1 = createLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002", "vertebrate_study_personal_data_geolocation_article_39e2");
        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_1)).thenReturn(Collections.singletonList(lb1));

        RuleSetEntity ruleSet = createRuleSet(RULES_WITH_PARTIAL_MATCH);
        when(rulesPersistenceService.getRules(TEMPLATE_ID_1, RuleFileType.ENTITY)).thenReturn(Optional.of(ruleSet));

        migration.run();

        verify(rulesPersistenceService, never()).setRules(anyString(), anyString(), any(RuleFileType.class));
    }


    @Test
    public void testMigrationSkipsIdenticalReasonAndTechnicalName() {

        DossierTemplateEntity dossierTemplate = createDossierTemplate(TEMPLATE_ID_1);
        when(dossierTemplateRepository.findAll()).thenReturn(Collections.singletonList(dossierTemplate));

        LegalBasisEntity lb1 = createLegalBasis("same_name", "same_name");
        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_1)).thenReturn(Collections.singletonList(lb1));

        RuleSetEntity ruleSet = createRuleSet(RULES_WITH_IDENTICAL_REASON_AND_TECHNICAL_NAME);
        when(rulesPersistenceService.getRules(TEMPLATE_ID_1, RuleFileType.ENTITY)).thenReturn(Optional.of(ruleSet));

        migration.run();

        verify(rulesPersistenceService, never()).setRules(anyString(), anyString(), any(RuleFileType.class));
    }


    @Test
    public void testMigrationProcessesMultipleDossierTemplates() {

        DossierTemplateEntity dossierTemplate1 = createDossierTemplate(TEMPLATE_ID_1);
        DossierTemplateEntity dossierTemplate2 = createDossierTemplate(TEMPLATE_ID_2);
        when(dossierTemplateRepository.findAll()).thenReturn(Arrays.asList(dossierTemplate1, dossierTemplate2));

        LegalBasisEntity lb1 = createLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002", "vertebrate_study_personal_data_geolocation_article_39e2");
        LegalBasisEntity lb2 = createLegalBasis("Article 63(2)(d) of Regulation (EC) No 1107/2009", "composition_plant_protection_product");
        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_1)).thenReturn(Collections.singletonList(lb1));
        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_2)).thenReturn(Collections.singletonList(lb2));

        RuleSetEntity ruleSet1 = createRuleSet(RULES_WITH_REASONS);
        RuleSetEntity ruleSet2 = createRuleSet(RULES_WITH_MULTIPLE_TEMPLATES);
        when(rulesPersistenceService.getRules(TEMPLATE_ID_1, RuleFileType.ENTITY)).thenReturn(Optional.of(ruleSet1));
        when(rulesPersistenceService.getRules(TEMPLATE_ID_2, RuleFileType.ENTITY)).thenReturn(Optional.of(ruleSet2));

        migration.run();

        ArgumentCaptor<String> rulesCaptor1 = ArgumentCaptor.forClass(String.class);
        ArgumentCaptor<String> rulesCaptor2 = ArgumentCaptor.forClass(String.class);
        verify(rulesPersistenceService, times(2)).setRules(any(), any(), eq(RuleFileType.ENTITY));
        verify(rulesPersistenceService).setRules(rulesCaptor1.capture(), eq(TEMPLATE_ID_1), eq(RuleFileType.ENTITY));
        verify(rulesPersistenceService).setRules(rulesCaptor2.capture(), eq(TEMPLATE_ID_2), eq(RuleFileType.ENTITY));

        String updatedRules1 = rulesCaptor1.getValue();
        String updatedRules2 = rulesCaptor2.getValue();

        assertAll(() -> {
            assertTrue(updatedRules1.contains("vertebrate_study_personal_data_geolocation_article_39e2"));
            assertFalse(updatedRules1.contains("Article 39(e)(2) of Regulation (EC) No 178/2002"));
        }, () -> {
            assertTrue(updatedRules2.contains("composition_plant_protection_product"));
            assertFalse(updatedRules2.contains("Article 63(2)(d) of Regulation (EC) No 1107/2009"));
        });
    }


    @Test
    public void testMigrationHandlesNullValuesGracefully() {

        DossierTemplateEntity dossierTemplate = createDossierTemplate(TEMPLATE_ID_1);
        when(dossierTemplateRepository.findAll()).thenReturn(Collections.singletonList(dossierTemplate));

        LegalBasisEntity lb1 = createLegalBasis(null, "personal_data_geolocation_article_39e3");
        LegalBasisEntity lb2 = createLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002", null);
        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_1)).thenReturn(Arrays.asList(lb1, lb2));

        RuleSetEntity ruleSet = createRuleSet(RULES_WITH_NULL_VALUES);
        when(rulesPersistenceService.getRules(TEMPLATE_ID_1, RuleFileType.ENTITY)).thenReturn(Optional.of(ruleSet));

        migration.run();

        verify(rulesPersistenceService, never()).setRules(anyString(), anyString(), any(RuleFileType.class));
    }


    @Test
    public void testMigrationUpdatesRuleSetWhenOnlySomeMappingsAreValid() {

        DossierTemplateEntity dossierTemplate = createDossierTemplate(TEMPLATE_ID_1);
        when(dossierTemplateRepository.findAll()).thenReturn(Collections.singletonList(dossierTemplate));

        LegalBasisEntity lb1 = createLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002", "vertebrate_study_personal_data_geolocation_article_39e2");
        LegalBasisEntity lb2 = createLegalBasis(null, "some_technical_name");
        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_1)).thenReturn(Arrays.asList(lb1, lb2));

        RuleSetEntity ruleSet = createRuleSet(RULES_WITH_PARTIAL_VALID_MAPPINGS);
        when(rulesPersistenceService.getRules(TEMPLATE_ID_1, RuleFileType.ENTITY)).thenReturn(Optional.of(ruleSet));

        migration.run();

        ArgumentCaptor<String> rulesCaptor = ArgumentCaptor.forClass(String.class);
        verify(rulesPersistenceService).setRules(rulesCaptor.capture(), eq(TEMPLATE_ID_1), eq(RuleFileType.ENTITY));

        String updatedRules = rulesCaptor.getValue();
        assertAll(() -> assertTrue(updatedRules.contains("vertebrate_study_personal_data_geolocation_article_39e2")),
                  () -> assertFalse(updatedRules.contains("Article 39(e)(2) of Regulation (EC) No 178/2002")),
                  () -> assertTrue(updatedRules.contains("Some Other Reason")));
    }


    @Test
    public void testMigrationWithEmptyRulesContent() {

        DossierTemplateEntity dossierTemplate = createDossierTemplate(TEMPLATE_ID_1);
        when(dossierTemplateRepository.findAll()).thenReturn(Collections.singletonList(dossierTemplate));

        LegalBasisEntity lb1 = createLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002", "vertebrate_study_personal_data_geolocation_article_39e2");
        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_1)).thenReturn(Collections.singletonList(lb1));

        RuleSetEntity ruleSet = createRuleSet("");
        when(rulesPersistenceService.getRules(TEMPLATE_ID_1, RuleFileType.ENTITY)).thenReturn(Optional.of(ruleSet));

        migration.run();

        verify(rulesPersistenceService, never()).setRules(anyString(), anyString(), any(RuleFileType.class));
    }


    @Test
    public void testMigrationReplacesMultipleOccurrencesOfSameReason() {

        DossierTemplateEntity dossierTemplate = createDossierTemplate(TEMPLATE_ID_1);
        when(dossierTemplateRepository.findAll()).thenReturn(Collections.singletonList(dossierTemplate));

        LegalBasisEntity lb1 = createLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002", "tech_name_39e2");
        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_1)).thenReturn(Collections.singletonList(lb1));

        String duplicatedRules = RULES_WITH_REASONS + "\n" + RULES_WITH_REASONS;
        RuleSetEntity ruleSet = createRuleSet(duplicatedRules);
        when(rulesPersistenceService.getRules(TEMPLATE_ID_1, RuleFileType.ENTITY)).thenReturn(Optional.of(ruleSet));

        migration.run();

        ArgumentCaptor<String> rulesCaptor = ArgumentCaptor.forClass(String.class);
        verify(rulesPersistenceService).setRules(rulesCaptor.capture(), eq(TEMPLATE_ID_1), eq(RuleFileType.ENTITY));

        String updatedRules = rulesCaptor.getValue();
        assertAll(() -> assertEquals(2, StringUtils.countMatches(updatedRules, "tech_name_39e2")),
                  () -> assertFalse(updatedRules.contains("Article 39(e)(2) of Regulation (EC) No 178/2002")));
    }


    @Test
    public void testMigrationDoesNotReplaceOverlappingReasonStrings() {

        DossierTemplateEntity dossierTemplate = createDossierTemplate(TEMPLATE_ID_1);
        when(dossierTemplateRepository.findAll()).thenReturn(Collections.singletonList(dossierTemplate));

        LegalBasisEntity lb1 = createLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002", "tech_name_39e2");
        LegalBasisEntity lb2 = createLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002 Extended", "tech_name_extended");
        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_1)).thenReturn(Arrays.asList(lb1, lb2));

        String overlappingRules = "rule \"Test Rule\"\n"
                                  + "    when\n"
                                  + "        $entity: TextEntity(type() == \"CBI_author\", dictionaryEntry)\n"
                                  + "    then\n"
                                  + "        $entity.redact(\"RuleCode\", \"Description\", \"Article 39(e)(2) of Regulation (EC) No 178/2002\");\n"
                                  + "        $entity.redact(\"RuleCode\", \"Description\", \"Article 39(e)(2) of Regulation (EC) No 178/2002 Extended\");\n"
                                  + "    end";

        RuleSetEntity ruleSet = createRuleSet(overlappingRules);
        when(rulesPersistenceService.getRules(TEMPLATE_ID_1, RuleFileType.ENTITY)).thenReturn(Optional.of(ruleSet));

        migration.run();

        ArgumentCaptor<String> rulesCaptor = ArgumentCaptor.forClass(String.class);
        verify(rulesPersistenceService).setRules(rulesCaptor.capture(), eq(TEMPLATE_ID_1), eq(RuleFileType.ENTITY));

        String updatedRules = rulesCaptor.getValue();
        assertAll(() -> assertTrue(updatedRules.contains("tech_name_39e2")),
                  () -> assertTrue(updatedRules.contains("tech_name_extended")),
                  () -> assertFalse(updatedRules.contains("Article 39(e)(2) of Regulation (EC) No 178/2002")),
                  () -> assertFalse(updatedRules.contains("Article 39(e)(2) of Regulation (EC) No 178/2002 Extended")));
    }


    @Test
    public void testMigrationWithDuplicateLegalBasisMappings() {

        DossierTemplateEntity dossierTemplate = createDossierTemplate(TEMPLATE_ID_1);
        when(dossierTemplateRepository.findAll()).thenReturn(Collections.singletonList(dossierTemplate));

        LegalBasisEntity lb1 = createLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002", "tech_name_39e2");
        LegalBasisEntity lb2 = createLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002", "tech_name_39e2_duplicate");
        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_1)).thenReturn(Arrays.asList(lb1, lb2));

        RuleSetEntity ruleSet = createRuleSet(RULES_WITH_REASONS);
        when(rulesPersistenceService.getRules(TEMPLATE_ID_1, RuleFileType.ENTITY)).thenReturn(Optional.of(ruleSet));

        migration.run();

        ArgumentCaptor<String> rulesCaptor = ArgumentCaptor.forClass(String.class);
        verify(rulesPersistenceService).setRules(rulesCaptor.capture(), eq(TEMPLATE_ID_1), eq(RuleFileType.ENTITY));

        String updatedRules = rulesCaptor.getValue();
        assertAll(() -> assertTrue(updatedRules.contains("tech_name_39e2_duplicate")),
                  () -> assertFalse(updatedRules.contains("\"tech_name_39e2\"")),
                  () -> assertFalse(updatedRules.contains("Article 39(e)(2) of Regulation (EC) No 178/2002")));
    }


    @Test
    public void testMigrationWithTechnicalNamesContainingQuotes() {

        DossierTemplateEntity dossierTemplate = createDossierTemplate(TEMPLATE_ID_1);
        when(dossierTemplateRepository.findAll()).thenReturn(Collections.singletonList(dossierTemplate));

        LegalBasisEntity lb1 = createLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002", "tech_name_with_\"quotes\"");
        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_1)).thenReturn(Collections.singletonList(lb1));

        RuleSetEntity ruleSet = createRuleSet(RULES_WITH_REASONS);
        when(rulesPersistenceService.getRules(TEMPLATE_ID_1, RuleFileType.ENTITY)).thenReturn(Optional.of(ruleSet));

        migration.run();

        ArgumentCaptor<String> rulesCaptor = ArgumentCaptor.forClass(String.class);
        verify(rulesPersistenceService).setRules(rulesCaptor.capture(), eq(TEMPLATE_ID_1), eq(RuleFileType.ENTITY));

        String updatedRules = rulesCaptor.getValue();
        assertAll(() -> assertTrue(updatedRules.contains("tech_name_with_\"quotes\"")),
                  () -> assertFalse(updatedRules.contains("Article 39(e)(2) of Regulation (EC) No 178/2002")));
    }


    @Test
    public void testMigrationDoesNotAlterOtherRuleFileTypes() {

        DossierTemplateEntity dossierTemplate = createDossierTemplate(TEMPLATE_ID_1);
        when(dossierTemplateRepository.findAll()).thenReturn(Collections.singletonList(dossierTemplate));

        LegalBasisEntity lb1 = createLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002", "tech_name_39e2");
        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_1)).thenReturn(Collections.singletonList(lb1));

        RuleSetEntity entityRuleSet = createRuleSet(RULES_WITH_REASONS);
        RuleSetEntity componentRuleSet = createRuleSet("component rules content");
        when(rulesPersistenceService.getRules(TEMPLATE_ID_1, RuleFileType.ENTITY)).thenReturn(Optional.of(entityRuleSet));
        when(rulesPersistenceService.getRules(TEMPLATE_ID_1, RuleFileType.COMPONENT)).thenReturn(Optional.of(componentRuleSet));

        migration.run();

        ArgumentCaptor<String> entityRulesCaptor = ArgumentCaptor.forClass(String.class);
        verify(rulesPersistenceService).setRules(entityRulesCaptor.capture(), eq(TEMPLATE_ID_1), eq(RuleFileType.ENTITY));

        String updatedEntityRules = entityRulesCaptor.getValue();
        assertAll(() -> assertTrue(updatedEntityRules.contains("tech_name_39e2")),
                  () -> assertFalse(updatedEntityRules.contains("Article 39(e)(2) of Regulation (EC) No 178/2002")));

        verify(rulesPersistenceService, never()).setRules(eq("component rules content"), eq(TEMPLATE_ID_1), eq(RuleFileType.COMPONENT));
    }


    @Test
    public void testMigrationDoesNotReprocessAlreadyMigratedRuleSets() {

        DossierTemplateEntity dossierTemplate = createDossierTemplate(TEMPLATE_ID_1);
        when(dossierTemplateRepository.findAll()).thenReturn(Collections.singletonList(dossierTemplate));

        when(migrationPersistenceService.getLatestProcessedVersion()).thenReturn(28L);

        migration.run();

        verifyNoInteractions(rulesPersistenceService);
    }


    @Test
    public void testMigrationWithLargeRuleSetsAndMappings() {

        DossierTemplateEntity dossierTemplate = createDossierTemplate(TEMPLATE_ID_1);
        when(dossierTemplateRepository.findAll()).thenReturn(Collections.singletonList(dossierTemplate));

        List<LegalBasisEntity> largeMappings = IntStream.range(0, 1000).mapToObj(i -> createLegalBasis("Article " + i + "(e)(2) of Regulation (EC) No 178/2002", "tech_name_" + i))
                .collect(Collectors.toList());
        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_1)).thenReturn(largeMappings);

        String largeRules = IntStream.range(0, 1000)
                .mapToObj(i -> "rule \"Rule "
                               + i
                               + "\"\n"
                               + "    when\n"
                               + "        $entity: TextEntity(type() == \"CBI_author\", dictionaryEntry)\n"
                               + "    then\n"
                               + "        $entity.redact(\"RuleCode\", \"Description\", \"Article "
                               + i
                               + "(e)(2) of Regulation (EC) No 178/2002\");\n"
                               + "    end\n\n")
                .collect(Collectors.joining());

        RuleSetEntity ruleSet = createRuleSet(largeRules);
        when(rulesPersistenceService.getRules(TEMPLATE_ID_1, RuleFileType.ENTITY)).thenReturn(Optional.of(ruleSet));

        migration.run();

        ArgumentCaptor<String> rulesCaptor = ArgumentCaptor.forClass(String.class);
        verify(rulesPersistenceService).setRules(rulesCaptor.capture(), eq(TEMPLATE_ID_1), eq(RuleFileType.ENTITY));

        String updatedRules = rulesCaptor.getValue();
        assertAll(() -> assertTrue(updatedRules.contains("tech_name_0")),
                  () -> assertTrue(updatedRules.contains("tech_name_500")),
                  () -> assertTrue(updatedRules.contains("tech_name_999")),
                  () -> {
                      assertFalse(updatedRules.contains("Article 0(e)(2) of Regulation (EC) No 178/2002"));
                      assertFalse(updatedRules.contains("Article 500(e)(2) of Regulation (EC) No 178/2002"));
                      assertFalse(updatedRules.contains("Article 999(e)(2) of Regulation (EC) No 178/2002"));
                  });
    }


    @Test
    public void testMigrationSkipsMappingsWithMissingTechnicalNames() {

        DossierTemplateEntity dossierTemplate = createDossierTemplate(TEMPLATE_ID_1);
        when(dossierTemplateRepository.findAll()).thenReturn(Collections.singletonList(dossierTemplate));

        LegalBasisEntity lb1 = createLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002", null);
        LegalBasisEntity lb2 = createLegalBasis("Article 39(e)(3) of Regulation (EC) No 178/2002", "tech_name_39e3");
        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_1)).thenReturn(Arrays.asList(lb1, lb2));

        RuleSetEntity ruleSet = createRuleSet(RULES_WITH_REASONS);
        when(rulesPersistenceService.getRules(TEMPLATE_ID_1, RuleFileType.ENTITY)).thenReturn(Optional.of(ruleSet));

        migration.run();

        ArgumentCaptor<String> rulesCaptor = ArgumentCaptor.forClass(String.class);
        verify(rulesPersistenceService).setRules(rulesCaptor.capture(), eq(TEMPLATE_ID_1), eq(RuleFileType.ENTITY));

        String updatedRules = rulesCaptor.getValue();
        assertAll(() -> assertTrue(updatedRules.contains("tech_name_39e3")),
                  () -> assertFalse(updatedRules.contains("Article 39(e)(3) of Regulation (EC) No 178/2002")),
                  () -> assertTrue(updatedRules.contains("Article 39(e)(2) of Regulation (EC) No 178/2002")));
    }


    @Test
    public void testMigrationWithEmptyLegalBasisMappingsList() {

        DossierTemplateEntity dossierTemplate = createDossierTemplate(TEMPLATE_ID_1);
        when(dossierTemplateRepository.findAll()).thenReturn(Collections.singletonList(dossierTemplate));

        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_1)).thenReturn(Collections.emptyList());

        migration.run();

        verifyNoInteractions(rulesPersistenceService);
    }


    @Test
    public void testMigrationProcessesOnlySpecifiedRuleFileType() {

        DossierTemplateEntity dossierTemplate = createDossierTemplate(TEMPLATE_ID_1);
        when(dossierTemplateRepository.findAll()).thenReturn(Collections.singletonList(dossierTemplate));

        LegalBasisEntity lb1 = createLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002", "tech_name_39e2");
        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_1)).thenReturn(Collections.singletonList(lb1));

        RuleSetEntity entityRuleSet = createRuleSet(RULES_WITH_REASONS);
        RuleSetEntity componentRuleSet = createRuleSet("component rules content");
        when(rulesPersistenceService.getRules(TEMPLATE_ID_1, RuleFileType.ENTITY)).thenReturn(Optional.of(entityRuleSet));
        when(rulesPersistenceService.getRules(TEMPLATE_ID_1, RuleFileType.COMPONENT)).thenReturn(Optional.of(componentRuleSet));

        migration.run();

        ArgumentCaptor<String> entityRulesCaptor = ArgumentCaptor.forClass(String.class);
        verify(rulesPersistenceService).setRules(entityRulesCaptor.capture(), eq(TEMPLATE_ID_1), eq(RuleFileType.ENTITY));

        String updatedEntityRules = entityRulesCaptor.getValue();
        assertAll(() -> assertTrue(updatedEntityRules.contains("tech_name_39e2")),
                  () -> assertFalse(updatedEntityRules.contains("Article 39(e)(2) of Regulation (EC) No 178/2002")));

        verify(rulesPersistenceService, never()).setRules(eq("component rules content"), eq(TEMPLATE_ID_1), eq(RuleFileType.COMPONENT));
    }


    @Test
    public void testMigrationWithInvalidRuleFileContent() {

        DossierTemplateEntity dossierTemplate = createDossierTemplate(TEMPLATE_ID_1);
        when(dossierTemplateRepository.findAll()).thenReturn(Collections.singletonList(dossierTemplate));

        LegalBasisEntity lb1 = createLegalBasis("Article 39(e)(2) of Regulation (EC) No 178/2002", "tech_name_39e2");
        when(legalBasisMappingPersistenceService.getLegalBasisMapping(TEMPLATE_ID_1)).thenReturn(Collections.singletonList(lb1));

        String malformedRules = "rule \"Malformed Rule\"\n"
                                + "    when\n"
                                + "        $entity: TextEntity(type() == \"CBI_author\", dictionaryEntry)\n"
                                + "    then\n"
                                + "        $entity.redact(RuleCode, Description, Article 39(e)(2) of Regulation (EC) No 178/2002);\n"
                                + "    end";

        RuleSetEntity ruleSet = createRuleSet(malformedRules);
        when(rulesPersistenceService.getRules(TEMPLATE_ID_1, RuleFileType.ENTITY)).thenReturn(Optional.of(ruleSet));

        migration.run();

        verify(rulesPersistenceService, never()).setRules(anyString(), anyString(), any(RuleFileType.class));
    }


    @Test
    public void testMigrationWithNoDossierTemplates() {

        when(dossierTemplateRepository.findAll()).thenReturn(Collections.emptyList());

        migration.run();

        verifyNoInteractions(rulesPersistenceService, legalBasisMappingPersistenceService);
    }

}
