package org.jboss.installer.postinstall.task.jsf;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.jboss.installer.actions.ActionException;
import org.jboss.installer.postinstall.TaskPrinter;
import org.jboss.installer.postinstall.task.JSFLibraryConfig;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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

public class MojarraJsfLibrarySetup {

    private final TaskPrinter printer;

    public MojarraJsfLibrarySetup(TaskPrinter printer) {
        this.printer = printer;
    }

    public boolean perform(JSFLibraryConfig config, Path basePath) throws IOException {
        // if urls_provides
        Path implJar = null;
        try {
            // download artifacts from URLs
            final String jsfVersion = config.getJsfVersion()==null?"unknown":config.getJsfVersion();
            if (config.getImplJarPath() != null) {
                if (config.getResolvedImplJarPath() == null) {
                    implJar = downloadJar(config.getImplJarPath(), "mojarra-impl-" + jsfVersion + ".jar");
                } else {
                    implJar = Path.of(config.getResolvedImplJarPath());
                }
            }
            //     set up Modules
            return installModules(config.getJsfProject(), jsfVersion, basePath, implJar);
        } finally {
            // delete the jars
            if (config.getImplJarPath() != null && !config.getImplJarPath().equals(config.getResolvedImplJarPath())) {
                FileUtils.deleteQuietly(implJar.toFile());
            }
        }
    }

    private Path downloadJar(String jarPath, String filename) throws IOException {
        File jar = new File(jarPath);
        boolean fromUrl = isRemoteUrl(jarPath) || jarPath.startsWith("file://");

        if (isRemoteUrl(jarPath)) {
            printer.print("tasks.jsf_module.downloading.started", jarPath);
        }

        final Path output;
        try {
            output = Files.createTempFile(filename, ".jar");
            output.toFile().deleteOnExit();
        } catch (IOException e) {
            throw e;
        }
        try (final InputStream in = fromUrl ? new BufferedInputStream(new URL(jarPath).openStream()) : new FileInputStream(jar);
             final FileOutputStream outputStream = new FileOutputStream(output.toFile())) {
            IOUtils.copy(in, outputStream);

            if (isRemoteUrl(jarPath)) {
                printer.print("tasks.jsf_module.downloading.finished", jarPath);

            }
        } catch (IOException e) {
            printer.print("tasks.jsf_module.downloading.failed", jarPath);
            throw e;
        }
        return output;
    }

    private boolean installModules(JSFLibraryConfig.JsfProject jsfProject, String jsfVersion, Path baseFolder, Path implJar) {
        printer.print("tasks.jsf_module.started");
        try {
            if (implJar != null) {
                printer.print("tasks.jsf_module.create", "jakarta.faces.impl");
                createModule(jsfProject, jsfVersion, implJar, baseFolder, Paths.get("jakarta", "faces", "impl"), "impl");
            }
            printer.print("tasks.jsf_module.create", "jakarta.faces.api");
            createModule(jsfProject, jsfVersion, null, baseFolder, Paths.get("jakarta", "faces", "api"), "api");
            printer.print("tasks.jsf_module.create", "org.jboss.as.jsf-injection");
            createInjectionModule(jsfProject, jsfVersion, baseFolder, Paths.get( "org", "jboss", "as", "jsf-injection"));
        } catch (IOException e) {
            taskLog.error("Failed to create modules directory", e);
            printer.print("tasks.jsf_module.failed");
            printer.print(e);
            return false;
        } catch (ActionException e) {
            taskLog.error(e.getMessage(), e);
            printer.print("tasks.jsf_module.failed");
            printer.print(e);
            return false;
        }
        printer.print("tasks.jsf_module.finished");
        return true;
    }

    private void createInjectionModule(JSFLibraryConfig.JsfProject jsfProject, String jsfVersion, Path baseFolder, Path moduleGroup) throws IOException, ActionException {
        final String slot = String.format("%s-%s", jsfProject.toString().toLowerCase(Locale.ROOT), jsfVersion);
        final Path modulePath = baseFolder.resolve("modules").resolve(moduleGroup).resolve(slot);
        final Path oldModulePath = baseFolder.resolve("modules")
                .resolve(Paths.get("system", "layers", "base", "org", "jboss", "as", "jsf-injection", "main"));

        final Optional<Path> weldJar = findFile(oldModulePath, "weld-jsf");
        final Optional<Path> jsfInjectionJar = findFile(oldModulePath, "wildfly-jsf-injection");

        if (weldJar.isEmpty() || jsfInjectionJar.isEmpty()) {
            throw new ActionException("Could not find Wildfly module JARs. Unable to copy.");
        }

        String versionWeldCore = extractSubstring("^weld-jsf-(.*?)\\.jar$", weldJar.get().getFileName().toString());
        String versionJbossAs = extractSubstring("^wildfly-jsf-injection-(.*?)\\.jar$", jsfInjectionJar.get().getFileName().toString());

        final HashMap<String, String> replacements = new HashMap<>();
        replacements.put("${jsf-version}", jsfVersion);
        replacements.put("${jsf-impl-name}", jsfProject.fileName());
        replacements.put("${version.jboss.as}", versionJbossAs);
        replacements.put("${version.weld.core}", versionWeldCore);


        if (!modulePath.toFile().exists()) {
            Files.createDirectories(modulePath);
            FileUtils.copyFileToDirectory(weldJar.get().toFile(), modulePath.toFile());
            FileUtils.copyFileToDirectory(jsfInjectionJar.get().toFile(), modulePath.toFile());

            createModuleFile(modulePath, replacements, String.format("templates/%s-injection-module.xml", jsfProject.fileName()));
        }
    }

    private static String extractSubstring(String regex, String str) {
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(str);
        String sub = "";
        if (matcher.find()) {
            sub = matcher.group(1);
        }
        return sub;
    }

    private Optional<Path> findFile(Path base, String prefix) {
        final String[] list = base.toFile().list((dir, name) -> name.startsWith(prefix));
        return (list == null || list.length == 0)?Optional.empty():Optional.of(base.resolve(list[0]));
    }

    private void createModule(JSFLibraryConfig.JsfProject jsfProject, String jsfVersion, Path jarFile, Path baseFolder, Path moduleGroup, String type) throws IOException {
        final String jsfDirName = String.format("%s-%s", jsfProject.toString().toLowerCase(Locale.ROOT), jsfVersion);
        final Path modulePath = baseFolder.resolve("modules").resolve(moduleGroup).resolve(jsfDirName);

        final HashMap<String, String> replacements = new HashMap<>();
        replacements.put("${jsf-version}", jsfVersion);
        replacements.put("${jsf-impl-name}", jsfProject.fileName());

        if (!modulePath.toFile().exists()) {
            Files.createDirectories(modulePath);
            if (type.equals("impl")) {
                String filename = String.format("jakarta.faces-%s.jar", jsfVersion);
                Files.copy(jarFile, modulePath.resolve(filename));
            }
        }

        createModuleFile(modulePath, replacements, String.format("templates/%s-%s-module.xml", jsfProject.fileName(), type));
    }

    private static boolean isRemoteUrl(String jarPath) {
        return jarPath.startsWith("http://") || jarPath.startsWith("ftp://") || jarPath.startsWith("https://");
    }

    private void createModuleFile(Path modulePath, Map<String, String> replacements, String template) throws IOException {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream(template)));
             PrintWriter writer = new PrintWriter(new FileWriter(modulePath.resolve("module.xml").toFile()))) {

            reader.lines()
                    .map(l->replaceArguments(l, replacements))
                    .forEach(writer::println);
        }
    }

    private String replaceArguments(String l, Map<String, String> replacements) {
        for (String key : replacements.keySet()) {
            if (l.contains(key)) {
                l = l.replace(key, replacements.get(key));
            }
        }
        return l;
    }
}
