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

import static org.assertj.core.api.Assertions.assertThat;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.iqser.red.service.peristence.v1.server.integration.service.DossierTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.service.FileTesterAndProvider;
import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest;
import com.iqser.red.service.persistence.management.v1.processor.migration.StorageToMongoCopyService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileManagementStorageService;
import com.iqser.red.service.persistence.management.v1.processor.utils.StorageIdUtils;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntryValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.OldComponentLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.OldComponentLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.OldComponentLogEntryValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileType;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.document.OldComponentLogEntryDocument;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.mapper.ComponentLogDocumentMapper;
import com.iqser.red.service.persistence.service.v1.api.shared.mongo.repository.OldComponentLogEntryDocumentRepository;
import com.iqser.red.storage.commons.service.StorageService;
import com.knecon.fforesight.tenantcommons.TenantContext;

import lombok.SneakyThrows;

public class ComponentLogStorageToMongoDBTest extends AbstractPersistenceServerServiceTest {

    @Autowired
    private StorageService storageService;
    @Autowired
    private OldComponentLogEntryDocumentRepository oldComponentLogEntryDocumentRepository;
    @Autowired
    private FileManagementStorageService fileManagementStorageService;
    @Autowired
    private StorageToMongoCopyService storageToMongoCopyService;
    @Autowired
    private DossierTesterAndProvider dossierTesterAndProvider;
    @Autowired
    private FileTesterAndProvider fileTesterAndProvider;

    private final ComponentLogDocumentMapper mapper = ComponentLogDocumentMapper.INSTANCE;


    @Test
    @SneakyThrows
    public void testComponentLogMigration() {

        var dossier = dossierTesterAndProvider.provideTestDossier();
        var file = fileTesterAndProvider.testAndProvideFile(dossier, "filename");

        String dossierId = dossier.getId();
        String fileId = file.getId();

        OldComponentLog oldComponentLog = createOldComponentLog();

        storeOldComponentLogInStorage(dossierId, fileId, oldComponentLog);

        List<OldComponentLogEntryDocument> oldOverrides = createAndSaveOldOverrides(dossierId, fileId);

        storageToMongoCopyService.copyComponentLogs();

        ComponentLog newComponentLog = fileManagementStorageService.getComponentLog(dossierId, fileId);

        verifyComponentLogMigration(oldComponentLog, oldOverrides, newComponentLog);
    }


    @Test
    @SneakyThrows
    public void testComponentLogMigrationWithMultipleOverrides() {

        var dossier = dossierTesterAndProvider.provideTestDossier();
        var file = fileTesterAndProvider.testAndProvideFile(dossier, "filename");

        String dossierId = dossier.getId();
        String fileId = file.getId();

        OldComponentLog oldComponentLog = createOldComponentLogWithMultipleEntries();

        storeOldComponentLogInStorage(dossierId, fileId, oldComponentLog);

        List<OldComponentLogEntryDocument> oldOverrides = createAndSaveMultipleOldOverrides(dossierId, fileId);

        storageToMongoCopyService.copyComponentLogs();

        ComponentLog newComponentLog = fileManagementStorageService.getComponentLog(dossierId, fileId);

        verifyComponentLogMigration(oldComponentLog, oldOverrides, newComponentLog);
    }


    @Test
    @SneakyThrows
    public void testComponentLogMigrationWithMultipleOverrideValues() {

        var dossier = dossierTesterAndProvider.provideTestDossier();
        var file = fileTesterAndProvider.testAndProvideFile(dossier, "filename");

        String dossierId = dossier.getId();
        String fileId = file.getId();

        OldComponentLog oldComponentLog = createOldComponentLog();

        storeOldComponentLogInStorage(dossierId, fileId, oldComponentLog);

        List<OldComponentLogEntryDocument> oldOverrides = createAndSaveOverrideWithMultipleValues(dossierId, fileId);

        storageToMongoCopyService.copyComponentLogs();

        ComponentLog newComponentLog = fileManagementStorageService.getComponentLog(dossierId, fileId);

        verifyComponentLogMigration(oldComponentLog, oldOverrides, newComponentLog);
    }


    @Test
    @SneakyThrows
    public void testComponentLogMigrationWithOverriddenFlagTrueButNoValues() {

        var dossier = dossierTesterAndProvider.provideTestDossier();
        var file = fileTesterAndProvider.testAndProvideFile(dossier, "filename");

        String dossierId = dossier.getId();
        String fileId = file.getId();

        OldComponentLog oldComponentLog = createOldComponentLog();

        storeOldComponentLogInStorage(dossierId, fileId, oldComponentLog);

        List<OldComponentLogEntryDocument> oldOverrides = createAndSaveOverrideWithNoValues(dossierId, fileId);

        storageToMongoCopyService.copyComponentLogs();

        ComponentLog newComponentLog = fileManagementStorageService.getComponentLog(dossierId, fileId);

        verifyComponentLogMigration(oldComponentLog, oldOverrides, newComponentLog);
    }


    @Test
    @SneakyThrows
    public void testComponentLogMigrationFromJsonFile() {

        ClassPathResource oldComponentLogFile = new ClassPathResource("files/componentlog/oldExampleComponentLog.json");
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        OldComponentLog oldComponentLog = objectMapper.readValue(oldComponentLogFile.getInputStream(), OldComponentLog.class);

        var dossier = dossierTesterAndProvider.provideTestDossier();
        var fileEntity = fileTesterAndProvider.testAndProvideFile(dossier, "filename");

        String dossierId = dossier.getId();
        String fileId = fileEntity.getId();

        storeOldComponentLogInStorage(dossierId, fileId, oldComponentLog);

        storageToMongoCopyService.copyComponentLogs();

        ComponentLog newComponentLog = fileManagementStorageService.getComponentLog(dossierId, fileId);

        ClassPathResource expectedComponentLogFile = new ClassPathResource("files/componentlog/exampleComponentLog.json");
        ComponentLog expectedComponentLog = objectMapper.readValue(expectedComponentLogFile.getInputStream(), ComponentLog.class);

        assertThat(newComponentLog).usingRecursiveComparison().isEqualTo(expectedComponentLog);
    }


    private OldComponentLog createOldComponentLog() {

        OldComponentLog oldComponentLog = new OldComponentLog();
        oldComponentLog.setAnalysisNumber(123);
        oldComponentLog.setComponentRulesVersion(1L);

        OldComponentLogEntry entry1 = new OldComponentLogEntry();
        entry1.setName("entry1");
        entry1.setOverridden(false);
        entry1.setComponentValues(Arrays.asList(new OldComponentLogEntryValue("value1", "originalValue1", "description1", "ruleId1", null),
                                                new OldComponentLogEntryValue("value2", "originalValue2", "description2", "ruleId2", null)));

        OldComponentLogEntry entry2 = new OldComponentLogEntry();
        entry2.setName("entry2");
        entry2.setOverridden(false);
        entry2.setComponentValues(List.of(new OldComponentLogEntryValue("value3", "originalValue3", "description3", "ruleId3", null)));

        oldComponentLog.setComponentLogEntries(Arrays.asList(entry1, entry2));

        return oldComponentLog;
    }


    private void storeOldComponentLogInStorage(String dossierId, String fileId, OldComponentLog oldComponentLog) {

        String storageId = StorageIdUtils.getStorageId(dossierId, fileId, FileType.COMPONENT_LOG);
        storageService.storeJSONObject(TenantContext.getTenantId(), storageId, oldComponentLog);
    }


    private List<OldComponentLogEntryDocument> createAndSaveOldOverrides(String dossierId, String fileId) {

        List<OldComponentLogEntryDocument> oldOverrides = new ArrayList<>();

        OldComponentLogEntryDocument overrideEntry = new OldComponentLogEntryDocument();
        String componentLogId = mapper.getLogId(dossierId, fileId);
        overrideEntry.setComponentLogId(componentLogId);
        overrideEntry.setName("entry1");
        overrideEntry.setOverridden(true);

        ComponentLogEntryValue overrideValue = new ComponentLogEntryValue();
        overrideValue.setValue("overriddenValue1");
        overrideValue.setValueDescription("overriddenDescription1");
        overrideValue.setComponentRuleId("ruleId1");

        overrideEntry.setOverrideValues(Collections.singletonList(overrideValue));

        oldComponentLogEntryDocumentRepository.save(overrideEntry);

        oldOverrides.add(overrideEntry);
        return oldOverrides;
    }


    private void verifyComponentLogMigration(OldComponentLog oldComponentLog, List<OldComponentLogEntryDocument> oldOverrides, ComponentLog newComponentLog) {

        assertThat(newComponentLog.getAnalysisNumber()).isEqualTo(oldComponentLog.getAnalysisNumber());
        assertThat(newComponentLog.getComponentRulesVersion()).isEqualTo(oldComponentLog.getComponentRulesVersion());

        List<ComponentLogEntry> newEntries = newComponentLog.getComponentLogEntries();
        assertThat(newEntries).hasSize(oldComponentLog.getComponentLogEntries().size());

        Map<String, OldComponentLogEntryDocument> overrideMap = oldOverrides.stream()
                .collect(Collectors.toMap(OldComponentLogEntryDocument::getName, Function.identity()));

        for (OldComponentLogEntry oldEntry : oldComponentLog.getComponentLogEntries()) {
            ComponentLogEntry newEntry = newEntries.stream()
                    .filter(e -> e.getName().equals(oldEntry.getName()))
                    .findFirst()
                    .orElse(null);

            assertThat(newEntry).isNotNull();

            List<String> oldOriginalValues = oldEntry.getComponentValues()
                    .stream()
                    .map(OldComponentLogEntryValue::getOriginalValue)
                    .collect(Collectors.toList());

            List<String> newOriginalValues = newEntry.getOriginalValues()
                    .stream()
                    .map(ComponentLogEntryValue::getValue)
                    .collect(Collectors.toList());

            assertThat(newOriginalValues).isEqualTo(oldOriginalValues);

            if (overrideMap.containsKey(oldEntry.getName())) {
                assertThat(newEntry.isOverridden()).isTrue();

                List<String> overrideValues = overrideMap.get(oldEntry.getName()).getOverrideValues()
                        .stream()
                        .map(ComponentLogEntryValue::getValue)
                        .collect(Collectors.toList());

                List<String> newValues = newEntry.getValues()
                        .stream()
                        .map(ComponentLogEntryValue::getValue)
                        .collect(Collectors.toList());

                assertThat(newValues).isEqualTo(overrideValues);
            } else {
                assertThat(newEntry.isOverridden()).isEqualTo(oldEntry.isOverridden());

                List<String> oldValues = oldEntry.getComponentValues()
                        .stream()
                        .map(OldComponentLogEntryValue::getValue)
                        .collect(Collectors.toList());

                List<String> newValues = newEntry.getValues()
                        .stream()
                        .map(ComponentLogEntryValue::getValue)
                        .collect(Collectors.toList());

                assertThat(newValues).isEqualTo(oldValues);
            }
        }
    }


    private OldComponentLog createOldComponentLogWithMultipleEntries() {

        OldComponentLog oldComponentLog = new OldComponentLog();
        oldComponentLog.setAnalysisNumber(123);
        oldComponentLog.setComponentRulesVersion(1L);

        OldComponentLogEntry entry1 = new OldComponentLogEntry();
        entry1.setName("entry1");
        entry1.setOverridden(false);
        entry1.setComponentValues(Arrays.asList(new OldComponentLogEntryValue("value1", "originalValue1", "description1", "ruleId1", null),
                                                new OldComponentLogEntryValue("value2", "originalValue2", "description2", "ruleId2", null)));

        OldComponentLogEntry entry2 = new OldComponentLogEntry();
        entry2.setName("entry2");
        entry2.setOverridden(false);
        entry2.setComponentValues(Collections.singletonList(new OldComponentLogEntryValue("value3", "originalValue3", "description3", "ruleId3", null)));

        OldComponentLogEntry entry3 = new OldComponentLogEntry();
        entry3.setName("entry3");
        entry3.setOverridden(false);
        entry3.setComponentValues(Collections.singletonList(new OldComponentLogEntryValue("value4", "originalValue4", "description4", "ruleId4", null)));

        oldComponentLog.setComponentLogEntries(Arrays.asList(entry1, entry2, entry3));

        return oldComponentLog;
    }


    private List<OldComponentLogEntryDocument> createAndSaveMultipleOldOverrides(String dossierId, String fileId) {

        List<OldComponentLogEntryDocument> oldOverrides = new ArrayList<>();

        String componentLogId = mapper.getLogId(dossierId, fileId);

        OldComponentLogEntryDocument overrideEntry1 = new OldComponentLogEntryDocument();
        overrideEntry1.setComponentLogId(componentLogId);
        overrideEntry1.setName("entry1");
        overrideEntry1.setOverridden(true);

        ComponentLogEntryValue overrideValue1 = new ComponentLogEntryValue();
        overrideValue1.setValue("overriddenValue1");
        overrideValue1.setValueDescription("overriddenDescription1");
        overrideValue1.setComponentRuleId("ruleId1");

        overrideEntry1.setOverrideValues(Collections.singletonList(overrideValue1));

        oldComponentLogEntryDocumentRepository.save(overrideEntry1);
        oldOverrides.add(overrideEntry1);

        OldComponentLogEntryDocument overrideEntry2 = new OldComponentLogEntryDocument();
        overrideEntry2.setComponentLogId(componentLogId);
        overrideEntry2.setName("entry3");
        overrideEntry2.setOverridden(true);

        ComponentLogEntryValue overrideValue2 = new ComponentLogEntryValue();
        overrideValue2.setValue("overriddenValue3");
        overrideValue2.setValueDescription("overriddenDescription3");
        overrideValue2.setComponentRuleId("ruleId4");

        overrideEntry2.setOverrideValues(Collections.singletonList(overrideValue2));

        oldComponentLogEntryDocumentRepository.save(overrideEntry2);
        oldOverrides.add(overrideEntry2);

        return oldOverrides;
    }


    private List<OldComponentLogEntryDocument> createAndSaveOverrideWithMultipleValues(String dossierId, String fileId) {

        List<OldComponentLogEntryDocument> oldOverrides = new ArrayList<>();

        String componentLogId = mapper.getLogId(dossierId, fileId);

        OldComponentLogEntryDocument overrideEntry = new OldComponentLogEntryDocument();
        overrideEntry.setComponentLogId(componentLogId);
        overrideEntry.setName("entry1");
        overrideEntry.setOverridden(true);

        ComponentLogEntryValue overrideValue1 = new ComponentLogEntryValue();
        overrideValue1.setValue("overriddenValue1");
        overrideValue1.setValueDescription("overriddenDescription1");
        overrideValue1.setComponentRuleId("ruleId1");

        ComponentLogEntryValue overrideValue2 = new ComponentLogEntryValue();
        overrideValue2.setValue("overriddenValue2");
        overrideValue2.setValueDescription("overriddenDescription2");
        overrideValue2.setComponentRuleId("ruleId2");

        overrideEntry.setOverrideValues(Arrays.asList(overrideValue1, overrideValue2));

        oldComponentLogEntryDocumentRepository.save(overrideEntry);
        oldOverrides.add(overrideEntry);

        return oldOverrides;
    }


    private List<OldComponentLogEntryDocument> createAndSaveOverrideWithNoValues(String dossierId, String fileId) {

        List<OldComponentLogEntryDocument> oldOverrides = new ArrayList<>();

        String componentLogId = mapper.getLogId(dossierId, fileId);

        OldComponentLogEntryDocument overrideEntry = new OldComponentLogEntryDocument();
        overrideEntry.setComponentLogId(componentLogId);
        overrideEntry.setName("entry2");
        overrideEntry.setOverridden(true);
        overrideEntry.setOverrideValues(Collections.emptyList());

        oldComponentLogEntryDocumentRepository.save(overrideEntry);
        oldOverrides.add(overrideEntry);

        return oldOverrides;
    }



    @Test
    @SneakyThrows
    @Disabled
    // this test can be used to migrate old to new component log files
    public void testComponentLogMigrationAndSaveResult() {

        ClassPathResource oldComponentLogFile = new ClassPathResource("files/componentlog/componentLog.json");
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        OldComponentLog oldComponentLog = objectMapper.readValue(oldComponentLogFile.getInputStream(), OldComponentLog.class);

        var dossier = dossierTesterAndProvider.provideTestDossier();
        var fileEntity = fileTesterAndProvider.testAndProvideFile(dossier, "filename");

        String dossierId = dossier.getId();
        String fileId = fileEntity.getId();

        storeOldComponentLogInStorage(dossierId, fileId, oldComponentLog);

        storageToMongoCopyService.copyComponentLogs();

        ComponentLog newComponentLog = fileManagementStorageService.getComponentLog(dossierId, fileId);

        String newComponentLogJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(newComponentLog);

        Files.writeString(Path.of("/tmp/result.json"), newComponentLogJson);
    }

}
