package com.atsebak.embeddedlinuxjvm.protocol.ssh;

import com.atsebak.embeddedlinuxjvm.commandline.JavaStatusChecker;
import com.atsebak.embeddedlinuxjvm.commandline.LinuxCommand;
import com.atsebak.embeddedlinuxjvm.console.EmbeddedLinuxJVMConsoleView;
import com.atsebak.embeddedlinuxjvm.localization.EmbeddedLinuxJVMBundle;
import com.atsebak.embeddedlinuxjvm.protocol.ssh.jsch.EmbeddedSSHClient;
import com.atsebak.embeddedlinuxjvm.protocol.ssh.jsch.SFTPHandler;
import com.atsebak.embeddedlinuxjvm.runner.data.EmbeddedLinuxJVMRunConfigurationRunnerParameters;
import com.atsebak.embeddedlinuxjvm.utils.FileUtilities;
import com.intellij.execution.configurations.RuntimeConfigurationException;
import com.intellij.execution.ui.ConsoleViewContentType;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.application.ApplicationManager;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import lombok.Builder;
import lombok.SneakyThrows;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;

@Builder
public class SSHHandlerTarget {
    public static final String NEW_LINE = System.getProperty("line.separator");
    private static final String OUTPUT_LOCATION = "IdeaProjects";
    private static final String EMBEDDED_LINUX_HOME = "home";
    private EmbeddedLinuxJVMRunConfigurationRunnerParameters params;
    private EmbeddedLinuxJVMConsoleView consoleView;
    private EmbeddedSSHClient ssh;

    /**
     * Uploads Java application output folders
     *
     * @param compileOutput Output directory folder where to store the java application
     * @param cmd           The command to execute on the java files
     * @throws IOException
     * @throws ClassNotFoundException
     * @throws RuntimeConfigurationException
     */
    public void uploadAndRunJavaApp(@NotNull final File compileOutput, @NotNull final String cmd)
            throws IOException, ClassNotFoundException, RuntimeConfigurationException {
        final String remoteDir = FileUtilities.SEPARATOR + EMBEDDED_LINUX_HOME + FileUtilities.SEPARATOR
                + params.getUsername() + FileUtilities.SEPARATOR + OUTPUT_LOCATION;
        String deploymentPath = remoteDir + FileUtilities.SEPARATOR + consoleView.getProject().getName();
        genericUpload(deploymentPath, compileOutput);
        runJavaApp(deploymentPath, cmd);
    }

    /**
     * Generic SSh Ftp uploader
     *
     * @param deploymentPath the remote location storing the compressed file
     * @param fileToUpload   files to upload
     * @throws IOException
     * @throws RuntimeConfigurationException
     */
    public void genericUpload(@NotNull final String deploymentPath, @NotNull final File fileToUpload) throws IOException, RuntimeConfigurationException {
        forceCreateDirectories(deploymentPath);
        Session session = connect(ssh.get());
        try {
            SFTPHandler sftpHandler = new SFTPHandler(consoleView);
            sftpHandler.upload(session, fileToUpload, deploymentPath);
        } catch (Exception e) {
            setErrorOnUI(e.getMessage());
        }
    }


    /**
     * Force create directories, if it exists it won't do anything
     *
     * @param path
     * @throws IOException
     * @throws RuntimeConfigurationException
     */
    private void forceCreateDirectories(@NotNull final String path) throws IOException, RuntimeConfigurationException {
        Session session = connect(ssh.get());
        try {
            ChannelExec channelExec = (ChannelExec) session.openChannel("exec");
            List<String> commands = Arrays.asList(
                    String.format("mkdir -p %s", path),
                    String.format("cd %s", path),
                    String.format("mkdir -p %s", FileUtilities.CLASSES),
                    String.format("mkdir -p %s", FileUtilities.LIB),
                    String.format("cd %s", path + FileUtilities.SEPARATOR + FileUtilities.CLASSES),
                    "rm -rf *"
            );
            for (String command : commands) {
                consoleView.print(EmbeddedLinuxJVMBundle.getString("pi.deployment.command") + command + NEW_LINE, ConsoleViewContentType.SYSTEM_OUTPUT);
            }
            channelExec.setCommand(LinuxCommand.builder().commands(commands).build().toString());
            channelExec.connect();
            channelExec.disconnect();
        } catch (JSchException e) {
            setErrorOnUI(e.getMessage());
        }
    }

    /**
     * Runs that java app with the specified command and then takes the console output from target to host machine
     *
     * @param path
     * @param cmd
     * @throws IOException
     */
    private void runJavaApp(@NotNull final String path, @NotNull final String cmd) throws IOException, RuntimeConfigurationException {
        consoleView.print(NEW_LINE + EmbeddedLinuxJVMBundle.getString("pi.deployment.build") + NEW_LINE + NEW_LINE, ConsoleViewContentType.SYSTEM_OUTPUT);
        Session session = connect(ssh.get());
        consoleView.setSession(session);
        try {
            ChannelExec channelExec = (ChannelExec) session.openChannel("exec");
            channelExec.setOutputStream(System.out, true);
            channelExec.setErrStream(System.err, true);
            List<String> commands = Arrays.asList(
                    String.format("%s kill -9 $(ps -efww | grep \"%s\"| grep -v grep | tr -s \" \"| cut -d\" \" -f2)", params.isRunAsRoot() ? "sudo" : "", params.getMainclass()),
                    String.format("cd %s", path),
                    String.format("tar -xvf %s.tar", consoleView.getProject().getName()),
                    "rm *.tar",
                    cmd);
            for (String command : commands) {
                consoleView.print(EmbeddedLinuxJVMBundle.getString("pi.deployment.command") + command + NEW_LINE, ConsoleViewContentType.SYSTEM_OUTPUT);
            }
            channelExec.setCommand(LinuxCommand.builder().commands(commands).build().toString());
            channelExec.connect();
            checkOnProcess(channelExec);
        } catch (JSchException e) {
            setErrorOnUI(e.getMessage());
        }
    }

    /**
     * Checks if command is done running
     */
    private void checkOnProcess(@NotNull final ChannelExec channelExec) {
        ApplicationManager.getApplication().executeOnPooledThread(new JavaStatusChecker(channelExec, consoleView));
    }

    public Vector getAlreadyDeployedLibraries() throws JSchException {
        final String remoteDir = FileUtilities.SEPARATOR + EMBEDDED_LINUX_HOME + FileUtilities.SEPARATOR
                + params.getUsername() + FileUtilities.SEPARATOR + OUTPUT_LOCATION;
        String path = remoteDir + FileUtilities.SEPARATOR + consoleView.getProject().getName();
        String jarsPath = path + FileUtilities.SEPARATOR + FileUtilities.LIB;
        SFTPHandler handler = new SFTPHandler();
        Session session = connect(ssh.get());
        return handler.getFiles(session, jarsPath);
    }

    /**
     * Authenticates and connects to remote target via ssh protocol
     * @param session
     * @return
     */
    @SneakyThrows({RuntimeConfigurationException.class})
    private Session connect(Session session) {
        if (!session.isConnected()) {
            session = EmbeddedSSHClient.builder()
                    .username(params.getUsername())
                    .password(params.getPassword())
                    .hostname(params.getHostname())
                    .port(params.getSshPort())
                    .build().get();
            if (!session.isConnected()) {
                setErrorOnUI(EmbeddedLinuxJVMBundle.getString("ssh.remote.error"));
                throw new RuntimeConfigurationException(EmbeddedLinuxJVMBundle.getString("ssh.remote.error"));
            } else {
                return session;
            }
        }
        return session;
    }

    /**
     * Sets errors on the UI
     *
     * @param message
     */
    private void setErrorOnUI(@NotNull final String message) {
        final Notification notification = new Notification(
                com.atsebak.embeddedlinuxjvm.utils.Notifications.GROUPDISPLAY_ID,
                EmbeddedLinuxJVMBundle.getString("pi.ssh.connection.error"), message,
                NotificationType.INFORMATION);
        Notifications.Bus.notify(notification);
    }
}