/*
 * 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.core;

import org.jboss.installer.validators.MavenOfflineRepositoryValidator;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.stream.Collectors;

import static org.jboss.installer.core.LoggerUtils.taskLog;
import static org.jboss.installer.validators.MavenOfflineRepositoryValidator.isFileUri;

public abstract class MavenRepositoryLoader {

    public static final String MAVEN_REPOSITORY_PROPERTY = "maven_repository.properties";
    public static final String INSTALLER_OVERRIDE_MAVEN_REPOSITORIES = "installer.override.maven.repositories";

    public Properties properties = new Properties();

    /**
     * Check if the {@code url} points to a ZIP archive, if it does, extract it and return URL to a temporary folder.
     * Otherwise, return the unmodified url.
     * @param url
     * @return
     */
    public static URL extractIfNeeded(URL url, UnarchiveProgressCallback unarchiveProgressCallback) {
        if (!url.getProtocol().equals("file")) {
            // it is a URL, not a file or a directory
            return url;
        }

        try {
            final Path path = Path.of(url.toURI());

            if (!Files.exists(path)) {
                throw new InstallerRuntimeException("cannot load maven repository from " + path);
            }

            if (Files.isDirectory(path)) {
                taskLog.debug("load maven offline repository " + path);
                return url;

            } else if (FileUtils.isZipFile(path.toFile())) {
                // unzip to a temporary directory
                try {
                    Path tempDir = FileUtils.createTempDirectory("eap-installer-offline-repo");
                    org.apache.commons.io.FileUtils.forceDeleteOnExit(tempDir.toFile());
                    taskLog.debug("unzip archived maven repository " + path + " to " + tempDir);
                    FileUtils.unzip(path.toFile(), tempDir.toFile(), unarchiveProgressCallback);
                    taskLog.debug("load maven offline repository " + tempDir);
                    File repoRoot = null;
                    for (File child : tempDir.toFile().listFiles()) {
                        if (child.isDirectory() && Files.exists(child.toPath().resolve("maven-repository"))) {
                            if (repoRoot == null) {
                                repoRoot = child;
                            } else {
                                throw new IllegalStateException(String.format("The repository zip contains " +
                                                "multiple repository root folders: `%s/maven-repository` and `%s/maven-respository`",
                                        child, repoRoot));
                            }
                        }
                    }
                    if (repoRoot == null) {
                        throw new IllegalStateException("The repository zip does not contains " +
                                "any repository root folders: (*/maven-repository)");
                    }
                    return repoRoot.toPath().resolve("maven-repository").toUri().toURL();
                } catch (IOException e) {
                    throw new InstallerRuntimeException("failed to load maven offline repository from " + path, e);
                }
            } else {
                throw new InstallerRuntimeException("unsupported type of maven repository " + path);
            }
        } catch (URISyntaxException e) {
            // this should not happen
            throw new InstallerRuntimeException("malformed maven repository URL " + url + ": " + e.getMessage());
        }
    }

    /**
     * Load default repositories used by the installer.
     *
     * @param offlineRepo
     * @return
     */
    public static Map<String, URL> getDefaultRepositories(Optional<Path> offlineRepo) {
        final Map<String, URL> defaultRepositories = new HashMap<>();
        final Properties properties = new Properties();

        // order of resolution:
        // * system property
        // * command line argument
        // * built-in value
        final String overrideRepos = System.getProperty(INSTALLER_OVERRIDE_MAVEN_REPOSITORIES);
        if (overrideRepos != null && !overrideRepos.isEmpty()) {
            final String[] urls = overrideRepos.split(",");
            for (int i = 0; i < urls.length; i++) {
                final String text = urls[i];
                final URL url = parseUrl(text);
                defaultRepositories.put("repo" + i, url);
            }
        } else if (offlineRepo.isPresent()) {
            try {
                final URL repoUrl = offlineRepo.get().toAbsolutePath().toUri().toURL();
                // by default, load maven repository from properties defined in maven_repository.properties
                defaultRepositories.put("offline-repo", repoUrl);
            } catch (MalformedURLException e) {
                // TODO: handle
            }
        } else {
            // by default, load maven repository from properties defined in maven_repository.properties
            try (final InputStream inputStream = FileUtils.class.getClassLoader().getResourceAsStream(MAVEN_REPOSITORY_PROPERTY)) {
                if (inputStream == null) {
                    throw new InstallerRuntimeException("Unable to load maven repository from maven_repository.properties.");
                }
                properties.load(inputStream);
                for (String propName : properties.stringPropertyNames()) {
                    String propValue = properties.getProperty(propName);
                    defaultRepositories.put(propName, new URL(propValue));
                    taskLog.debug("load maven repository " + propName + " : " + propValue);
                }
            } catch (IOException e) {
                throw new InstallerRuntimeException("Unable to determine maven repository from maven_repository.properties.", e);
            }
        }

        return defaultRepositories;
    }

    private static URL parseUrl(String text) {
        try {
            try {
                final URI uri = new URI(text);
                if (uri.isAbsolute()) {
                    return uri.toURL();
                }
            } catch (URISyntaxException e) {
                // ignore - might be a file
            }

            return Paths.get(text).toUri().toURL();

        } catch (MalformedURLException | InvalidPathException e) {
            throw new InstallerRuntimeException("Repository " + text + " has unrecognized format (file path or URI).", e);
        }
    }

    /**
     * Convert a list of repository Strings as URLs. If the repository String is a file or directory, a file: URL is created.
     * If the repository is already in URL format, it is not changed.
     *
     * @param offlineRepositories
     * @return
     */
    public static Map<String, URL> parseToUrls(List<String> offlineRepositories) {
        Map<String, URL> repositories;
        final List<URL> repositoryURLs = offlineRepositories.stream().map(repo -> {
            String repository = repo.trim();
            try {
                if (MavenOfflineRepositoryValidator.isRemoteProtocol(repository)) {
                    // it is a URL
                    return new URI(repository).toURL();
                } else {
                    // it is a directory or a file
                    final Path path = isFileUri(repository) ? Path.of(new URI(repository)) : Path.of(repository);
                    return path.toUri().toURL();
                }
            } catch (URISyntaxException | MalformedURLException e) {
                // might be ignored
            }
            // the following should not happen
            return null;
        }).collect(Collectors.toList());

        repositories = new HashMap<>(repositoryURLs.size());
        for (int i = 0; i < repositoryURLs.size(); i++) {
            URL url = repositoryURLs.get(i);
            repositories.put("repo-" + i, url);
        }
        return repositories;
    }
}
