/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.syncope.installer.utilities;

import com.izforge.izpack.panels.process.AbstractUIProcessHandler;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
import org.apache.maven.shared.invoker.DefaultInvocationRequest;
import org.apache.maven.shared.invoker.DefaultInvoker;
import org.apache.maven.shared.invoker.InvocationRequest;
import org.apache.maven.shared.invoker.InvocationResult;
import org.apache.maven.shared.invoker.Invoker;
import org.apache.maven.shared.invoker.MavenInvocationException;
import org.apache.maven.shared.invoker.PrintStreamHandler;
import org.apache.maven.shared.invoker.PrintStreamLogger;
import org.jasypt.commons.CommonUtils;
import org.jasypt.digest.StandardStringDigester;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSParser;

public class MavenUtils {

    private static final String MAVEN_HOME_PROPERTY = "maven.home";

    private final AbstractUIProcessHandler handler;

    public MavenUtils(final String mavenHomeDirectory, final AbstractUIProcessHandler handler) {
        if (System.getProperty(MAVEN_HOME_PROPERTY) == null || System.getProperty(MAVEN_HOME_PROPERTY).isEmpty()) {
            System.setProperty(MAVEN_HOME_PROPERTY, mavenHomeDirectory);
        }
        this.handler = handler;
    }

    public void archetypeGenerate(
            final String archetypeVersion,
            final String groupId,
            final String artifactId,
            final String secretKey,
            final String anonymousKey,
            final String jwsKey,
            final String adminPassword,
            final String installPath,
            final File customSettingsFile) {

        final InvocationRequest request = new DefaultInvocationRequest();
        request.setGoals(Collections.singletonList(
                archetypeVersion.contains("SNAPSHOT")
                ? "org.apache.maven.plugins:maven-archetype-plugin:2.4:generate"
                : "archetype:generate"));
        request.setBatchMode(true);
        final Properties properties =
                archetypeProperties(archetypeVersion, groupId, artifactId, secretKey,
                        anonymousKey, jwsKey, adminPassword);
        request.setProperties(properties);
        if (customSettingsFile != null && FileUtils.sizeOf(customSettingsFile) > 0) {
            request.setUserSettingsFile(customSettingsFile);
        }
        logToHandler(request.getGoals(), properties);
        logToFile(request.getGoals(), properties);
        invoke(request, installPath);
    }

    private Properties archetypeProperties(
            final String archetypeVersion,
            final String groupId,
            final String artifactId,
            final String secretKey,
            final String anonymousKey,
            final String jwsKey,
            final String adminPassword) {

        final Properties properties = new Properties();
        properties.setProperty("archetypeGroupId", "org.apache.syncope");
        properties.setProperty("archetypeArtifactId", "syncope-archetype");
        properties.setProperty("archetypeRepository",
                archetypeVersion.contains("SNAPSHOT")
                ? "https://repository.apache.org/content/repositories/snapshots"
                : "https://repo1.maven.org/maven2");
        properties.setProperty("archetypeVersion", archetypeVersion);
        properties.setProperty("groupId", groupId);
        properties.setProperty("artifactId", artifactId);
        properties.setProperty("secretKey", secretKey);
        properties.setProperty("anonymousKey", anonymousKey);
        properties.setProperty("jwsKey", jwsKey);

        if (adminPassword != null) {
            StandardStringDigester digester = getDigester("S-SHA-256");
            String encodedPassword = digester.digest(adminPassword);
            properties.setProperty("adminPassword", encodedPassword);
        }
        properties.setProperty("version", "1.0-SNAPSHOT");
        return properties;
    }

    public void mvnCleanPackageWithProperties(
            final String path, final Properties properties, final File customSettingsFile) {

        final InvocationRequest request = new DefaultInvocationRequest();
        request.setProperties(properties);
        if (customSettingsFile != null && FileUtils.sizeOf(customSettingsFile) > 0) {
            request.setUserSettingsFile(customSettingsFile);
        }
        final List<String> mavenGoals = new ArrayList<>();
        mavenGoals.add("clean");
        mavenGoals.add("package");
        request.setGoals(mavenGoals);
        logToHandler(request.getGoals(), properties);
        logToFile(request.getGoals(), properties);
        invoke(request, path);
    }

    private void logToHandler(final List<String> goals, final Properties properties) {
        handler.logOutput("Executing maven command:", true);
        final StringBuilder mavenCommand = new StringBuilder("mvn ");
        for (final String goal : goals) {
            mavenCommand.append(goal).append(" ");
        }
        handler.logOutput(mavenCommand.toString(), true);
        for (final String propertyName : properties.stringPropertyNames()) {
            handler.logOutput("-D " + propertyName + "=" + properties.getProperty(propertyName), true);
        }
    }

    private void logToFile(final List<String> goals, final Properties properties) {
        InstallLog.getInstance().info("Executing maven command:");
        final StringBuilder mavenCommand = new StringBuilder("mvn ");
        for (final String goal : goals) {
            mavenCommand.append(goal).append(" ");
        }
        InstallLog.getInstance().info(mavenCommand.toString());
        for (final String propertyName : properties.stringPropertyNames()) {
            InstallLog.getInstance().info("-D " + propertyName + "=" + properties.getProperty(propertyName));
        }
    }

    private InvocationResult invoke(final InvocationRequest request, final String path) {
        InvocationResult result = null;
        final Invoker invoker = new DefaultInvoker();
        try {
            invoker.setLogger(new PrintStreamLogger(
                    new PrintStream(InstallLog.getInstance().getFileAbsolutePath()), 1000));
            invoker.setOutputHandler(new PrintStreamHandler(
                    new PrintStream(InstallLog.getInstance().getFileAbsolutePath()), true));
            invoker.setWorkingDirectory(new File(path));
            result = invoker.execute(request);
        } catch (MavenInvocationException | FileNotFoundException ex) {
            final String messageError = "Maven exception: " + ex.getMessage();
            handler.emitError(messageError, messageError);
            InstallLog.getInstance().info(messageError);
        }
        return result;
    }

    public static File createSettingsWithProxy(final String path, final String proxyHost, final String proxyPort,
            final String proxyUser, final String proxyPassword) throws Exception {

        final File settingsXML = new File(System.getProperty(MAVEN_HOME_PROPERTY) + (System.getProperty(
                MAVEN_HOME_PROPERTY).endsWith("/") ? "conf/settings.xml" : "/conf/settings.xml"));
        final File tempSettingsXML = new File(path + (path.endsWith("/") ? "settings_temp.xml" : "/settings_temp.xml"));
        if (settingsXML.canRead() && !tempSettingsXML.exists()) {
            tempSettingsXML.createNewFile();

            DOMImplementationRegistry reg = DOMImplementationRegistry.newInstance();
            DOMImplementationLS domImpl = (DOMImplementationLS) reg.getDOMImplementation("LS");
            LSInput lsinput = domImpl.createLSInput();
            lsinput.setByteStream(new FileInputStream(settingsXML));
            LSParser parser = domImpl.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS, null);
            // parse settings.xml
            final Document settings = parser.parse(lsinput);

            final Element proxies = (Element) settings.getDocumentElement().getElementsByTagName("proxies").item(0);

            final Element proxy = settings.createElement("proxy");

            final Element id = settings.createElement("id");
            final Element active = settings.createElement("active");
            final Element protocol = settings.createElement("protocol");
            final Element host = settings.createElement("host");
            final Element port = settings.createElement("port");
            final Element nonProxyHosts = settings.createElement("nonProxyHosts");
            id.appendChild(settings.createTextNode("optional"));
            active.appendChild(settings.createTextNode("true"));
            protocol.appendChild(settings.createTextNode("http"));
            host.appendChild(settings.createTextNode(proxyHost));
            port.appendChild(settings.createTextNode(proxyPort));
            proxy.appendChild(id);
            proxy.appendChild(active);
            proxy.appendChild(protocol);
            // create username and password tags only if required
            if (proxyUser != null && !proxyUser.isEmpty() && proxyPassword != null) {
                final Element username = settings.createElement("username");
                final Element password = settings.createElement("password");
                username.appendChild(settings.createTextNode(proxyUser));
                password.appendChild(settings.createTextNode(proxyPassword));
                proxy.appendChild(username);
                proxy.appendChild(password);
            }
            proxy.appendChild(host);
            proxy.appendChild(port);
            proxy.appendChild(nonProxyHosts);

            proxies.appendChild(proxy);

            FileSystemUtils.writeXML(settings, Files.newOutputStream(tempSettingsXML.toPath()));

        }
        return tempSettingsXML;
    }

    private static StandardStringDigester getDigester(final String cipherAlgorithm) {
        StandardStringDigester digester = new StandardStringDigester();

        if (cipherAlgorithm.startsWith("S-")) {
            // Salted ...
            digester.setAlgorithm(cipherAlgorithm.replaceFirst("S\\-", ""));
            digester.setIterations(1);
            digester.setSaltSizeBytes(8);
            digester.setInvertPositionOfPlainSaltInEncryptionResults(true);
            digester.setInvertPositionOfSaltInMessageBeforeDigesting(true);
            digester.setUseLenientSaltSizeCheck(true);
        } else {
            // Not salted ...
            digester.setAlgorithm(cipherAlgorithm);
            digester.setIterations(1);
            digester.setSaltSizeBytes(0);
        }

        digester.setStringOutputType(CommonUtils.STRING_OUTPUT_TYPE_HEXADECIMAL);
        return digester;
    }
}
