package org.jboss.windup.addon.ui;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import javax.inject.Inject;
import javax.inject.Singleton;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.forge.addon.resource.DirectoryResource;
import org.jboss.forge.addon.ui.controller.CommandController;
import org.jboss.forge.addon.ui.output.UIMessage;
import org.jboss.forge.addon.ui.result.Failed;
import org.jboss.forge.addon.ui.result.Result;
import org.jboss.forge.addon.ui.test.UITestHarness;
import org.jboss.forge.arquillian.AddonDependencies;
import org.jboss.forge.arquillian.AddonDependency;
import org.jboss.forge.arquillian.archive.AddonArchive;
import org.jboss.forge.furnace.util.OperatingSystemUtils;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.windup.config.AbstractRuleProvider;
import org.jboss.windup.config.loader.RuleLoaderContext;
import org.jboss.windup.config.metadata.MetadataBuilder;
import org.jboss.windup.config.metadata.TechnologyReference;
import org.jboss.windup.exec.configuration.WindupConfiguration;
import org.jboss.windup.exec.configuration.options.InputPathOption;
import org.jboss.windup.exec.configuration.options.OutputPathOption;
import org.jboss.windup.exec.configuration.options.OverwriteOption;
import org.jboss.windup.exec.configuration.options.TargetOption;
import org.jboss.windup.exec.configuration.options.UserIgnorePathOption;
import org.jboss.windup.exec.configuration.options.UserRulesDirectoryOption;
import org.jboss.windup.graph.GraphContext;
import org.jboss.windup.graph.model.resource.FileModel;
import org.jboss.windup.graph.model.resource.IgnoredFileModel;
import org.jboss.windup.graph.service.GraphService;
import org.jboss.windup.ui.WindupCommand;
import org.jboss.windup.util.PathUtil;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ocpsoft.rewrite.config.Configuration;
import org.ocpsoft.rewrite.config.ConfigurationBuilder;

@RunWith(Arquillian.class)
public class WindupCommandTest
{
    @Deployment
    @AddonDependencies({
                @AddonDependency(name = "org.jboss.windup.ui:windup-ui"),
                @AddonDependency(name = "org.jboss.windup.exec:windup-exec"),
                @AddonDependency(name = "org.jboss.windup.graph:windup-graph"),
                @AddonDependency(name = "org.jboss.windup.rules.apps:windup-rules-java"),
                @AddonDependency(name = "org.jboss.windup.utils:windup-utils"),
                @AddonDependency(name = "org.jboss.forge.addon:ui-test-harness"),
                @AddonDependency(name = "org.jboss.forge.furnace.container:cdi"),
    })
    public static AddonArchive getDeployment()
    {
        return ShrinkWrap
                    .create(AddonArchive.class)
                    .addBeansXML()
                    .addAsResource(WindupCommandTest.class.getResource("/test.jar"), "/test.jar")
                    .addAsResource(WindupCommandTest.class.getResource("/ignore/test-windup-ignore.txt"), TEST_IGNORE_FILE);
    }

    private static String TEST_IGNORE_FILE = "/test.txt";

    @Inject
    private UITestHarness uiTestHarness;

    // This is just there to make sure that we have at least one "target" technology available
    @Inject
    private SampleProviderForTarget provider;

    @Before
    public void beforeTest()
    {
        if (System.getProperty("forge.home") == null)
        {
            String defaultForgeHomePath = Paths.get(OperatingSystemUtils.getTempDirectory().getAbsolutePath())
                        .resolve("Windup")
                        .resolve("fakeforgehome_" + RandomStringUtils.randomAlphanumeric(6)).toString();
            System.setProperty("forge.home", defaultForgeHomePath);
        }
    }

    @Test
    public void testOutputDirCannotBeParentOfInputDir() throws Exception
    {
        try (CommandController controller = uiTestHarness.createCommandController(WindupCommand.class))
        {
            File tempDir = OperatingSystemUtils.createTempDir();
            File inputFile = File.createTempFile("windupwizardtest", ".jar", tempDir);
            inputFile.deleteOnExit();
            try (InputStream iStream = getClass().getResourceAsStream("/test.jar"))
            {
                try (OutputStream oStream = new FileOutputStream(inputFile))
                {
                    IOUtils.copy(iStream, oStream);
                }
            }

            try
            {
                controller.initialize();
                Assert.assertTrue(controller.isEnabled());
                controller.setValueFor(TargetOption.NAME, Collections.singletonList("eap"));
                controller.setValueFor(InputPathOption.NAME, Collections.singletonList(inputFile));
                Assert.assertTrue(controller.canExecute());
                controller.setValueFor(OutputPathOption.NAME, tempDir);
                Assert.assertFalse(controller.canExecute());
                List<UIMessage> messages = controller.validate();
                boolean validationFound = false;
                for (UIMessage message : messages)
                {
                    if (message.getDescription().equals("Output path must not be a parent of input path."))
                    {
                        validationFound = true;
                        break;
                    }
                }
                Assert.assertTrue(validationFound);
                controller.setValueFor(OutputPathOption.NAME, null);
                Assert.assertTrue(controller.canExecute());
                controller.setValueFor(OverwriteOption.NAME, true);
            }
            finally
            {
                FileUtils.deleteDirectory(tempDir);
            }
        }
    }

    @Test
    public void testOverwriteConfirmation() throws Exception
    {
        String overwritePromptMessage = "Overwrite all contents of .*\\?";

        // Sets the overwrite response flag to false
        uiTestHarness.getPromptResults().put(overwritePromptMessage, "false");

        try (CommandController controller = uiTestHarness.createCommandController(WindupCommand.class))
        {
            File inputFile = File.createTempFile("windupwizardtest", "jar");
            inputFile.deleteOnExit();
            try (InputStream iStream = getClass().getResourceAsStream("/test.jar"))
            {
                try (OutputStream oStream = new FileOutputStream(inputFile))
                {
                    IOUtils.copy(iStream, oStream);
                }
            }

            File reportPath = new File(inputFile.getAbsoluteFile() + "_output");
            try
            {
                reportPath.mkdirs();
                File newFileInOutputPath = new File(reportPath, "forceoverwriteprompt");
                // make sure that at least one file is in the output
                newFileInOutputPath.createNewFile();

                setupController(controller, inputFile, reportPath);

                Result result = controller.execute();

                // make sure that it failed to run (since the user's response to the overwrite question is false)
                Assert.assertTrue(result instanceof Failed);
                Assert.assertTrue(result.getMessage().contains("overwrite not specified"));
            }
            finally
            {
                inputFile.delete();
                FileUtils.deleteDirectory(reportPath);
            }
        }
    }

    @Test
    public void testNewMigration() throws Exception
    {
        Assert.assertNotNull(uiTestHarness);
        try (CommandController controller = uiTestHarness.createCommandController(WindupCommand.class))
        {
            File inputFile = File.createTempFile("windupwizardtest", ".jar");
            inputFile.deleteOnExit();
            try (InputStream iStream = getClass().getResourceAsStream("/test.jar"))
            {
                try (OutputStream oStream = new FileOutputStream(inputFile))
                {
                    IOUtils.copy(iStream, oStream);
                }
            }

            File reportPath = new File(inputFile.getAbsoluteFile() + "_output");
            try
            {
                reportPath.mkdirs();

                setupController(controller, inputFile, reportPath);

                Result result = controller.execute();
                final String msg = "controller.execute() 'Failed': " + result.getMessage();
                Assert.assertFalse(msg, result instanceof Failed);
            }
            finally
            {
                inputFile.delete();
                FileUtils.deleteDirectory(reportPath);
            }
        }
    }

    @Test
    public void testOutputDefaultValue() throws Exception
    {
        Assert.assertNotNull(uiTestHarness);
        try (CommandController controller = uiTestHarness.createCommandController(WindupCommand.class))
        {
            File inputFile = File.createTempFile("windupwizardtest", ".jar");
            inputFile.deleteOnExit();
            try (InputStream iStream = getClass().getResourceAsStream("/test.jar"))
            {
                try (OutputStream oStream = new FileOutputStream(inputFile))
                {
                    IOUtils.copy(iStream, oStream);
                }
            }

            try
            {

                setupController(controller, inputFile, null);

                Result result = controller.execute();
                Object outputDir = controller.getValueFor("output");
                Assert.assertTrue("The output should be a folder",
                            DirectoryResource.class.isAssignableFrom(outputDir.getClass()));
                Assert.assertTrue("The output should be created inside the .report folder by default",
                            ((DirectoryResource) outputDir).getName().endsWith(".report"));
                ArrayList<File> inputDirs = (ArrayList<File>) controller.getValueFor("input");
                Assert.assertEquals(1, inputDirs.size());

                File inputDirParent = inputDirs.get(0).getParentFile();
                File child = new File(inputDirParent, ((DirectoryResource) outputDir).getName());
                Assert.assertTrue("The output should be created near the ${input} folder by default", child.exists());
                Assert.assertTrue("The output should be created near the ${input} folder by default", child.isDirectory());
                final String msg = "controller.execute() 'Failed': " + result.getMessage();
                Assert.assertFalse(msg, result instanceof Failed);
            }
            finally
            {
                inputFile.delete();
            }
        }
    }

    @Test
    public void testUserRulesDirMigration() throws Exception
    {
        Assert.assertNotNull(uiTestHarness);
        try (CommandController controller = uiTestHarness.createCommandController(WindupCommand.class))
        {
            File outputFile = File.createTempFile("windupwizardtest", ".jar");
            outputFile.deleteOnExit();
            try (InputStream iStream = getClass().getResourceAsStream("/test.jar"))
            {
                try (OutputStream oStream = new FileOutputStream(outputFile))
                {
                    IOUtils.copy(iStream, oStream);
                }
            }

            File reportPath = new File(outputFile.getAbsoluteFile() + "_output");
            try
            {
                reportPath.mkdirs();

                setupController(controller, outputFile, reportPath);

                File userRulesDir = FileUtils.getTempDirectory().toPath().resolve("Windup")
                            .resolve("windupcommanduserrules_" + RandomStringUtils.randomAlphanumeric(6)).toFile();
                userRulesDir.mkdirs();
                controller.setValueFor("userRulesDirectory", userRulesDir);

                Result result = controller.execute();
                final String msg = "controller.execute() 'Failed': " + result.getMessage();
                Assert.assertFalse(msg, result instanceof Failed);

                WindupConfiguration windupConfiguration = (WindupConfiguration) controller.getContext()
                            .getAttributeMap()
                            .get(WindupConfiguration.class);
                File resultUserSpecifiedRulesDir = windupConfiguration.getOptionValue(UserRulesDirectoryOption.NAME);
                Assert.assertEquals(userRulesDir, resultUserSpecifiedRulesDir);

                Iterable<Path> allRulesPaths = windupConfiguration.getAllUserRulesDirectories();

                Path expectedUserHomeRulesDir = PathUtil.getUserRulesDir();
                Path expectedWindupHomeRulesDir = PathUtil.getWindupRulesDir();

                boolean foundUserSpecifiedPath = false;
                boolean foundUserHomeDirRulesPath = false;
                boolean foundWindupHomeDirRulesPath = false;
                int totalFound = 0;
                for (Path rulesPath : allRulesPaths)
                {
                    totalFound++;
                    if (rulesPath.equals(userRulesDir.toPath()))
                    {
                        foundUserSpecifiedPath = true;
                    }
                    if (rulesPath.equals(expectedUserHomeRulesDir))
                    {
                        foundUserHomeDirRulesPath = true;
                    }
                    if (rulesPath.equals(expectedWindupHomeRulesDir))
                    {
                        foundWindupHomeDirRulesPath = true;
                    }
                }
                Assert.assertTrue(foundUserSpecifiedPath);
                Assert.assertTrue(foundUserHomeDirRulesPath);
                Assert.assertTrue(foundWindupHomeDirRulesPath);
                Assert.assertEquals(3, totalFound);
            }
            finally
            {
                outputFile.delete();
                FileUtils.deleteDirectory(reportPath);
            }
        }
    }

    @Test
    public void testDuplicateUserRulesDirMigration() throws Exception
    {
        Assert.assertNotNull(uiTestHarness);
        try (CommandController controller = uiTestHarness.createCommandController(WindupCommand.class))
        {
            File outputFile = File.createTempFile("windupwizardtest", ".jar");
            outputFile.deleteOnExit();
            try (InputStream iStream = getClass().getResourceAsStream("/test.jar"))
            {
                try (OutputStream oStream = new FileOutputStream(outputFile))
                {
                    IOUtils.copy(iStream, oStream);
                }
            }

            File reportPath = new File(outputFile.getAbsoluteFile() + "_output");
            try
            {
                reportPath.mkdirs();

                setupController(controller, outputFile, reportPath);

                Path expectedUserHomeRulesDir = PathUtil.getUserRulesDir();
                expectedUserHomeRulesDir.toFile().mkdirs();
                controller.setValueFor("userRulesDirectory", expectedUserHomeRulesDir.toFile());

                Result result = controller.execute();
                final String msg = "controller.execute() 'Failed': " + result.getMessage();
                Assert.assertFalse(msg, result instanceof Failed);

                WindupConfiguration windupConfiguration = (WindupConfiguration) controller.getContext()
                            .getAttributeMap()
                            .get(WindupConfiguration.class);
                File resultUserSpecifiedRulesDir = windupConfiguration.getOptionValue(UserRulesDirectoryOption.NAME);
                Assert.assertEquals(expectedUserHomeRulesDir.toFile(), resultUserSpecifiedRulesDir);

                Iterable<Path> allRulesPaths = windupConfiguration.getAllUserRulesDirectories();

                Path expectedWindupHomeRulesDir = PathUtil.getWindupRulesDir();

                boolean foundUserHomeDirRulesPath = false;
                boolean foundWindupHomeDirRulesPath = false;
                int totalFound = 0;
                for (Path rulesPath : allRulesPaths)
                {
                    totalFound++;
                    if (rulesPath.equals(expectedUserHomeRulesDir))
                    {
                        foundUserHomeDirRulesPath = true;
                    }
                    if (rulesPath.equals(expectedWindupHomeRulesDir))
                    {
                        foundWindupHomeDirRulesPath = true;
                    }
                }
                Assert.assertTrue(foundUserHomeDirRulesPath);
                Assert.assertTrue(foundWindupHomeDirRulesPath);
                Assert.assertEquals(2, totalFound);
            }
            finally
            {
                outputFile.delete();
                FileUtils.deleteDirectory(reportPath);
            }
        }
    }

    @Test
    public void testUserIgnoreDirMigration() throws Exception
    {
        Assert.assertNotNull(uiTestHarness);
        try (CommandController controller = uiTestHarness.createCommandController(WindupCommand.class))
        {
            File outputFile = File.createTempFile("windupwizardtest", ".jar");
            outputFile.deleteOnExit();
            File inputIgnoreFile = File.createTempFile("generated-windup-ignore", ".txt");
            inputIgnoreFile.deleteOnExit();
            try (InputStream iStream = getClass().getResourceAsStream("/test.jar"))
            {
                try (OutputStream oStream = new FileOutputStream(outputFile))
                {
                    IOUtils.copy(iStream, oStream);
                }
            }
            try (InputStream iStream = getClass().getResourceAsStream(TEST_IGNORE_FILE))
            {
                try (OutputStream oStream = new FileOutputStream(inputIgnoreFile))
                {
                    IOUtils.copy(iStream, oStream);
                }
            }

            File reportPath = new File(outputFile.getAbsoluteFile() + "_output");
            try
            {
                reportPath.mkdirs();

                setupController(controller, outputFile, reportPath);

                controller.setValueFor(UserIgnorePathOption.NAME, inputIgnoreFile);

                Result result = controller.execute();
                final String msg = "controller.execute() 'Failed': " + result.getMessage();
                Assert.assertFalse(msg, result instanceof Failed);

                WindupConfiguration windupConfiguration = (WindupConfiguration) controller.getContext()
                            .getAttributeMap()
                            .get(WindupConfiguration.class);
                File resultIgnoreFile = windupConfiguration.getOptionValue(UserIgnorePathOption.NAME);
                Assert.assertEquals(inputIgnoreFile, resultIgnoreFile);

                Iterable<Path> allIgnoreDirectories = windupConfiguration.getAllIgnoreDirectories();

                Path expectedUserHomeIgnoreDir = PathUtil.getUserIgnoreDir();
                Path expectedWindupHomeIgnoreDir = PathUtil.getWindupIgnoreDir();

                boolean foundUserSpecifiedPath = false;
                boolean foundUserHomeDirIgnorePath = false;
                boolean foundWindupHomeDirIgnorePath = false;
                int totalFound = 0;
                for (Path rulesPath : allIgnoreDirectories)
                {
                    totalFound++;
                    if (rulesPath.equals(resultIgnoreFile.toPath()))
                    {
                        foundUserSpecifiedPath = true;
                    }
                    if (rulesPath.equals(expectedUserHomeIgnoreDir))
                    {
                        foundUserHomeDirIgnorePath = true;
                    }
                    if (rulesPath.equals(expectedWindupHomeIgnoreDir))
                    {
                        foundWindupHomeDirIgnorePath = true;
                    }
                }
                Assert.assertTrue(foundUserSpecifiedPath);
                Assert.assertTrue(foundUserHomeDirIgnorePath);
                Assert.assertTrue(foundWindupHomeDirIgnorePath);
                Assert.assertEquals(3, totalFound);
                GraphContext context = (GraphContext) controller.getContext().getAttributeMap().get(GraphContext.class);
                GraphService<FileModel> service = new GraphService<>(context.load(), FileModel.class);
                Iterable<FileModel> findAll = service.findAll();
                boolean notEmpty = false;
                for (FileModel fileModel : findAll)
                {
                    notEmpty = true;
                    if (!(fileModel instanceof IgnoredFileModel) && (fileModel.getFileName().contains("META-INF")))
                    {
                        Assert.fail("The file " + fileModel.getFileName() + " should be ignored");
                    }
                }
                Assert.assertTrue("There should be some file models present in the graph", notEmpty);
            }
            finally
            {
                outputFile.delete();
                FileUtils.deleteDirectory(reportPath);
            }
        }
    }

    private void setupController(CommandController controller, File inputFile, File outputFile) throws Exception
    {
        controller.initialize();
        Assert.assertTrue(controller.isEnabled());
        controller.setValueFor(InputPathOption.NAME, Collections.singletonList(inputFile)); // FORGE-2524
        final Object value = controller.getValueFor(InputPathOption.NAME);
        Assume.assumeTrue(value instanceof Collection);
        Assume.assumeTrue(((Collection) value).iterator().hasNext());
        Assume.assumeTrue(((Collection) value).iterator().next() instanceof File);
        Assume.assumeTrue(((Collection) value).iterator().next().equals(inputFile));

        if (outputFile != null)
        {
            controller.setValueFor(OutputPathOption.NAME, outputFile);
        }
        controller.setValueFor(TargetOption.NAME, Collections.singletonList("eap"));

        Assert.assertTrue(controller.canExecute());
        controller.setValueFor("packages", "org.jboss");
        Assert.assertTrue(controller.canExecute());
    }

    /**
     * This class exists purely to guarantee that we have at least one selection available for "target".
     */
    @Singleton
    public static class SampleProviderForTarget extends AbstractRuleProvider
    {
        public SampleProviderForTarget()
        {
            super(MetadataBuilder.forProvider(SampleProviderForTarget.class)
                        .addTag("tag2")
                        .addSourceTechnology(new TechnologyReference("foo", "[1.0,)"))
                        .addTargetTechnology(new TechnologyReference("eap", "[1.0,)")));
        }

        @Override
        public Configuration getConfiguration(RuleLoaderContext ruleLoaderContext)
        {
            return ConfigurationBuilder.begin();
        }
    }
}
