package be.iminds.ilabt.jfed.experimenter_gui.slice.actions;

import be.iminds.ilabt.jfed.experimenter_gui.slice.ExperimentViewController;
import be.iminds.ilabt.jfed.experimenter_gui.ui.PasswordInputDialog;
import be.iminds.ilabt.jfed.experimenter_gui.ui.progressbar.TaskService;
import be.iminds.ilabt.jfed.experimenter_gui.util.FileUtils;
import be.iminds.ilabt.jfed.lowlevel.GeniUser;
import be.iminds.ilabt.jfed.lowlevel.GeniUserProvider;
import be.iminds.ilabt.jfed.lowlevel.connection.JFedConnection;
import be.iminds.ilabt.jfed.lowlevel.ssh_key_info.SshKeyInfo;
import be.iminds.ilabt.jfed.lowlevel.ssh_key_info.SshKeyInfoFactory;
import be.iminds.ilabt.jfed.preferences.GuiPreferenceKey;
import be.iminds.ilabt.jfed.preferences.JFedGuiPreferences;
import be.iminds.ilabt.jfed.rspec.model.BasicStringRspec;
import be.iminds.ilabt.jfed.lowlevel.ssh_key_info.putty.PuTTYPrivateKeyFile;
import be.iminds.ilabt.jfed.ssh_terminal_tool.ssh_key_info.PreferencesPuttySshKeyInfo;
import be.iminds.ilabt.jfed.util.AnsibleFileWriter;
import be.iminds.ilabt.jfed.util.FXPlatformUtil;
import be.iminds.ilabt.jfed.preferences.ProxyPreferencesManager;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.control.Alert;
import javafx.stage.FileChooser;
import javafx.stage.Window;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.security.PrivateKey;
import java.security.PublicKey;

/**
 * User: twalcari
 * Date: 2/2/2015
 * Time: 16:44
 */
public class ExportToolsConfigFilesAction extends ExperimentViewAction {
    private static final Logger LOG = LoggerFactory.getLogger(ExportToolsConfigFilesAction.class);
    private static final String ROOT_USERNAME = "root";


    private final TaskService taskService;
    private final JFedGuiPreferences jFedPreferences;
    private final GeniUserProvider geniUserProvider;
    private final ProxyPreferencesManager proxyPreferencesManager;

    ExportToolsConfigFilesAction(Window parentWindow,
                                 ExperimentViewController experimentViewController,
                                 TaskService taskService,
                                 JFedGuiPreferences jFedPreferences,
                                 GeniUserProvider geniUserProvider,
                                 ProxyPreferencesManager proxyPreferencesManager) {
        super(experimentViewController);
        this.taskService = taskService;
        this.jFedPreferences = jFedPreferences;
        this.geniUserProvider = geniUserProvider;
        this.proxyPreferencesManager = proxyPreferencesManager;
    }

    @Override
    public void run() {
        FileChooser fc = new FileChooser();
        fc.setTitle("Save tool config files");
        fc.getExtensionFilters().add(FileUtils.ZIP_EXTENSION_FILTER);
        fc.setInitialFileName(experiment.getName() + FileUtils.ZIP_FILE_EXTENSION);


        if (jFedPreferences.containsPreference(GuiPreferenceKey.PREF_RSPECFOLDER_REQUEST))
            fc.setInitialDirectory(new File(jFedPreferences.getString(GuiPreferenceKey.PREF_RSPECFOLDER_REQUEST)));
        else if (System.getProperty("user.home") != null)
            fc.setInitialDirectory(new File(System.getProperty("user.home")));

        File file = fc.showSaveDialog(parentWindow);

        if (file == null)
            return;


        //save folder in which the file was saved for future file selector dialogs
        jFedPreferences.setString(GuiPreferenceKey.PREF_RSPECFOLDER_REQUEST,
                file.getParentFile().getAbsolutePath());

        if (!file.getAbsolutePath().endsWith(FileUtils.ZIP_FILE_EXTENSION))
            file = new File(file.getAbsolutePath() + FileUtils.ZIP_FILE_EXTENSION);

        Task<Void> saveTask = createSaveToolConfigurationFilesTask(file);
        taskService.submitTask(saveTask);
    }

    private Task<Void> createSaveToolConfigurationFilesTask(File file) {
        return new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                updateMessage(String.format("Writing tool configuration files to '%s'", file.getAbsolutePath()));

                //fetching public and private key
                assert geniUserProvider.isUserLoggedIn();
                GeniUser loggedInUser = geniUserProvider.getLoggedInGeniUser();
                assert loggedInUser != null;
                PrivateKey privateKey = loggedInUser.getPrivateKey();
                PublicKey publicKey = loggedInUser.getPublicKey();
                String passphrase = null; //will only be used when converting from PuTTY PPK to PEM
                final SshKeyInfo overriddenSshKeyInfo = jFedPreferences.getCustomSshKeyInfo();
                final GeniUser geniUser = loggedInUser;
                final SshKeyInfo usedSshKeyInfo = overriddenSshKeyInfo != null ? overriddenSshKeyInfo : SshKeyInfoFactory.createGeniUserSshKeyInfo(geniUser);

                if (overriddenSshKeyInfo != null) {
                    publicKey = overriddenSshKeyInfo.getPublicKey();

                    if (overriddenSshKeyInfo instanceof PreferencesPuttySshKeyInfo) {
                        PreferencesPuttySshKeyInfo preferencesPuttySshKeyInfo =
                                (PreferencesPuttySshKeyInfo) overriddenSshKeyInfo;

                        PuTTYPrivateKeyFile ppk = preferencesPuttySshKeyInfo.getPPK();

                        assert ppk != null;

                        if (ppk.isEncrypted()) {
                            //ask user for password
                            boolean success = false;
                            boolean userCancelled = false;

                            while (!success && !userCancelled) {
                                passphrase = FXPlatformUtil.runAndWait(this::getPrivateKeyPassword);

                                if (passphrase != null) {
                                    try {
                                        success = ppk.unlock(passphrase);
                                    } catch (RuntimeException ex) {
                                        LOG.warn("Error while unlocking ppk: {}. Incorrect passphrase?",
                                                ex.getMessage(), ex);
                                    }
                                } else {
                                    userCancelled = true;
                                }
                            }
                            if (success)
                                privateKey = ppk.getPrivateKey();
                            else
                                privateKey = null;
                        } else {
                            privateKey = overriddenSshKeyInfo.getPrivateKey();
                        }
                    } else {
                        privateKey = overriddenSshKeyInfo.getPrivateKey();
                    }
                }

                if (privateKey == null) {
                    Platform.runLater(() -> {
                        Alert alert = new Alert(Alert.AlertType.WARNING);
                        alert.setHeaderText("Private key could not be saved");
                        alert.setContentText("The private key is unavailable, and could not be included into the zip-file.\n" +
                                "You will have to provide it yourself.");
                        alert.show();
                    });
                    LOG.error("No private key available to include into the zip file!");
                }

                if (publicKey == null) {
                    Platform.runLater(() -> {
                        Alert alert = new Alert(Alert.AlertType.WARNING);
                        alert.setHeaderText("Public key could not be saved");
                        alert.setContentText("The public key is unavailable, and could not be included into the zip-file.\n" +
                                "You will have to provide it yourself.");
                        alert.show();
                    });
                    LOG.error("No public key available to include into the zip file!");
                }

                JFedConnection.ProxyInfo proxyInfo =
                            proxyPreferencesManager.getSshProxySettings(
                                    loggedInUser.getUserAuthorityServer().getTestbed().getProxies(),
                                    loggedInUser);
                JFedConnection.SshProxyInfo sshProxyInfo =
                        proxyInfo != null && proxyInfo instanceof JFedConnection.SshProxyInfo ?
                                (JFedConnection.SshProxyInfo) proxyInfo :
                                null;

                AnsibleFileWriter ansibleWriter = AnsibleFileWriter.createWithCopiedPrivateKey(
                        new BasicStringRspec(experiment.getSlice().getManifestRspec().getRspecXmlString()),
                        privateKey,
                        publicKey,
                        loggedInUser,
                        sshProxyInfo,
                        null /*preferredUser*/);
                updateMessage(ansibleWriter.writeFilesToZip(file));

                return null;
            }


            @Override
            protected void failed() {
                LOG.error("Error while writing tool configuration files", getException());

                Platform.runLater(() -> new Alert(Alert.AlertType.ERROR,
                        String.format("Error while saving tool configuration files to '%s':\n%s",
                                file.getAbsolutePath(), getException().getLocalizedMessage()))
                        .show());
                updateMessage("Error while writing tool configuration files");
            }

            private String getPrivateKeyPassword() {
                PasswordInputDialog dialog = new PasswordInputDialog();
                dialog.setHeaderText("Private key conversion");
                dialog.setContentText("Please enter the password of your private key:");

                return dialog.showAndWait().orElse(null);
            }


        };
    }
}
