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

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.LegalBasisEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.FileEntity;
import com.iqser.red.service.persistence.management.v1.processor.migration.migrations.V31TechnicalNameImportedFilesMigration;
import com.iqser.red.service.persistence.management.v1.processor.service.FileManagementStorageService;
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.repository.DossierRepository;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.FileRepository;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.imported.ImportedLegalBases;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.imported.ImportedLegalBasis;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.imported.ImportedRedaction;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.entitylog.imported.ImportedRedactionsPerPage;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileType;

public class TechnicalNameImportedFilesMigrationTest {

    @Mock
    private DossierRepository dossierRepository;

    @Mock
    private FileRepository fileRepository;

    @Mock
    private FileManagementStorageService fileManagementStorageService;

    @Mock
    private LegalBasisMappingPersistenceService legalBasisMappingPersistenceService;

    @Mock
    private MigrationPersistenceService migrationPersistenceService;

    private LegalBasisMappingService legalBasisMappingService;

    private V31TechnicalNameImportedFilesMigration migration;


    @BeforeEach
    public void setup() {

        MockitoAnnotations.openMocks(this);

        when(migrationPersistenceService.getLatestProcessedVersion()).thenReturn(0L);
        when(migrationPersistenceService.isProcessed(anyLong(), anyLong())).thenReturn(false);

        legalBasisMappingService = new LegalBasisMappingService(legalBasisMappingPersistenceService);

        migration = new V31TechnicalNameImportedFilesMigration(dossierRepository, fileRepository, fileManagementStorageService, legalBasisMappingService);

        migration.setMigrationPersistenceService(migrationPersistenceService);
    }


    @Test
    public void testMigrationUpdatesMappedLegalBasisTechnicalNamesInImportedRedactions() {

        DossierEntity dossier = createDossier("dossier1", "template1");
        when(dossierRepository.findAll()).thenReturn(Collections.singletonList(dossier));

        LegalBasisEntity lb1 = LegalBasisEntity.builder().technicalName("LB1_TechName").reason("LB1_Reason").build();
        LegalBasisEntity lb2 = LegalBasisEntity.builder().technicalName("LB2_TechName").reason("LB2_Reason").build();
        when(legalBasisMappingPersistenceService.getLegalBasisMapping("template1")).thenReturn(Arrays.asList(lb1, lb2));

        FileEntity file = createFile("file1", "dossier1");
        when(fileRepository.findByDossierId("dossier1")).thenReturn(Collections.singletonList(file));

        when(fileManagementStorageService.objectExists("dossier1", "file1", FileType.IMPORTED_REDACTIONS)).thenReturn(true);

        ImportedRedaction redaction1 = new ImportedRedaction();
        redaction1.setLegalBasis("LB1_Reason");

        ImportedRedaction redaction2 = new ImportedRedaction();
        redaction2.setLegalBasis("LB2_Reason");

        ImportedRedactionsPerPage importedRedactions = new ImportedRedactionsPerPage();
        importedRedactions.setImportedRedactions(Map.of(1, Arrays.asList(redaction1, redaction2)));

        when(fileManagementStorageService.getImportedRedactions("dossier1", "file1")).thenReturn(importedRedactions);

        migration.run();

        assertEquals("LB1_TechName", redaction1.getLegalBasis());
        assertEquals("LB2_TechName", redaction2.getLegalBasis());

        verify(fileManagementStorageService).storeJSONObject("dossier1", "file1", FileType.IMPORTED_REDACTIONS, importedRedactions);
    }


    @Test
    public void testMigrationHashesUnmappedLegalBasisInImportedRedactions() {

        DossierEntity dossier = createDossier("dossier1", "template1");
        when(dossierRepository.findAll()).thenReturn(Collections.singletonList(dossier));
        when(legalBasisMappingPersistenceService.getLegalBasisMapping("template1")).thenReturn(Collections.emptyList());

        FileEntity file = createFile("file1", "dossier1");
        when(fileRepository.findByDossierId("dossier1")).thenReturn(Collections.singletonList(file));

        when(fileManagementStorageService.objectExists("dossier1", "file1", FileType.IMPORTED_REDACTIONS)).thenReturn(true);

        ImportedRedaction redaction1 = new ImportedRedaction();
        redaction1.setLegalBasis("Unmapped_LB1");

        ImportedRedaction redaction2 = new ImportedRedaction();
        redaction2.setLegalBasis("Unmapped_LB2");

        ImportedRedactionsPerPage importedRedactions = new ImportedRedactionsPerPage();
        importedRedactions.setImportedRedactions(Map.of(1, Arrays.asList(redaction1, redaction2)));

        when(fileManagementStorageService.getImportedRedactions("dossier1", "file1")).thenReturn(importedRedactions);

        migration.run();

        String hashedLB1 = legalBasisMappingService.processLegalBasis("Unmapped_LB1", new HashMap<>()).technicalName();
        String hashedLB2 = legalBasisMappingService.processLegalBasis("Unmapped_LB2", new HashMap<>()).technicalName();

        assertEquals(hashedLB1, redaction1.getLegalBasis());
        assertEquals(hashedLB2, redaction2.getLegalBasis());

        verify(fileManagementStorageService).storeJSONObject("dossier1", "file1", FileType.IMPORTED_REDACTIONS, importedRedactions);
    }


    @Test
    public void testMigrationUpdatesTechnicalNamesInImportedLegalBases() {

        DossierEntity dossier = createDossier("dossier1", "template1");
        when(dossierRepository.findAll()).thenReturn(Collections.singletonList(dossier));

        LegalBasisEntity lb1 = LegalBasisEntity.builder().technicalName("LB1_TechName").reason("LB1_Reason").build();
        when(legalBasisMappingPersistenceService.getLegalBasisMapping("template1")).thenReturn(Collections.singletonList(lb1));

        FileEntity file = createFile("file1", "dossier1");
        when(fileRepository.findByDossierId("dossier1")).thenReturn(Collections.singletonList(file));

        when(fileManagementStorageService.objectExists("dossier1", "file1", FileType.IMPORTED_LEGAL_BASES)).thenReturn(true);

        ImportedLegalBasis importedLegalBasis = new ImportedLegalBasis();
        importedLegalBasis.setReason("LB1_Reason");
        importedLegalBasis.setTechnicalName(null);

        ImportedLegalBases importedLegalBases = new ImportedLegalBases();
        importedLegalBases.setImportedLegalBases(Collections.singletonList(importedLegalBasis));

        when(fileManagementStorageService.getImportedLegalBases("dossier1", "file1")).thenReturn(importedLegalBases);

        migration.run();

        assertEquals("LB1_TechName", importedLegalBasis.getTechnicalName());

        verify(fileManagementStorageService).storeJSONObject("dossier1", "file1", FileType.IMPORTED_LEGAL_BASES, importedLegalBases);
    }


    @Test
    public void testMigrationHandlesFilesWithoutImportedRedactionsOrLegalBases() {

        DossierEntity dossier = createDossier("dossier1", "template1");
        when(dossierRepository.findAll()).thenReturn(Collections.singletonList(dossier));
        when(legalBasisMappingPersistenceService.getLegalBasisMapping("template1")).thenReturn(Collections.emptyList());

        FileEntity file = createFile("file1", "dossier1");
        when(fileRepository.findByDossierId("dossier1")).thenReturn(Collections.singletonList(file));

        when(fileManagementStorageService.objectExists("dossier1", "file1", FileType.IMPORTED_REDACTIONS)).thenReturn(false);
        when(fileManagementStorageService.objectExists("dossier1", "file1", FileType.IMPORTED_LEGAL_BASES)).thenReturn(false);

        migration.run();

        verify(fileManagementStorageService, never()).getImportedRedactions(anyString(), anyString());
        verify(fileManagementStorageService, never()).getImportedLegalBases(anyString(), anyString());

        verify(fileManagementStorageService, never()).storeJSONObject(anyString(), anyString(), any(FileType.class), any());
    }


    @Test
    public void testMigrationSkipsDossiersWithNoFiles() {

        DossierEntity dossier = createDossier("dossier1", "template1");
        when(dossierRepository.findAll()).thenReturn(Collections.singletonList(dossier));
        when(fileRepository.findByDossierId("dossier1")).thenReturn(Collections.emptyList());

        migration.run();

        verify(fileManagementStorageService, never()).objectExists(anyString(), anyString(), any(FileType.class));
    }


    @Test
    public void testMigrationProcessesMultipleDossiersAndFiles() {

        DossierEntity dossier1 = createDossier("dossier1", "template1");
        DossierEntity dossier2 = createDossier("dossier2", "template2");
        when(dossierRepository.findAll()).thenReturn(Arrays.asList(dossier1, dossier2));

        LegalBasisEntity lb1 = LegalBasisEntity.builder().technicalName("LB1_TechName").reason("LB1_Reason").build();
        when(legalBasisMappingPersistenceService.getLegalBasisMapping("template1")).thenReturn(Collections.singletonList(lb1));

        LegalBasisEntity lb2 = LegalBasisEntity.builder().technicalName("LB2_TechName").reason("LB2_Reason").build();
        when(legalBasisMappingPersistenceService.getLegalBasisMapping("template2")).thenReturn(Collections.singletonList(lb2));

        FileEntity file1 = createFile("file1", "dossier1");
        FileEntity file2 = createFile("file2", "dossier2");
        when(fileRepository.findByDossierId("dossier1")).thenReturn(Collections.singletonList(file1));
        when(fileRepository.findByDossierId("dossier2")).thenReturn(Collections.singletonList(file2));

        when(fileManagementStorageService.objectExists("dossier1", "file1", FileType.IMPORTED_REDACTIONS)).thenReturn(true);
        when(fileManagementStorageService.objectExists("dossier2", "file2", FileType.IMPORTED_REDACTIONS)).thenReturn(true);

        ImportedRedaction redaction1 = new ImportedRedaction();
        redaction1.setLegalBasis("LB1_Reason");

        ImportedRedactionsPerPage importedRedactions1 = new ImportedRedactionsPerPage();
        importedRedactions1.setImportedRedactions(Map.of(1, Collections.singletonList(redaction1)));

        when(fileManagementStorageService.getImportedRedactions("dossier1", "file1")).thenReturn(importedRedactions1);

        ImportedRedaction redaction2 = new ImportedRedaction();
        redaction2.setLegalBasis("LB2_Reason");

        ImportedRedactionsPerPage importedRedactions2 = new ImportedRedactionsPerPage();
        importedRedactions2.setImportedRedactions(Map.of(1, Collections.singletonList(redaction2)));

        when(fileManagementStorageService.getImportedRedactions("dossier2", "file2")).thenReturn(importedRedactions2);

        migration.run();

        assertEquals("LB1_TechName", redaction1.getLegalBasis());
        assertEquals("LB2_TechName", redaction2.getLegalBasis());

        verify(fileManagementStorageService).storeJSONObject("dossier1", "file1", FileType.IMPORTED_REDACTIONS, importedRedactions1);
        verify(fileManagementStorageService).storeJSONObject("dossier2", "file2", FileType.IMPORTED_REDACTIONS, importedRedactions2);
    }


    @Test
    public void testMigrationDoesNotUpdateWhenNoChangesNeeded() {

        DossierEntity dossier = createDossier("dossier1", "template1");
        when(dossierRepository.findAll()).thenReturn(Collections.singletonList(dossier));

        LegalBasisEntity lb1 = LegalBasisEntity.builder().technicalName("LB1_TechName").reason("LB1_Reason").build();
        when(legalBasisMappingPersistenceService.getLegalBasisMapping("template1")).thenReturn(Collections.singletonList(lb1));

        FileEntity file = createFile("file1", "dossier1");
        when(fileRepository.findByDossierId("dossier1")).thenReturn(Collections.singletonList(file));

        when(fileManagementStorageService.objectExists("dossier1", "file1", FileType.IMPORTED_REDACTIONS)).thenReturn(true);

        ImportedRedaction redaction = new ImportedRedaction();
        redaction.setLegalBasis("LB1_TechName");

        ImportedRedactionsPerPage importedRedactions = new ImportedRedactionsPerPage();
        importedRedactions.setImportedRedactions(Map.of(1,Collections.singletonList(redaction)));

        when(fileManagementStorageService.getImportedRedactions("dossier1", "file1")).thenReturn(importedRedactions);

        migration.run();

        assertEquals("LB1_TechName", redaction.getLegalBasis());

        verify(fileManagementStorageService, never()).storeJSONObject(anyString(), anyString(), any(FileType.class), any());
    }


    @Test
    public void testMigrationHandlesImportedLegalBasesWithExistingTechnicalNames() {

        DossierEntity dossier = createDossier("dossier1", "template1");
        when(dossierRepository.findAll()).thenReturn(Collections.singletonList(dossier));

        LegalBasisEntity lb1 = LegalBasisEntity.builder().technicalName("LB1_TechName").reason("LB1_Reason").build();
        when(legalBasisMappingPersistenceService.getLegalBasisMapping("template1")).thenReturn(Collections.singletonList(lb1));

        FileEntity file = createFile("file1", "dossier1");
        when(fileRepository.findByDossierId("dossier1")).thenReturn(Collections.singletonList(file));

        when(fileManagementStorageService.objectExists("dossier1", "file1", FileType.IMPORTED_LEGAL_BASES)).thenReturn(true);

        ImportedLegalBasis importedLegalBasis = new ImportedLegalBasis();
        importedLegalBasis.setReason("LB1_Reason");
        importedLegalBasis.setTechnicalName("LB1_TechName");

        ImportedLegalBases importedLegalBases = new ImportedLegalBases();
        importedLegalBases.setImportedLegalBases(Collections.singletonList(importedLegalBasis));

        when(fileManagementStorageService.getImportedLegalBases("dossier1", "file1")).thenReturn(importedLegalBases);

        migration.run();

        assertEquals("LB1_TechName", importedLegalBasis.getTechnicalName());

        verify(fileManagementStorageService, never()).storeJSONObject(anyString(), anyString(), any(FileType.class), any());
    }


    @Test
    public void testMigrationHashesUnmappedReasonsInImportedLegalBases() {

        DossierEntity dossier = createDossier("dossier1", "template1");
        when(dossierRepository.findAll()).thenReturn(Collections.singletonList(dossier));
        when(legalBasisMappingPersistenceService.getLegalBasisMapping("template1")).thenReturn(Collections.emptyList());

        FileEntity file = createFile("file1", "dossier1");
        when(fileRepository.findByDossierId("dossier1")).thenReturn(Collections.singletonList(file));

        when(fileManagementStorageService.objectExists("dossier1", "file1", FileType.IMPORTED_LEGAL_BASES)).thenReturn(true);

        ImportedLegalBasis importedLegalBasis = new ImportedLegalBasis();
        importedLegalBasis.setReason("Unmapped_Reason");
        importedLegalBasis.setTechnicalName(null);

        ImportedLegalBases importedLegalBases = new ImportedLegalBases();
        importedLegalBases.setImportedLegalBases(Collections.singletonList(importedLegalBasis));

        when(fileManagementStorageService.getImportedLegalBases("dossier1", "file1")).thenReturn(importedLegalBases);

        migration.run();

        String hashedTechnicalName = legalBasisMappingService.processLegalBasis("Unmapped_Reason", new HashMap<>()).technicalName();

        assertEquals(hashedTechnicalName, importedLegalBasis.getTechnicalName());

        verify(fileManagementStorageService).storeJSONObject("dossier1", "file1", FileType.IMPORTED_LEGAL_BASES, importedLegalBases);
    }


    private DossierEntity createDossier(String id, String templateId) {

        DossierEntity dossier = new DossierEntity();
        dossier.setId(id);
        dossier.setDossierTemplateId(templateId);
        return dossier;
    }


    private FileEntity createFile(String id, String dossierId) {

        FileEntity file = new FileEntity();
        file.setId(id);
        file.setDossierId(dossierId);
        return file;
    }

}
