/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2022 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed 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.jboss.installer.actions.impl;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.text.StringSubstitutor;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import static org.jboss.installer.core.LoggerUtils.taskLog;

public class MavenLocalRepositoryHelper {

    public static final String M2 = ".m2";
    public static final String USER_HOME = System.getProperty("user.home");
    public static final String SETTINGS_XML = "settings.xml";
    public static final Path MAVEN_DEFAULT_LOCAL_REPOSITORY_PATH = Paths.get(USER_HOME, M2, "repository");
    public static final Path M2_DEFAULT_PATH = Paths.get(USER_HOME, M2);

    public static final Path USER_SPECIFIC_SETTINGS_PATH = Paths.get(USER_HOME, M2, SETTINGS_XML);

    public static Path determineLocalRepository() {
        Optional<Path> localRepoPath = Optional.empty();
        final Optional<String> mavenHome;
        // check if settings.xml exists.
        if (Files.exists(USER_SPECIFIC_SETTINGS_PATH)) {
            // first, check user-specific {user.home}/.m2/settings.xml. It would be the dominant over ${M2_HOME}/conf/settings.xml.
            localRepoPath = loadLocalRepositoryFromSettings(USER_SPECIFIC_SETTINGS_PATH);
        }
        if (localRepoPath.isEmpty()) {
            // If <localRepository> is not found in {user.home}/.m2/settings.xml , check ${M2_HOME}/conf/settings.xml.
            mavenHome = System.getenv("M2_HOME") != null ? Optional.of(System.getenv("M2_HOME")) : getMavenHome();
            // It is possible M2_HOME is not defined in env, we need to check in other means.
            if (mavenHome.isPresent() && Files.exists(Paths.get(mavenHome.get(), "conf", SETTINGS_XML))) {
                localRepoPath = loadLocalRepositoryFromSettings(Paths.get(mavenHome.get(), "conf", SETTINGS_XML));
            }
            if (localRepoPath.isEmpty()) {
                // didn't get a valid <localRepository> from settings.xml. If default {user.home}/.m2 exists, assume that we can use default {user.home}/.m2/repository.
                if (Files.exists(M2_DEFAULT_PATH)) {
                    localRepoPath = Optional.of(MAVEN_DEFAULT_LOCAL_REPOSITORY_PATH);
                } else {
                    // fail back to tmp dir.
                    Path tmpPath = org.jboss.installer.core.FileUtils.createTempDirectory("eap-installer-local-repo");
                    localRepoPath = Optional.of(tmpPath);
                    Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                        taskLog.debug("Deleting the temporary local maven repository " + tmpPath);
                        try {
                            FileUtils.deleteDirectory(tmpPath.toFile());
                        } catch (IOException e) {
                            taskLog.debug("error to delete temporary local maven repository " + tmpPath, e);
                        }
                    }));
                }
            }
        }
        return localRepoPath.get();
    }

    public static Optional<Path> loadLocalRepositoryFromSettings(Path mavenSettingsPath) {
        if (Files.exists(mavenSettingsPath)) {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            try {
                dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
                DocumentBuilder db = dbf.newDocumentBuilder();
                Document doc = db.parse(mavenSettingsPath.toFile());
                // We only care about <localRepository>
                NodeList list = doc.getElementsByTagName("localRepository");
                if (list.getLength() > 0) {
                    Node node = list.item(0); // it should only contain zero or one <localRepository>
                    if (node.getNodeType() == Node.ELEMENT_NODE) {
                        Element element = (Element) node;
                        String rawLocalRepository = element.getTextContent();
                        if (StringUtils.isEmpty(rawLocalRepository)) {
                            return Optional.empty();
                        }
                        // must resolve possible system properties e.g. <localRepository>${user.home}/.m2/repository2</localRepository>
                        String resolvedLocalRepoPath = StringSubstitutor.replaceSystemProperties(rawLocalRepository);
                        taskLog.debug("Found local repository " + resolvedLocalRepoPath + " from " + mavenSettingsPath);
                        return Optional.of(Paths.get(resolvedLocalRepoPath));
                    }
                }
            } catch (ParserConfigurationException | SAXException | IOException e) {
                taskLog.debug(e);
            }
        } else {
            taskLog.debug(mavenSettingsPath.toAbsolutePath() + " does not exist");
            return Optional.empty();
        }
        return Optional.empty();
    }

    public static Optional<String> getMavenHome() {
        final String command = SystemUtils.IS_OS_WINDOWS ?"mvn.cmd":"mvn";
        try {
            ProcessBuilder pb = new ProcessBuilder(command, "-v");
            Process p = pb.start();
            BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line;
            while ((line = br.readLine()) != null) {
                if (line.startsWith("Maven home:")) {
                    String mavenHome = line.substring(11).trim();
                    taskLog.debug("maven home : " + mavenHome);
                    return Optional.of(mavenHome);
                }
            }
            if (p.waitFor(10, TimeUnit.SECONDS)) {
                taskLog.debug("Exited with error code " + p.exitValue());
            } else {
                taskLog.debug("Call timed out");
            }
        } catch (Exception e) {
            taskLog.debug("Can't not determine maven home location for command: " + command, e);
        }
        return Optional.empty();
    }
}
