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

import org.apache.commons.io.FileUtils;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.deployment.DeployRequest;
import org.eclipse.aether.deployment.DeploymentException;
import org.eclipse.aether.repository.RemoteRepository;
import org.jboss.galleon.ProvisioningException;
import org.jboss.galleon.config.ConfigId;
import org.jboss.installer.actions.ActionException;
import org.jboss.installer.actions.impl.InstallEap;
import org.jboss.installer.actions.impl.MavenLocalRepositoryHelper;
import org.jboss.installer.actions.impl.ProvisioningExceptionHandler;
import org.jboss.installer.core.InstallerRuntimeException;
import org.jboss.installer.core.LanguageUtils;
import org.jboss.installer.postinstall.TaskPrinter;
import org.wildfly.channel.Channel;
import org.wildfly.channel.ChannelManifest;
import org.wildfly.channel.ChannelManifestCoordinate;
import org.wildfly.channel.ChannelManifestMapper;
import org.wildfly.channel.Stream;
import org.wildfly.prospero.actions.FeaturesAddAction;
import org.wildfly.prospero.actions.MetadataAction;
import org.wildfly.prospero.api.Console;
import org.wildfly.prospero.api.MavenOptions;
import org.wildfly.prospero.api.ProvisioningProgressEvent;
import org.wildfly.prospero.api.exceptions.MetadataException;
import org.wildfly.prospero.api.exceptions.OperationException;
import org.wildfly.prospero.wfchannel.MavenSessionManager;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import static org.jboss.installer.actions.impl.InstallEap.MAVEN_REPO_PATH;
import static org.jboss.installer.core.LoggerUtils.taskLog;

public class MyFacesJsfLibrarySetup {

    public static final String MYFACES_GROUP_ID = "org.apache.myfaces.core";
    public static final String MYFACES_API_ARTIFACT_ID = "myfaces-api";
    public static final String MYFACES_IMPL_ARTIFACT_ID = "myfaces-impl";
    public static final String DEFAULT_CHANNEL_GROUP_ID = "org.apache.myfaces.channel";
    public static final String DEFAULT_CHANNEL_ARTIFACT_ID = "myfaces";
    public static final String MYFACES_LAYER = "myfaces";
    public static final String DEFAULT_CHANNEL_COORDINATE = DEFAULT_CHANNEL_GROUP_ID + ":" + DEFAULT_CHANNEL_ARTIFACT_ID;
    private static final String MYFACES_FEATURE_PACK_GA = "org.jboss.eap:eap-myfaces-feature-pack";
    private static final ConfigId DEFAULT_CONFIG = null;
    private final ProvisioningExceptionHandler exceptionHandler;
    private final Path installationDir;

    public MyFacesJsfLibrarySetup(Path installationDir, LanguageUtils langUtils) {
        this.installationDir = installationDir;
        this.exceptionHandler = new ProvisioningExceptionHandler(langUtils);
    }

    public String createChannelManifest(String jsfVersion, String localChannelRepositoryPath) throws ActionException {
        Path tempFile = null;
        try {
            // create manifest
            final ChannelManifest channelManifest = new ChannelManifest("MyFaces manifest", DEFAULT_CHANNEL_ARTIFACT_ID, null, List.of(
                    new Stream(MYFACES_GROUP_ID, MYFACES_API_ARTIFACT_ID, jsfVersion),
                    new Stream(MYFACES_GROUP_ID, MYFACES_IMPL_ARTIFACT_ID, jsfVersion)));

            tempFile = org.jboss.installer.core.FileUtils.createTempFile("myfaces-manifest", ".yaml");
            try {
                Files.writeString(tempFile, ChannelManifestMapper.toYaml(channelManifest), StandardCharsets.UTF_8);
            } catch (IOException e) {
                throw new InstallerRuntimeException("Unable to write a temporary file " + tempFile, e);
            }

            deployToRepository(Path.of(localChannelRepositoryPath), new DefaultArtifact(DEFAULT_CHANNEL_GROUP_ID, DEFAULT_CHANNEL_ARTIFACT_ID,
                    "manifest", "yaml", "1.0.0", null, tempFile.toFile()));

            return DEFAULT_CHANNEL_COORDINATE;
        } catch (DeploymentException e) {
            throw new ActionException("Unable to deploy created channel manifest file to " + localChannelRepositoryPath, e);
        } finally {
            if (tempFile != null) {
                FileUtils.deleteQuietly(tempFile.toFile());
            }
        }
    }

    public static void deployToRepository(Path localChannelRepositoryPath, DefaultArtifact artifact)
            throws DeploymentException {
        final Path localRepoPath;
        if (MAVEN_REPO_PATH != null) {
            localRepoPath = Paths.get(MAVEN_REPO_PATH);
        } else {
            localRepoPath = MavenLocalRepositoryHelper.determineLocalRepository();
        }

        taskLog.debug("localRepoPath set to : " + localRepoPath);

        final MavenOptions mavenOptions = MavenOptions.builder()
                .setLocalCachePath(localRepoPath)
                .setOffline(false)
                .build();

        final MavenSessionManager msm;
        try {
            msm = new MavenSessionManager(mavenOptions);
        } catch (ProvisioningException e) {
            throw new InstallerRuntimeException("Unable to create a temporary maven cache folder", e);
        }
        final RepositorySystem system = msm.newRepositorySystem();
        final DefaultRepositorySystemSession session = msm.newRepositorySystemSession(system);

        final URL url;
        try {
            url = localChannelRepositoryPath.toUri().toURL();
        } catch (MalformedURLException e) {
            throw new InstallerRuntimeException("The path " + localChannelRepositoryPath + " cannot be transformed to an URL", e);
        }
        final DeployRequest deployRequest = new DeployRequest();
        deployRequest.setRepository(new RemoteRepository.Builder("jsf-repo", "default", url.toExternalForm()).build());
        deployRequest.addArtifact(artifact);
        system.deploy(session, deployRequest);
    }

    public void registerChannel(List<String> repositoryUrls, String manifestCoordinates) {
        // add the myfaces channel definition
        try (MetadataAction metadataAction = new MetadataAction(installationDir)) {
            final Channel.Builder channel = new Channel.Builder()
                    .setName("MyFaces channel")
                    .setManifestCoordinate(new ChannelManifestCoordinate(manifestCoordinates.split(":")[0],
                            manifestCoordinates.split(":")[1]));
            for (int i = 0; i < repositoryUrls.size(); i++) {
                channel.addRepository("myfaces-" + i, repositoryUrls.get(i));
            }
            try {
                metadataAction.addChannel(channel.build());
            } catch (MetadataException e) {
                throw new InstallerRuntimeException("Unable to add channel " + channel.build().getName(), e);
            }
        } catch (MetadataException e) {
            throw new InstallerRuntimeException("Unable to load the server installation metadata", e);
        }
    }

    public void installFeaturePack(TaskPrinter printer) throws ActionException {
        try {
            final Path localRepoPath;
            if (MAVEN_REPO_PATH != null) {
                localRepoPath = Paths.get(MAVEN_REPO_PATH);
            } else {
                localRepoPath = MavenLocalRepositoryHelper.determineLocalRepository();
            }

            taskLog.debug("Creating a maven session with localRepoPath set to : " + localRepoPath);
            final MavenOptions mavenOptions = MavenOptions.builder()
                    .setLocalCachePath(localRepoPath)
                    .setOffline(false)
                    .build();

            taskLog.debug(String.format("Installing the feature pack %s using %s layer", MYFACES_FEATURE_PACK_GA, MYFACES_GROUP_ID));
            final TaskPrinterConsoleAdapter consoleAdapter = new TaskPrinterConsoleAdapter(printer);
            final FeaturesAddAction featuresAddAction = new FeaturesAddAction(mavenOptions, installationDir,
                    Collections.emptyList(), consoleAdapter);
            featuresAddAction.addFeaturePackWithLayers(MYFACES_FEATURE_PACK_GA, Set.of(MYFACES_LAYER), DEFAULT_CONFIG);
            taskLog.debug("Feature pack installation completed");

        } catch (MetadataException e) {
            throw new InstallerRuntimeException("Unable to load the server installation metadata", e);
        } catch (ProvisioningException e) {
            throw exceptionHandler.handle(e);
        } catch (OperationException e) {
            throw exceptionHandler.handle(e);
        }
    }

    private static class TaskPrinterConsoleAdapter implements Console {

        private final TaskPrinter printer;
        private boolean cleanupStarted;
        private String currentPhase;

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

        public void completed() {
            if (cleanupStarted) {
                // ugly hack - prospero should emit some information when post-provisioning stuff happens
                printer.print("tasks.jsf_module.provision.phase.done", "${" + InstallEap.Stage.CLEANUP.getKey() + "}");
            }
            cleanupStarted = false;
        }

        @Override
        public void progressUpdate(ProvisioningProgressEvent update) {
            InstallEap.Stage stage = InstallEap.Stage.fromGalleonStage(update.getStage());

            if (update.getEventType() == ProvisioningProgressEvent.EventType.STARTING) {
                printer.print("tasks.jsf_module.provision.phase.start", "${" + stage.getKey() + "}");
                taskLog.debug("Starting phase " + stage);
            } else if (update.getEventType() == ProvisioningProgressEvent.EventType.COMPLETED) {
                printer.print("tasks.jsf_module.provision.phase.done", "${" + stage.getKey() + "}");
                taskLog.debug("Completed phase " + stage);
                if (stage == InstallEap.Stage.EXTRA_CONFIG) {
                    cleanupStarted = true;
                    printer.print("tasks.jsf_module.provision.phase.start", "${" + InstallEap.Stage.CLEANUP.getKey() + "}");
                    taskLog.debug("Starting phase " + stage);
                }
            } else {
                if (!update.getStage().equals(currentPhase)) {
                    printer.print("tasks.jsf_module.provision.phase.start", "${" + stage.getKey() + "}");
                    taskLog.debug("Completed phase " + stage);
                }
            }
            currentPhase = update.getStage();
        }

        @Override
        public void println(String text) {

        }
    }
}
