package de.renew.plugin.locate;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import de.renew.plugin.PluginProperties;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * Test class that tests the methods of the PluginSubDirFinder class.
 *
 * @author Philipp Schult
 */
@Disabled("These tests sometimes fail the pipeline.")
public class PluginSubDirFinderTest {
    private static String _currentDir;
    private static URL _currentDirURL;
    private static File _currentFile;
    private static File[] _createdSubDirs;
    private static File _cfg;

    @BeforeAll
    public static void setUpAll() throws IOException {
        String currentBaseDir = System.getProperty("user.dir");
        _currentDir = currentBaseDir + "/Dummyplugin";
        _currentFile = new File(_currentDir);
        _currentDirURL = _currentFile.toURI().toURL();
        //create some files and directories
        if (_currentFile.exists()) {
            //if the directory already exists (e.g. if tests were interrupted before deleting temporary files),
            //delete them before creating the new files
            deleteDirectory(_currentFile);
        }
        Files.createDirectories(Paths.get(_currentDir));
        Files.createFile(Paths.get(_currentDir + "/test.txt"));

        _createdSubDirs = new File[2];
        _createdSubDirs[0] = Files.createDirectory(Paths.get(_currentDir + "/subdir/")).toFile();
        _createdSubDirs[1] =
            Files.createDirectory(Paths.get(_currentDir + "/anothersubdir/")).toFile();
        Files.createDirectory(Paths.get(_currentDir + "/subdir/subsubdir/"));

        _cfg = Files.createFile(Paths.get(_currentDir + "/plugin.cfg")).toFile();
        String content = "name = Dummy Plugin\nsomeProperty = set";
        writeToConfig(_cfg, content);

        File subdirCfg = Files.createFile(Paths.get(_currentDir + "/subdir/plugin.cfg")).toFile();
        String subdirContent = "name = Dummy Subdir Plugin\nsomeProperty = set in subdir";
        writeToConfig(subdirCfg, subdirContent);

        File anotherSubDirCfg =
            Files.createFile(Paths.get(_currentDir + "/anothersubdir/plugin.cfg")).toFile();
        String anotherSubDirContent =
            "name = Dummy Anothersubdir Plugin\nanotherProperty = set in anothersubdir";
        writeToConfig(anotherSubDirCfg, anotherSubDirContent);
    }

    @AfterAll
    public static void tearDownAll() {
        //delete the files that were created after testing has concluded
        deleteDirectory(_currentFile);
    }

    @Test
    public void testGetPluginFiles() {
        //given
        PluginSubDirFinder testee = new PluginSubDirFinder(_currentDirURL);
        List<File> expectedResult = Arrays.asList(_createdSubDirs);
        //when
        List<File> actualResult = Arrays.asList(testee.getPluginFiles(_currentFile));
        //then
        //contrary to the name of the method,
        //this method only returns directories inside the parent directory, not files.
        //this behaviour is explained in the documentation of the method, so it seems to be intended.
        assertEquals(expectedResult.size(), actualResult.size());
        //using this instead of assertArrayEquals because the order of the items does not matter
        assertTrue(
            actualResult.containsAll(expectedResult) && expectedResult.containsAll(actualResult));
    }

    @Test
    public void testGetPluginConfigurations() {
        //given
        PluginSubDirFinder testee = new PluginSubDirFinder(_currentDirURL);
        //when
        Collection<PluginProperties> result = testee.getPluginConfigurations(_createdSubDirs);
        //then
        verifyCorrectnessOfPluginProperties(result);
    }

    @Test
    public void testGetPluginConfigFromDirectoryFileIsNotDirectory() {
        //given
        PluginSubDirFinder testee = new PluginSubDirFinder(_currentDirURL);
        File file = new File(_currentDir + "/test.txt");
        //when
        PluginProperties result = testee.getPluginConfigFromDirectory(file);
        //then
        assertNull(result);
    }

    @Test
    public void testGetPluginConfigFromDirectoryDirectoryContainsNoConfig() {
        PluginSubDirFinder testee = new PluginSubDirFinder(_currentDirURL);
        File file = new File(_currentDir + "/subdir/subsubdir");
        //when
        PluginProperties result = testee.getPluginConfigFromDirectory(file);
        //then
        assertNull(result);
    }

    @Test
    public void testGetPluginConfigFromDirectory() {
        PluginSubDirFinder testee = new PluginSubDirFinder(_currentDirURL);
        File file = new File(_currentDir);
        //when
        PluginProperties result = testee.getPluginConfigFromDirectory(file);
        //then
        assertEquals("Dummy Plugin", result.getName());
        assertTrue(result.isKnownProperty("someProperty"));
        assertEquals("set", result.getProperty("someProperty"));
    }

    @Test
    public void testCreatePluginConfigGivenNonexistentFile() {
        //given
        PluginSubDirFinder testee = new PluginSubDirFinder(_currentDirURL);
        File file = new File(_currentDir + "thisfiledoesntexist");
        //when
        PluginProperties result = testee.createPluginConfig(file);
        //then
        assertNull(result);
    }

    @Test
    public void testCreatePluginConfig() {
        //given
        PluginSubDirFinder testee = new PluginSubDirFinder(_currentDirURL);
        //when
        PluginProperties result = testee.createPluginConfig(_cfg);
        //then
        assertEquals("Dummy Plugin", result.getName());
        assertTrue(result.isKnownProperty("someProperty"));
        assertEquals("set", result.getProperty("someProperty"));
    }

    @Test
    public void testFindPluginLocations() {
        //given
        PluginSubDirFinder testee = new PluginSubDirFinder(_currentDirURL);
        //when
        Collection<PluginProperties> result = testee.findPluginLocations();
        //then
        verifyCorrectnessOfPluginProperties(result);
    }

    //helper methods

    /**
     * deletes a directory recursively
     * @param dir directory to be deleted
     */
    private static void deleteDirectory(File dir) {
        File[] dirContent = dir.listFiles();
        if (dirContent != null) {
            for (File file : dirContent) {
                deleteDirectory(file);
            }
        }
        dir.delete();
    }

    /**
     * sets some properties in the config file for the purposes of testing if getPluginConfigFromDirectory
     * reads the config file properly and returns a proper PluginProperties object.
     * @param file the config file
     */
    private static void writeToConfig(File file, String content) {
        try {
            FileWriter fw = new FileWriter(file.getAbsoluteFile());
            BufferedWriter bw = new BufferedWriter(fw);
            bw.write(content);
            bw.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * tests the correctness of the PluginProperties objects regardless of the order they come in
     * @param properties the properties collection to be tested
     */
    private static void verifyCorrectnessOfPluginProperties(
        Collection<PluginProperties> properties)
    {
        assertEquals(_createdSubDirs.length, properties.size());
        Iterator<PluginProperties> iterator = properties.iterator();
        while (iterator.hasNext()) {
            PluginProperties pluginProperties = iterator.next();
            if (pluginProperties.getName().equals("Dummy Subdir Plugin")) {
                verifySubdirPluginAssertions(pluginProperties);
            } else {
                verifyAnothersubdirPluginAssertions(pluginProperties);
            }
        }
    }

    /**
     * tests the correctness of the PluginProperties object for Subdir
     * @param pluginProperties the PluginProperties for Subdir
     */
    private static void verifySubdirPluginAssertions(PluginProperties pluginProperties) {
        assertEquals("Dummy Subdir Plugin", pluginProperties.getName());
        assertTrue(pluginProperties.isKnownProperty("someProperty"));
        assertFalse(pluginProperties.isKnownProperty("anotherProperty"));
        assertEquals("set in subdir", pluginProperties.getProperty("someProperty"));
    }

    /**
     * tests the correctness of the PluginProperties object for Anothersubdir
     * @param pluginProperties the PluginProperties for Anothersubdir
     */
    private static void verifyAnothersubdirPluginAssertions(PluginProperties pluginProperties) {
        assertEquals("Dummy Anothersubdir Plugin", pluginProperties.getName());
        assertTrue(pluginProperties.isKnownProperty("anotherProperty"));
        assertFalse(pluginProperties.isKnownProperty("someProperty"));
        assertEquals("set in anothersubdir", pluginProperties.getProperty("anotherProperty"));
    }
}
