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

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.when;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mock.web.MockMultipartFile;

import com.iqser.red.service.peristence.v1.server.integration.client.DictionaryClient;
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.utils.AbstractPersistenceServerServiceTest;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierTemplateManagementService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.TypeResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.TypeValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DossierTemplate;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.importexport.ImportDossierTemplateRequest;
import com.iqser.red.service.redaction.v1.model.DroolsBlacklistErrorMessage;
import com.iqser.red.service.redaction.v1.model.DroolsValidation;
import com.iqser.red.service.redaction.v1.model.RuleValidationModel;
import com.knecon.fforesight.tenantcommons.TenantContext;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class DossierTemplateImportTest extends AbstractPersistenceServerServiceTest {

    public static final String DOSSIERTEMPLATES_PATH = "files/dossiertemplates/";
    @Autowired
    private DossierTemplateManagementService dossierTemplateManagementService;

    @Autowired
    private DossierTemplateClient dossierTemplateClient;

    @Autowired
    private DictionaryClient dictionaryClient;

    @Autowired
    private DossierTemplateTesterAndProvider dossierTemplateTesterAndProvider;



    @SneakyThrows
    @Test
    // For local testing, so disabled for CI
    @Disabled
    public void testLocalDossierTemplateImport() {

        var importDir = new File("/tmp/knoell");
        assertThat(importDir).isNotNull();
        assertThat(importDir.exists()).isTrue();

        TenantContext.setTenantId("redaction");
        for (var file : importDir.listFiles()) {
            if (file.isDirectory()) {
                var archive = pack(file.getAbsolutePath());
                log.info("Importing file: " + file.getName() + " " + " with size: " + archive.length);
                testDossierTemplateImport(archive);
            }
        }
    }


    public byte[] pack(String sourceDirPath) throws IOException {

        var bos = new ByteArrayOutputStream();
        var p = Paths.get(sourceDirPath);
        try (ZipOutputStream zs = new ZipOutputStream(bos)) {
            try (Stream<Path> paths = Files.walk(p)) {
                {
                    paths.filter(path -> !Files.isDirectory(path))
                            .forEach(path -> {
                                ZipEntry zipEntry = new ZipEntry(p.relativize(path).toString());
                                try {
                                    zs.putNextEntry(zipEntry);
                                    Files.copy(path, zs);
                                    zs.closeEntry();
                                } catch (IOException e) {
                                    log.error("Failed to read a file", e);
                                }
                            });
                }
            }
        }

        return bos.toByteArray();

    }

    @SneakyThrows
    @Test
    public void testDocumineDossierTemplateImport() {

        TenantContext.setTenantId("redaction");

        var archive = loadFileFromClasspath("Flora.zip");
        testDossierTemplateImport(archive);
    }


    @SneakyThrows
    @Test
    public void testDossierTemplateImportAsNew() {

        TenantContext.setTenantId("redaction");

        var archive = loadFileFromClasspath("oldDossierTemplate.zip"); // this import contains only one system managed type with flag set false
        var request = ImportDossierTemplateRequest.builder().archive(archive).updateExistingDossierTemplate(false).userId("system").build();
        DossierTemplate dossierTemplate = dossierTemplateManagementService.importDossierTemplate(request);

        TypeResponse types = dictionaryClient.getAllTypes(dossierTemplate.getId(), null, true);
        List<TypeValue> systemManagedTypes = types.getTypes().stream().filter(TypeValue::isSystemManaged).collect(Collectors.toList());
        assertThat(systemManagedTypes.size()).isEqualTo(8);
    }

    @SneakyThrows
    @Test
    public void testDossierTemplateImportAsUpdate() {

        TenantContext.setTenantId("redaction");

//        var archive = loadFileFromClasspath("EFSA_sanitisation_GFL_v1.zip");
        var archive = loadFileFromClasspath("oldDossierTemplate.zip");
        var initialDossierTemplate = dossierTemplateTesterAndProvider.provideTestTemplate();
        TypeResponse initTypes = dictionaryClient.getAllTypes(initialDossierTemplate.getId(), null, true);
        List<TypeValue> initSystemManagedTypes = initTypes.getTypes().stream().filter(TypeValue::isSystemManaged).collect(Collectors.toList());
        assertThat(initSystemManagedTypes.size()).isEqualTo(8);

        var request = ImportDossierTemplateRequest.builder().archive(archive).dossierTemplateId(initialDossierTemplate.getId()).updateExistingDossierTemplate(true).userId("system").build();
        DossierTemplate dossierTemplate = dossierTemplateManagementService.importDossierTemplate(request);

        TypeResponse types = dictionaryClient.getAllTypes(dossierTemplate.getId(), null, true);
        List<TypeValue> systemManagedTypes = types.getTypes().stream().filter(TypeValue::isSystemManaged).collect(Collectors.toList());
        assertThat(systemManagedTypes.size()).isEqualTo(8);
    }

    @SneakyThrows
    @Test
    public void testDossierTemplateImportAsUpdateDM() {

        TenantContext.setTenantId("redaction");

        var archive = loadFileFromClasspath("dm.zip");
        var request = ImportDossierTemplateRequest.builder().archive(archive).updateExistingDossierTemplate(false).userId("system").build();
        DossierTemplate dossierTemplate = dossierTemplateManagementService.importDossierTemplate(request);

        TypeResponse types = dictionaryClient.getAllTypes(dossierTemplate.getId(), null, true);
        List<TypeValue> systemManagedTypes = types.getTypes().stream().filter(TypeValue::isSystemManaged).collect(Collectors.toList());
        assertThat(systemManagedTypes.size()).isEqualTo(8);

        var request1 = ImportDossierTemplateRequest.builder().archive(archive).dossierTemplateId(dossierTemplate.getId()).updateExistingDossierTemplate(true).userId("system").build();
        dossierTemplateManagementService.importDossierTemplate(request1);
    }

    @SneakyThrows
    @Test
    public void testDossierTemplateImportWithTheSameName() {

        TenantContext.setTenantId("redaction");

        var archive = loadFileFromClasspath("EFSA_sanitisation_GFL_v1.zip");
        var dt1 = testDossierTemplateImport(archive);
        assertThat(dt1.getName()).isEqualTo("EFSA sanitisation GFL v1");
        var dt2 = testDossierTemplateImport(archive);
        assertThat(dt2.getName()).isEqualTo("Copy of EFSA sanitisation GFL v1");
    }

    @Test
    void testDossierTemplateImportDMWithError() {

        TenantContext.setTenantId("redaction");

        List<DroolsBlacklistErrorMessage> blacklistErrorMessages = new ArrayList<>();
        List<String> blacklistedKeywords = new ArrayList<>();
        blacklistedKeywords.add("System.");
        blacklistErrorMessages.add(DroolsBlacklistErrorMessage.builder().blacklistedKeywords(blacklistedKeywords).build());
        when(redactionClient.testRules(new RuleValidationModel(RuleFileType.COMPONENT.name(), Mockito.any()))).thenReturn(DroolsValidation.builder()
                                                                                                                                          .syntaxErrorMessages(Collections.emptyList())
                                                                                                                                          .deprecatedWarnings(Collections.emptyList())
                                                                                                                                          .blacklistErrorMessages(blacklistErrorMessages)
                                                                                                                                          .build());

        var archive = loadFileFromClasspath("Flora.zip");
        var request = ImportDossierTemplateRequest.builder().archive(archive).updateExistingDossierTemplate(false).userId("system").build();
        var e = assertThrows(BadRequestException.class,
                             () -> dossierTemplateManagementService.importDossierTemplate(request));

        assertThat(e.getMessage().contains("The import process failed in step: 13")).isTrue();
        var reportTemplates = reportTemplateRepository.findAll();
        assertThat(reportTemplates).hasSize(2);
        var alldossierTemplates = dossierTemplateManagementService.getAllDossierTemplates();
        assertThat(alldossierTemplates).hasSize(1);
    }


    @SneakyThrows
    private byte[] loadFileFromClasspath(String filename) {

        ClassPathResource classPathResource = new ClassPathResource(DOSSIERTEMPLATES_PATH + filename);
        assertThat(classPathResource.exists()).isTrue();
        try (InputStream inputStream = classPathResource.getInputStream()) {
            return inputStream.readAllBytes();
        }
    }


    private DossierTemplate testDossierTemplateImport(byte[] archive) {

        var request = ImportDossierTemplateRequest.builder().archive(archive).updateExistingDossierTemplate(false).userId("system").build();
        DossierTemplate dossierTemplate = dossierTemplateManagementService.importDossierTemplate(request);
        assertThat(dossierTemplate).isNotNull();
        assertThat(dossierTemplate.getId()).isNotBlank();
        return dossierTemplate;
    }


    @SneakyThrows
    @Test
    public void testDossierTemplateImportOverwrite() {

        TenantContext.setTenantId("redaction");

        var archive = loadFileFromClasspath("EFSA_sanitisation_GFL_v1.zip");
        var dossierTemplate = testDossierTemplateImport(archive);

        var request = ImportDossierTemplateRequest.builder()
                .archive(archive)
                .updateExistingDossierTemplate(true)
                .userId("system")
                .dossierTemplateId(dossierTemplate.getId())
                .build();
        DossierTemplate reimportedTemplate = dossierTemplateManagementService.importDossierTemplate(request);
        assertThat(reimportedTemplate).isNotNull();
        assertThat(reimportedTemplate.getId()).isNotBlank();
    }


    @Test
    @SneakyThrows
    // This is currently not working, since there seems to be some missing Feign decoder config for tests
    @Disabled
    public void dossierImportClientTest() {

        var multipartFile = loadMultiPartFileFromClasspath("EFSA_sanitisation_GFL_v1.zip");
        DossierTemplateModel dossierTemplateModel = dossierTemplateClient.importDossierTemplate(multipartFile, null, false);
        assertThat(dossierTemplateModel).isNotNull();
        assertThat(dossierTemplateModel.getId()).isNotBlank();

    }


    @SneakyThrows
    private MockMultipartFile loadMultiPartFileFromClasspath(String filename) {

        ClassPathResource classPathResource = new ClassPathResource(DOSSIERTEMPLATES_PATH + filename);
        assertThat(classPathResource.exists()).isTrue();
        try (InputStream inputStream = classPathResource.getInputStream()) {
            return new MockMultipartFile(filename, filename, "", inputStream);
        }
    }


    // This can be used to test with local files that are not part of the project.
    @SuppressWarnings("unused")
    @SneakyThrows
    private MockMultipartFile loadDossierTemplateFromFile(String filename) {

        File file = new File(filename);
        assertThat(file.exists()).isTrue();

        return new MockMultipartFile(filename, filename, "", Files.readAllBytes(file.toPath()));
    }

}


