package be.iminds.ilabt.jfed.ui.cli;

import be.iminds.ilabt.jfed.lowlevel.GeniUser;
import be.iminds.ilabt.jfed.lowlevel.ApiInfo;
import be.iminds.ilabt.jfed.lowlevel.api.test.TestClassList;

import be.iminds.ilabt.jfed.fedmon.webapi.service.json.*;
import be.iminds.ilabt.jfed.testing.base.*;
import be.iminds.ilabt.jfed.ui.commandline.BaseCli;
import be.iminds.ilabt.jfed.util.TargetAuthority;
import com.google.inject.Injector;
import org.apache.commons.cli.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.lang.Exception;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.Properties;

/**
 * AutomatedTesterCli
 */
public class MultiStitchingTestsCli {
    private static final Logger LOG = LoggerFactory.getLogger(MultiStitchingTestsCli.class);
    //this is a singleton, because there is a strange bug when the cli is called multiple times in parallel, and Options is created each time.
    private static Options optionsSingleton;

    public static void help(Options options) {
        String footer_extra = "";
        try {
            for (String testclass : TestClassList.getInstance().allTestClasses()) {
                footer_extra += ("   - " + testclass + "\n");
                Collection<String> groups = TestClassList.getInstance().getGroups(testclass);
                if (!groups.isEmpty())
                    footer_extra += ("      groups:\n");
                for (String group : groups)
                    footer_extra += ("       - " + group + "\n");
            }
        } catch (AssertionError t) {
            LOG.error("Something went wrong trying to list all test classes and their groups: " + t.getMessage(), t);
        } catch (Exception t) {
            LOG.error("Something went wrong trying to list all test classes and their groups: " + t.getMessage(), t);
        }

        HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp(200, "jfed-automated-testing-cli [options ... ]", "Options:", options,
                "\n\nExample Test Context Properties File content:\n  username = <username>\n" +
                        "  passwordFilename = <filename of file containing password>\n" +
                        "  pemKeyAndCertFilename = <filename of file containing user certificate and private key in PEM format>\n" +
                        "  userAuthorityUrn = <URN of test user's authority>\n" +
                        "  testedAggregateManagerUrn = <URN of tested Aggregate Manager>\n\n" +
                        "Known test classes:\n" + footer_extra
        );

        //TODO: support a number of other syntaxes:
        //TODO    - 1 file (including context and 1 or more tests)
        //TODO    - 1 file with context + 1 file with one or more tests
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        int retval = main_helper(args, System.out, System.err, System.in);
        System.exit(retval);
    }

    public static synchronized Options getOptions() {
        if (optionsSingleton == null) {
            // create the Options
            optionsSingleton = new Options();

            BaseCli.addUserConfig(optionsSingleton, "The properties file containing context details (login, tested server, etc.) (MANDATORY)", false);
            optionsSingleton.getOption("context-file").setRequired(true);

            BaseCli.addAuthoritiesConfig(optionsSingleton);

            optionsSingleton.addOption(OptionBuilder.withLongOpt("test-class")
                    .withDescription("Fully quantified test class name (MANDATORY)")
                    .hasArg()
                    .withArgName("CLASS NAME")
                    .isRequired()
                    .create("C"));

            optionsSingleton.addOption(OptionBuilder.withLongOpt("output-dir")
                    .withDescription("Directory to write the generated reports (html file + PNGs).")
                    .hasArg()
                    .withArgName("DIR")
                    .create("o"));
            optionsSingleton.addOption(OptionBuilder.withLongOpt("group")
                    .withDescription("Test group to execute within test class (default: all methods)")
                    .hasArg()
                    .withArgName("NAME")
                    .create("g"));
            optionsSingleton.addOption(OptionBuilder.withLongOpt("fake")
                    .withDescription("Do everything, expect for executing the actual tests (there are considered successful)")
                    .create());
            optionsSingleton.addOption("q", "quiet", false, "less output");
            optionsSingleton.addOption("S", "show-credentials", false, "do not automatically hide credentials and other security sensitive data. " +
                    "Obviously, this can be a security risk if the report is made available online.");
            optionsSingleton.addOption("d", "debug", false, "extra debugging output");
        }
        return optionsSingleton;
    }

    /**
     * main_helper with default TRUE for temporarilyRedirectSystemStreams
     */
    public static int main_helper(String[] args,
                                  final PrintStream out, final PrintStream err, final InputStream in)
            throws IOException, ClassNotFoundException {
        return main_helper(args, out, err, in, new BaseCli(), true);
    }

    public static int main_helper(String[] args,
                                  final PrintStream out, final PrintStream err, final InputStream in,
                                  boolean temporarilyRedirectSystemStreams)
            throws IOException, ClassNotFoundException {
        return main_helper(args, out, err, in, new BaseCli(), temporarilyRedirectSystemStreams);
    }
    public static int main_helper(String[] args,
                                  final PrintStream out, final PrintStream err, final InputStream in,
                                  BaseCli baseCli,
                                  boolean temporarilyRedirectSystemStreams)
            throws IOException, ClassNotFoundException {
        final PrintStream orig_out = System.out;
        final PrintStream orig_err = System.err;
        final InputStream orig_in = System.in;
        if (temporarilyRedirectSystemStreams) {
            System.setOut(out);
            System.setErr(err);
            System.setIn(in);
        }

        int retval = -1;
        try {
            retval = main_internal(args, out, err, in, baseCli);
        } finally {
            if (temporarilyRedirectSystemStreams) {
                System.setOut(orig_out);
                System.setErr(orig_err);
                System.setIn(orig_in);
            }
        }
        return retval;
    }

    public static int main_internal(String[] args,
                                    final PrintStream out, final PrintStream err, final InputStream in,
                                    BaseCli baseCli) throws IOException, ClassNotFoundException {
        CommandLineParser parser = new BasicParser();

        // create the Options
        Options options = getOptions();//create options only once

        CommandLine line = null;
        try {
            // parse the command line arguments
            line = parser.parse(options, args);
        } catch (ParseException exp) {
            err.println("Command line argument Syntax error: " + exp.getMessage());
            help(options);
            return 3;
        }

        String outputdirArg = line.getOptionValue("output-dir");
        String group = line.getOptionValue("group");
        String testclassname = line.getOptionValue("test-class");
        boolean debug = line.hasOption("debug");
        boolean showCredentials = line.hasOption("show-credentials");
        boolean fake = line.hasOption("fake");
        boolean silent = line.hasOption("quiet");


        baseCli.initAuthoritiesModule(line);
        try {
            baseCli.initUserModule(line, out, err, in);
        } catch (BaseCli.CliArgumentException e) {
            LOG.debug("Got CliArgumentException", e);
            //User has been informed already
            return 3;
        }
        try {
            baseCli.initTestTargetFromContextFileModule(line, out, err, in);
        } catch (BaseCli.CliArgumentException e) {
            LOG.debug("Got CliArgumentException", e);
            //User has been informed already
            return 3;
        }
        Injector injector = baseCli.getInjector(err, new AutomatedTestModule());

        TargetAuthority targetAuthority = injector.getInstance(TargetAuthority.class);
        Server testedAuthorityToConnect = targetAuthority.getSfaAuthorityToConnect();
        Server testedAuthorityForRspec = targetAuthority.getSfaAuthorityForRspec();
        GeniUser user = injector.getInstance(GeniUser.class);
        be.iminds.ilabt.jfed.log.Logger logger = injector.getInstance(be.iminds.ilabt.jfed.log.Logger.class);
        TestbedInfoSource testbedInfoSource = injector.getInstance(TestbedInfoSource.class);

        if (group != null && group.equals("null"))
            group = null;
        if (group != null && group.equals(""))
            group = null;

        if (!BaseCli.checkUser(user, line, out, err, in))
            return 3;

        out.println("Got info on "+testbedInfoSource.getAuthorities().size()+" authorities.");

        if (!silent) {
            out.println("Read context properties from file \"" + line.getOptionValue("context-file") + "\":");
            out.println("   Tested Authority:" + testedAuthorityToConnect.getName());
            out.println("      URN (connect):" + testedAuthorityToConnect.getDefaultComponentManagerUrn());
            out.println("      URN (rspec):" + testedAuthorityForRspec.getDefaultComponentManagerUrn());
            out.println("      Hrn:" + testedAuthorityToConnect.getName());
            out.println("      Server certificates:" + testedAuthorityToConnect.getPemSslTrustCerts());
            out.println("      Allowed server certificate hostname aliases:" + testedAuthorityToConnect.getAllowedCertificateHostnameAliases());
            for (Map.Entry<ServerType, URL> e : testedAuthorityToConnect.getUrls().entrySet()) {
                out.println("      URL for " + e.getKey() + ": " + e.getValue());
            }
            out.println("   User:" + user.getUserUrnString());
            out.println("      Authority URN:" + user.getUserAuthority().getDefaultComponentManagerUrn());
        }

        Properties testConfig = new Properties();
        testConfig.load(new FileInputStream(line.getOptionValue("context-file")));

        //backward compatibility: support old StitchingTest class
        if (       testclassname.equals("be.iminds.ilabt.jfed.lowlevel.api.test.StitchingTest")
                || testclassname.equals("be.iminds.ilabt.jfed.lowlevel.api.test.StitchingUsingWrapperTest")
                || testclassname.equals("be.iminds.ilabt.jfed.lowlevel.api.test.StitchingAmv2Test"))
            testclassname = "be.iminds.ilabt.jfed.lowlevel.api.test.StitchingPingTest";

        Class<? extends ApiTest> testClass = (Class<? extends ApiTest>) Class.forName(testclassname);

        AutomatedTestRunner automatedTestRunner = injector.getInstance(AutomatedTestRunner.class);
        ApiTestFactory testFactory = injector.getInstance(ApiTestFactory.class);
        ApiTest test = null;
        try {
            test = testFactory.createApiTest(logger, targetAuthority, testClass);
        } catch (ApiTestFactory.InvalidApiTestException e) {
            err.println("Failed to create test: " + e.getMessage());
            LOG.error("InvalidApiTestException", e);
        }
        assert test != null;
        ApiTestResult result = automatedTestRunner.runTest(testConfig, test, group, new AutomatedTestRunner.TestListener() {
            @Override
            public void onStart(String testname, int testNr, int testCount) {
                out.println("Running " + testname + "...");
            }

            @Override
            public void onResult(ApiTestResult.ApiTestMethodResult result, int testNr, int testCount) {
                out.println("                               " + result.getState().toString());
            }

            @Override
            public void onAllTestDone(ApiTestResult result, int testCount) {
            }
        }, fake);

        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_hh-mm-ss");
        Date now = new Date();
        String outputFilenameHtml = "result.html";
        String outputFilenameXml = "result-overview.xml";
        String outputdirname = "test-results-" + testClass.getSimpleName();
        if (group != null)
            outputdirname += "-" + group;
        outputdirname += "-" + dateFormat.format(now);
        if (outputdirArg != null) {
            outputdirname = outputdirArg;
        }
        File outputdir = new File(outputdirname).getAbsoluteFile();
        File outputFileHtml = new File(outputdir, outputFilenameHtml);
        File outputFileXml = new File(outputdir, outputFilenameXml);

        if (!outputdir.exists()) {
            if (outputdir.getParentFile() != null && !outputdir.getParentFile().exists()) {
                throw new RuntimeException("Error: parent directory \"" + outputdir.getParentFile() + "\"" +
                        " of output file does not exist.");
            } else
                outputdir.mkdir();
        }

        out.println("Saving detailed results to \"" + outputFileHtml.getPath() + "\"...");
        result.toHtmlFile(outputFileHtml, showCredentials);

        out.println("Saving overview to \"" + outputFileXml.getPath() + "\"...");
        result.toXmlOverview(outputFileXml, showCredentials);

        return result.getReturnValue();
    }
}
