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

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.FileSystemUtils;

import com.iqser.red.service.peristence.v1.server.integration.utils.AbstractPersistenceServerServiceTest;
import com.iqser.red.service.persistence.management.v1.processor.acl.custom.dossier.DossierACLService;
import com.iqser.red.service.persistence.management.v1.processor.entity.download.DownloadStatusEntity;
import com.iqser.red.service.persistence.management.v1.processor.dataexchange.ExportDownloadMessage;
import com.iqser.red.service.persistence.management.v1.processor.dataexchange.service.DossierTemplateImportService;
import com.iqser.red.service.persistence.management.v1.processor.dataexchange.service.DossierTemplateExportService;
import com.iqser.red.service.persistence.management.v1.processor.dataexchange.service.FileExchangeExportService;
import com.iqser.red.service.persistence.management.v1.processor.dataexchange.service.FileExchangeImportService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DownloadStatusPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileExchangeExportRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.importexport.ImportDossierTemplateRequest;
import com.iqser.red.storage.commons.service.StorageService;
import com.iqser.red.storage.commons.utils.FileSystemBackedStorageService;
import com.knecon.fforesight.tenantcommons.TenantContext;

import lombok.SneakyThrows;

public class FileExchangeImportExportTest extends AbstractPersistenceServerServiceTest {

    public static final String IMPORTED_TEMPLATE_NAME = "imported-template";
    public static final String FILE_EXCHANGE_FOLDER_NAME = "file-exchange";
    public static final String USER_ID = "NutzerIdentifikationsNummer";
    public static final String AFTER = "after";
    public static final String BEFORE = "before";

    @Autowired
    DossierTemplateImportService dossierTemplateImportService;

    @Autowired
    DossierTemplateExportService dossierTemplateExportService;

    @Autowired
    FileExchangeImportService fileExchangeImportService;

    @Autowired
    FileExchangeExportService fileExchangeExportService;

    @Autowired
    StorageService storageService;

    @MockBean
    DownloadStatusPersistenceService downloadStatusPersistenceService;

    @MockBean
    DossierACLService dossierACLService;


    @AfterEach
    public void clearStorage() {

        ((FileSystemBackedStorageService) storageService).clearStorage();
    }


    @Test
    @SneakyThrows
    public void testFileExchangeRoundtrip() {

        TenantContext.setTenantId(TENANT_1);

        Path fileExchangeArchive = new ClassPathResource("files/fileexchange/EFSA sanitisation GFL v1_file-exchange.zip").getFile().toPath();

        String importedDossierTemplateId = fileExchangeImportService.importFileExchangeArchive(USER_ID, Files.readAllBytes(fileExchangeArchive)).getDossierTemplateId();

        fileExchangeExportService.createDownloadArchive(ExportDownloadMessage.builder()
                                                                .dossierTemplateId(importedDossierTemplateId)
                                                                .storageId(IMPORTED_TEMPLATE_NAME)
                                                                .fileExchangeExportRequest(buildDefaultRequest())
                                                                .userId(USER_ID)
                                                                .build());

        File tmpFile = Files.createTempFile(IMPORTED_TEMPLATE_NAME, ".zip").toFile();
        storageService.downloadTo(TenantContext.getTenantId(), IMPORTED_TEMPLATE_NAME, tmpFile);

        Path outDir = Files.createTempDirectory(FILE_EXCHANGE_FOLDER_NAME);

        unzip(fileExchangeArchive.toFile().toString(), outDir.resolve(BEFORE));
        unzip(tmpFile.toString(), outDir.resolve(AFTER));

        assert tmpFile.delete();

        Map<Path, byte[]> beforeContents = getDirectoryContents(outDir.resolve(BEFORE));
        Map<Path, byte[]> afterContents = getDirectoryContents(outDir.resolve(AFTER));

        assertNotEquals(beforeContents.size(), afterContents.size());

        // can't assert equality on contents or paths as UUID's are supposed to change on import

        FileSystemUtils.deleteRecursively(outDir);
    }


    private FileExchangeExportRequest buildDefaultRequest() {

        return new FileExchangeExportRequest(Collections.emptySet(), Collections.emptySet(), false, false, false, false, true);

    }


    @Test
    @SneakyThrows
    public void testDossierTemplateRoundtripRM() {

        Path dossierTemplate = new ClassPathResource("files/dossiertemplates/DossierTemplate.zip").getFile().toPath();

        testImportExport(dossierTemplate);
    }

    @Test
    @SneakyThrows
    public void testDossierTemplateRoundtripDM() {

        Path floraTemplate = new ClassPathResource("files/dossiertemplates/Flora.zip").getFile().toPath();

        testImportExport(floraTemplate);
    }

    @SneakyThrows
    public void testImportExport(Path dossierTemplateExportArchive) {

        TenantContext.setTenantId(TENANT_1);
        Path outDir = Files.createTempDirectory(IMPORTED_TEMPLATE_NAME);

        String importedDossierTemplateId = dossierTemplateImportService.importDossierTemplate(ImportDossierTemplateRequest.builder()
                                                                                                      .archive(Files.readAllBytes(dossierTemplateExportArchive))
                                                                                                      .userId(USER_ID)
                                                                                                      .build());

        when(downloadStatusPersistenceService.getStatus(anyString()))//
                .thenReturn(DownloadStatusEntity.builder().filename(importedDossierTemplateId + ".zip").storageId(IMPORTED_TEMPLATE_NAME).build());

        dossierTemplateExportService.createDownloadArchive(ExportDownloadMessage.builder()
                                                                   .dossierTemplateId(importedDossierTemplateId)
                                                                   .storageId(importedDossierTemplateId)
                                                                   .userId(USER_ID)
                                                                   .build());

        File tmpFile = Files.createTempFile(IMPORTED_TEMPLATE_NAME, ".zip").toFile();
        storageService.downloadTo(TenantContext.getTenantId(), IMPORTED_TEMPLATE_NAME, tmpFile);

        unzip(dossierTemplateExportArchive.toFile().toString(), outDir.resolve(BEFORE));
        unzip(tmpFile.toString(), outDir.resolve(AFTER));

        assert tmpFile.delete();

        Map<Path, byte[]> beforeContents = getDirectoryContents(outDir.resolve(BEFORE));
        Map<Path, byte[]> afterContents = getDirectoryContents(outDir.resolve(AFTER));

        assertNotEquals(beforeContents.size(), afterContents.size());
        assertNotEquals(beforeContents.keySet(), afterContents.keySet());

        for (Path path : beforeContents.keySet()) {
            assertEquals(beforeContents.get(path).length, afterContents.get(path).length);
        }
        // can't assert equality on contents as UUID's are supposed to change on import

        FileSystemUtils.deleteRecursively(outDir);
    }


    public static void unzip(String zipFilePath, Path destDirectory) throws IOException {

        File destDir = destDirectory.toFile();
        if (!destDir.exists()) {
            destDir.mkdir();
        }
        try (ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath))) {
            ZipEntry entry = zipIn.getNextEntry();
            while (entry != null) {
                String filePath = destDirectory + File.separator + entry.getName();
                if (!entry.isDirectory()) {
                    // if the entry is a file, extracts it
                    extractFile(zipIn, filePath);
                } else {
                    // if the entry is a directory, make the directory
                    File dir = new File(filePath);
                    dir.mkdirs();
                }
                zipIn.closeEntry();
                entry = zipIn.getNextEntry();
            }
        }
    }


    private static void extractFile(ZipInputStream zipIn, String filePath) throws IOException {

        try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath))) {
            byte[] bytesIn = new byte[4096];
            int read;
            while ((read = zipIn.read(bytesIn)) != -1) {
                bos.write(bytesIn, 0, read);
            }
        }
    }


    private static Map<Path, byte[]> getDirectoryContents(Path dir) throws IOException, NoSuchAlgorithmException {

        Map<Path, byte[]> contents = new HashMap<>();
        MessageDigest digest = MessageDigest.getInstance("SHA-256");

        Files.walkFileTree(dir, new SimpleFileVisitor<>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {

                contents.put(dir.relativize(file), digest.digest(Files.readAllBytes(file)));
                return FileVisitResult.CONTINUE;
            }


            @Override
            public FileVisitResult preVisitDirectory(Path subDir, BasicFileAttributes attrs) {

                contents.put(dir.relativize(subDir), new byte[0]);
                return FileVisitResult.CONTINUE;
            }
        });

        return contents;
    }

}
