package be.iminds.ilabt.jfed.experimenter_gui.util;

import be.iminds.ilabt.jfed.experimenter_gui.TriggeredMessageHandler;
import be.iminds.ilabt.jfed.experimenter_gui.preferences.PreferencesDialogFactory;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.TriggeredMessage;
import be.iminds.ilabt.jfed.highlevel.model.Slice;
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.JFedGuiPreferences;
import be.iminds.ilabt.jfed.preferences.JFedPreferences;
import be.iminds.ilabt.jfed.rspec.model.BasicStringRspec;
import be.iminds.ilabt.jfed.rspec.model.BasicStringRspec.LoginService;
import be.iminds.ilabt.jfed.rspec.model.javafx_impl.FXRspecNode;
import be.iminds.ilabt.jfed.rspec.rspec_source.ManifestRspecSource;
import be.iminds.ilabt.jfed.ssh_terminal_tool.ExternalSshTerminal;
import be.iminds.ilabt.jfed.ui.javafx.dialogs.JFDialogs;
import be.iminds.ilabt.jfed.util.BestNodeLoginFinder;
import be.iminds.ilabt.jfed.util.ProxyServiceUtil;
import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Optional;

/**
 * User: twalcari
 * Date: 1/29/2015
 * Time: 16:13
 */
@Singleton
public class SSHTerminalUtil {
    private static final Logger LOG = LoggerFactory.getLogger(SSHTerminalUtil.class);

    private final JFedPreferences jFedPreferences;
    private final ExternalSshTerminal externalSshTerminal;
    private final PreferencesDialogFactory preferencesDialogFactory;
    private final GeniUserProvider geniUserProvider;
    private final ProxyServiceUtil proxyServiceUtil;
    private final TriggeredMessageHandler triggeredMessageHandler;

    @Inject
    public SSHTerminalUtil(JFedPreferences jFedPreferences, ExternalSshTerminal externalSshTerminal, PreferencesDialogFactory preferencesDialogFactory,
                           GeniUserProvider geniUserProvider, ProxyServiceUtil proxyServiceUtil, TriggeredMessageHandler triggeredMessageHandler) {
        this.jFedPreferences = jFedPreferences;
        this.externalSshTerminal = externalSshTerminal;
        this.preferencesDialogFactory = preferencesDialogFactory;
        this.geniUserProvider = geniUserProvider;
        this.proxyServiceUtil = proxyServiceUtil;
        this.triggeredMessageHandler = triggeredMessageHandler;
    }

    public void launchSSHTerminalForcedLocalUser(FXRspecNode rspecNode, ManifestRspecSource manifestRspecSource) {
        String username =
                geniUserProvider.getLoggedInGeniUser().getUserUrn().getEncodedResourceName();
        launchSSHTerminal(rspecNode, manifestRspecSource, username);
    }


    public void launchSSHTerminalForcedLocalUser(FXRspecNode rspecNode, Slice slice) {
        String username =
                geniUserProvider.getLoggedInGeniUser().getUserUrn().getEncodedResourceName();
        launchSSHTerminal(rspecNode, slice, username);
    }

    public void launchSSHTerminal(FXRspecNode rspecNode, Slice slice) {
        launchSSHTerminal(rspecNode, slice, null);
    }

    public void launchSSHTerminal(@Nonnull FXRspecNode rspecNode, @Nullable Slice slice, @Nullable String overrideUserName) {
        //get the associated manifest RSpec-node
        if (slice == null) {
            LOG.warn("Could not open SSH terminal: manifestRspecSource=null");
            JFDialogs.create()
                    .message("There is currently insufficient information available to login.")
                    .showError();
        } else {
            launchSSHTerminal(rspecNode, slice.getManifestRspec(), overrideUserName);
        }
    }


    public void launchSSHTerminal(FXRspecNode rspecNode, ManifestRspecSource manifestRspecSource) {
        launchSSHTerminal(rspecNode, manifestRspecSource, null);
    }

    /**
     * The rspecNode is NOT necessarily a part of the provided manifestRspecSource. It can be that the rspecNode is from the RequestRspec.
     * <p>
     * We first have to match the rspecNode to the corresponding node in the manifstRspecSource!
     */
    public void launchSSHTerminal(@Nonnull FXRspecNode rspecNode, @Nullable ManifestRspecSource manifestRspecSource,
                                  @Nullable String overrideUsername) {
        //get the associated manifest RSpec-node
        if (manifestRspecSource == null) {
            LOG.warn("launchSSHTerminal: Could not open SSH terminal: manifestRspecSource=null");
            JFDialogs.create()
                    .message("There is currently insufficient information available to login.")
                    .showError();
            return;
        }

        LOG.warn("launchSSHTerminal called with rspecNode "+rspecNode.getUniqueId()+" overrideUsername="+overrideUsername);

        BestNodeLoginFinder bestNodeLoginFinder = new BestNodeLoginFinder(
                manifestRspecSource.getStringRspec(),
                overrideUsername /*preferred username*/,    //we will prefer to find the overrideUsername, but if it is not found, this will return something else., but not use that username later.
                geniUserProvider.getLoggedInGeniUser() /*for finding logged in username*/,
                new BestNodeLoginFinder.Feedback() {
                    @Override public void info(String s) { LOG.info("launchSSHTerminal: "+s); }
                    @Override public void error(String s) { LOG.error("launchSSHTerminal: "+s); }
                    @Override public void debug(String s) { LOG.debug("launchSSHTerminal: "+s); }
                }
        );
        Optional<LoginService> best = Optional.ofNullable(bestNodeLoginFinder.findBestLogin(rspecNode.getUniqueId()));

        if (best.isPresent()) {
            LOG.debug("launchSSHTerminal: found login service for user "+best.get().getUsername());

            LoginService bestLoginService = best.get();

            if(bestLoginService.getSshProxy() != null && bestLoginService.getSshProxy().getSshKeyInfo() == null){
                LOG.warn("Found proxy info with empty SshKeyInfo. Replacing with logged-in user SshKeyInfo");

                JFedGuiPreferences guiPrefs = jFedPreferences instanceof JFedGuiPreferences ? (JFedGuiPreferences) jFedPreferences : null;
                SshKeyInfo overriddenUserSshKey = guiPrefs == null ? null : guiPrefs.getCustomSshKeyInfo();

                final be.iminds.ilabt.jfed.lowlevel.GeniUser geniUser = geniUserProvider.getLoggedInGeniUser();
                final JFedConnection.SshProxyInfo newSshProxyInfo =
                        new JFedConnection.SshProxyInfo(bestLoginService.getSshProxy(),
                                overriddenUserSshKey != null ?
                                        overriddenUserSshKey :
                                        SshKeyInfoFactory.createGeniUserSshKeyInfo(geniUser));

                bestLoginService = new LoginService(bestLoginService.getAuthentication(),
                        bestLoginService.getHostname(),
                        bestLoginService.getPort(),
                        bestLoginService.getUsername(),
                        newSshProxyInfo);
            }

            //note: includes proxy info
            //note: if another username than overrideUsername is found, launchSSHTerminal will overwrite it with overrideUsername
            launchSSHTerminal(bestLoginService, overrideUsername);
            return;
        } else {
            LOG.warn("launchSSHTerminal: There are no loginServices available for {}", rspecNode.getClientId());
            JFDialogs.create()
                    .message("The selected node doesn't provide any information to perform an interactive login.")
                    .masthead("Could not open SSH terminal")
                    .title("Could not open SSH terminal")
                    .showException(new RuntimeException(
                            "node id=\"" + rspecNode.getUniqueId() + "\" "
                                    + manifestRspecSource.getStringRspec().getXmlString()
                    ));

            return;
        }
    }

    public void launchSSHTerminal(BasicStringRspec.LoginService loginService) {
        launchSSHTerminal(loginService, null);
    }

    private void launchSSHTerminal(@Nonnull LoginService loginService,
                                   @Nullable String overrideUsername) {
        Preconditions.checkNotNull(loginService);

        try {
            triggeredMessageHandler.onEvent(TriggeredMessage.GuiTrigger.SSH_TERMINAL_START);
        } catch (Exception e) {
            LOG.error("Error showing Triggered Message (will be ignored)", e);
        }

        if (!externalSshTerminal.isConfigured()) {
            LOG.warn("ExternalSsshTerminal is not configured. Showing warning dialog.");
            JFDialogs.create()
                    .message("jFed must be configured with the location of the terminal application and a valid Private Key.")
                    .showWarning();
            preferencesDialogFactory.showDialog();
        }
        if (!externalSshTerminal.isConfigured()) {
            JFDialogs.create()
                    .message("jFed was not properly configured. Please configure jFed first, and try again.")
                    .showError();
            return;
        }

        externalSshTerminal.launch(
                overrideUsername != null ? overrideUsername : loginService.getUsername(),
                loginService.getHostname(),
                loginService.getPort(),
                loginService.getSshProxy());

    }


}
