package ru.yandex.qatools.allure.command;

import io.airlift.airline.Arguments;
import io.airlift.airline.Command;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.DirectoryStream;
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.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;

/**
 * @author Artem Eroshenko <[email protected]>
 */
@Command(name = "generate", description = "Generate report")
public class ReportGenerate extends ReportCommand {

    private static final Logger LOGGER = LoggerFactory.getLogger(ReportGenerate.class);

    public static final String MAIN = "ru.yandex.qatools.allure.AllureMain";

    public static final String JAR_FILES = "*.jar";

    @Arguments(title = "Results directories", required = true,
            description = "A list of input directories to be processed")
    public List<String> results = new ArrayList<>();

    /**
     * {@inheritDoc}
     */
    @Override
    protected void runUnsafe() throws Exception {
        validateResultsDirectories();
        CommandLine commandLine = createCommandLine();
        new DefaultExecutor().execute(commandLine);
        LOGGER.info("Report successfully generated to the directory <{}>. " +
                "Use `allure report open` command to show the report.", getReportDirectoryPath());
    }

    /**
     * Throws an exception if at least one results directory is missing.
     */
    protected void validateResultsDirectories() {
        for (String result : results) {
            if (Files.notExists(Paths.get(result))) {
                throw new AllureCommandException(String.format("Report directory <%s> not found.", result));
            }
        }
    }

    /**
     * Create a {@link CommandLine} to run bundle with needed arguments.
     */
    private CommandLine createCommandLine() throws IOException {
        return new CommandLine(getJavaExecutablePath())
                .addArguments(getBundleJavaOptsArgument())
                .addArgument(getLoggerConfigurationArgument())
                .addArgument("-jar")
                .addArgument(getExecutableJar())
                .addArguments(results.toArray(new String[results.size()]), false)
                .addArgument(getReportDirectoryPath().toString(), false);
    }

    /**
     * Returns the classpath for executable jar.
     */
    protected String getClasspath() throws IOException {
        List<String> classpath = new ArrayList<>();
        classpath.add(getBundleJarPath());
        classpath.addAll(getPluginsPath());
        return StringUtils.toString(classpath.toArray(new String[classpath.size()]), " ");
    }

    /**
     * Create an executable jar to generate the report. Created jar contains only
     * allure configuration file.
     */
    protected String getExecutableJar() throws IOException {
        Manifest manifest = new Manifest();
        manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
        manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, MAIN);
        manifest.getMainAttributes().put(Attributes.Name.CLASS_PATH, getClasspath());

        Path jar = createTempDirectory("exec").resolve("generate.jar");
        try (JarOutputStream output = new JarOutputStream(Files.newOutputStream(jar), manifest)) {
            output.putNextEntry(new JarEntry("allure.properties"));
            Path allureConfig = PROPERTIES.getAllureConfig();
            if (Files.exists(allureConfig)) {
                byte[] bytes = Files.readAllBytes(allureConfig);
                output.write(bytes);
            }
            output.closeEntry();
        }

        return jar.toAbsolutePath().toString();
    }

    /**
     * Returns the bundle jar classpath element.
     */
    protected String getBundleJarPath() throws MalformedURLException {
        Path path = PROPERTIES.getAllureHome().resolve("app/allure-bundle.jar").toAbsolutePath();
        if (Files.notExists(path)) {
            throw new AllureCommandException(String.format("Bundle not found by path <%s>", path));
        }
        return path.toUri().toURL().toString();
    }

    /**
     * Returns the plugins classpath elements.
     */
    protected List<String> getPluginsPath() throws IOException {
        List<String> result = new ArrayList<>();
        Path pluginsDirectory = PROPERTIES.getAllureHome().resolve("plugins").toAbsolutePath();
        if (Files.notExists(pluginsDirectory)) {
            return Collections.emptyList();
        }

        try (DirectoryStream<Path> plugins = Files.newDirectoryStream(pluginsDirectory, JAR_FILES)) {
            for (Path plugin : plugins) {
                result.add(plugin.toUri().toURL().toString());
            }
        }
        return result;
    }

    /**
     * Get argument to configure log level for bundle.
     */
    protected String getLoggerConfigurationArgument() {
        return String.format("-Dorg.slf4j.simpleLogger.defaultLogLevel=%s",
                isQuiet() || !isVerbose() ? "error" : "debug");
    }

    /**
     * Returns the bundle java options split by space.
     */
    protected String getBundleJavaOptsArgument() {
        return PROPERTIES.getBundleJavaOpts();
    }

    /**
     * Returns the path to java executable.
     */
    protected String getJavaExecutablePath() {
        String executableName = isWindows() ? "bin/java.exe" : "bin/java";
        return PROPERTIES.getJavaHome().resolve(executableName).toAbsolutePath().toString();
    }

    /**
     * Returns true if operation system is windows, false otherwise.
     */
    protected boolean isWindows() {
        return PROPERTIES.getOsName().contains("win");
    }
}