/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2023 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.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.jboss.installer.dialogs.downloader.Download;
import org.jboss.installer.dialogs.downloader.DownloadHandler;
import org.jboss.installer.dialogs.downloader.DownloadManager;
import org.jboss.installer.validators.PathValidator;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

public class DownloadUtils {
    private final DownloadUIFactory downloaderUIFactory;
    private final Consumer<Download> validationErrorUI;
    private final DownloadManager downloadManager;
    private final Map<String, String> resolvedUrlsMap = new HashMap<>();
    private final Path tempDirectory;
    private final Set<String> fileNames;

    public DownloadUtils(DownloadManager downloadManager, DownloadUIFactory downloaderUI,
                         Consumer<Download> validationErrorUI) {
        this.downloadManager = downloadManager;
        this.downloaderUIFactory = downloaderUI;
        this.validationErrorUI = validationErrorUI;

        try {
            tempDirectory = Files.createTempDirectory("eap-installer-downloads");
            org.apache.commons.io.FileUtils.forceDeleteOnExit(tempDirectory.toFile());
        }  catch (IOException e) {
            throw new InstallerRuntimeException("Unable to create a temporary folder.", e);
        }

        fileNames = new HashSet<>();
    }

    /**
     * downloads all files represented as URLs. If the file is not a URL, the value is passed unchanged
     *
     * @param files
     * @return a list of local paths, or null if the download was cancelled.
     */
    public List<String> downloadAll(List<String> files) {
        final List<String> resolvedJarLocations = new ArrayList<>(files.size());
        List<Download> downloads = new ArrayList<>();

        try {
            for (String jar : files) {
                if (jar != null && FileUtils.isUrl(jar)) {
                    if (resolvedUrlsMap.containsKey(jar)) {
                        resolvedJarLocations.add(resolvedUrlsMap.get(jar));
                        continue;
                    }
                    final URL url = new URL(jar);
                    String fileName = FilenameUtils.getName(url.getPath());

                    // make sure the filename is not too long
                    if (fileName.length() > PathValidator.FILENAME_LENGTH_LIMIT - 7) {
                        fileName = fileName.substring(0, PathValidator.FILENAME_LENGTH_LIMIT - 7);
                    }

                    final int suffixIndex = fileName.lastIndexOf('.');
                    final String suffix = suffixIndex>0?fileName.substring(suffixIndex):"";
                    final String base = suffixIndex>0?fileName.substring(0, suffixIndex):fileName;
                    int count = 1;
                    while (fileNames.contains(fileName)) {
                        if (count++ > 10) {
                            throw new InstallerRuntimeException("Failed to generate unique name after 10 attempts: " + fileName);
                        }
                        fileName = base + "-" + RandomStringUtils.randomAlphabetic(6).toLowerCase(Locale.ROOT) + suffix;
                    }
                    fileNames.add(fileName);

                    final Path downloadedFile = tempDirectory.resolve(fileName);
                    downloads.add(new Download(url, downloadedFile.toFile()));
                    resolvedJarLocations.add(downloadedFile.toAbsolutePath().toString());
                    resolvedUrlsMap.put(jar, downloadedFile.toAbsolutePath().toString());
                } else {
                    resolvedJarLocations.add(jar);
                }
            }
            if (downloads.isEmpty()) {
                return resolvedJarLocations;
            }

            for (Download download : downloads) {
                if (!downloadManager.verify(download)) {
                    validationErrorUI.accept(download);
                    return null;
                }
            }

            final DownloadUI downloadUI = downloaderUIFactory.prepareUI(downloads, downloadManager);
            downloadManager.start(downloads, downloadUI.getDownloadHandler());

            if (!downloadUI.display()) {
                return null;
            }

        } catch (MalformedURLException e) {
            // already checked that we get valid URLs
            throw new InstallerRuntimeException("Provided value is not an URL.", e);
        }
        return resolvedJarLocations;
    }


    public interface DownloadUI {
        /**
         * make the UI visible to the user. Potentially blocking operation, called right after the downloads are started.
         * @return
         */
        boolean display();

        /**
         * get callbacks used to report download progress.
         * @return
         */
        DownloadHandler getDownloadHandler();
    }

    public interface DownloadUIFactory {
        /**
         * Build the UI and prepare it for displaying, but don't execute and blocking code.
         *
         * @param downloads
         * @param downloadManager
         * @return
         */
        DownloadUI prepareUI(List<Download> downloads, DownloadManager downloadManager);
    }
}
