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

import be.iminds.ilabt.jfed.experiment.CapabilityChecker;
import be.iminds.ilabt.jfed.experiment.ExperimentController;
import be.iminds.ilabt.jfed.experiment.ExperimentPart;
import be.iminds.ilabt.jfed.experiment.SfaExperimentPart;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.highlevel.model.SfaModel;
import be.iminds.ilabt.jfed.highlevel.model.Slice;
import be.iminds.ilabt.jfed.highlevel.model.UserSpecHelper;
import be.iminds.ilabt.jfed.lowlevel.GeniUserProvider;
import be.iminds.ilabt.jfed.lowlevel.api.user_spec.UserSpec;
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.rspec.model.ModelRspecType;
import be.iminds.ilabt.jfed.rspec.model.javafx_impl.FXModelRspec;
import be.iminds.ilabt.jfed.rspec.parser.extensions.UserSpecParser;
import be.iminds.ilabt.jfed.rspec.parser.extensions.UserSpecWriter;
import be.iminds.ilabt.jfed.ui.javafx.FXMLUtil;
import be.iminds.ilabt.jfed.ui.javafx.GlyphUtils;
import be.iminds.ilabt.jfed.ui.javafx.dialogs.JFDialogs;
import be.iminds.ilabt.jfed.util.KeyUtil;
import com.google.common.base.Joiner;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Window;
import org.controlsfx.glyphfont.FontAwesome;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

/**
 */
public class EditSshKeysDialog extends VBox {
    private static final Logger LOG = LoggerFactory.getLogger(EditSshKeysDialog.class);

    private final Window parentWindow;
    private final Slice slice;
    private final ExperimentController experimentController;
    private final boolean hasSupportingAuthorities;

    private final GeniUserProvider geniUserProvider;
    private final SfaModel sfaModel;
    private final JFedGuiPreferences jFedPreferences;

    private final CapabilityChecker capabilityChecker;


    @FXML
    private TextArea xmlUsersField;

    @FXML
    private Label informationLabel;

    @FXML
    private Button fetchSshKeysButton;

    @FXML
    private ProgressBar fetchSshKeysProgressBar;

    private final Dialog<ButtonType> dialog;

    EditSshKeysDialog(Window parentWindow, ExperimentController experimentController,
                      GeniUserProvider geniUserProvider, SfaModel sfaModel, JFedGuiPreferences jFedPreferences
            , CapabilityChecker capabilityChecker) {
        this.parentWindow = parentWindow;
        this.experimentController = experimentController;
        this.geniUserProvider = geniUserProvider;
        this.sfaModel = sfaModel;
        this.jFedPreferences = jFedPreferences;
        this.capabilityChecker = capabilityChecker;
        this.slice = this.experimentController.getExperiment().getSlice();

        //initialize the scene and dialog
        FXMLUtil.injectFXML(this);

        setVgrow(xmlUsersField, Priority.ALWAYS);

        dialog = new Dialog<>();
        dialog.initOwner(parentWindow);
        dialog.setTitle("Edit SSH-keys");
        dialog.setHeaderText("Edit SSH-keys");
        dialog.setGraphic(GlyphUtils.createDialogGlyph(FontAwesome.Glyph.GROUP, Color.BLUE));
        dialog.getDialogPane().setContent(this);

        ButtonType editSSHKeysButtonType = new ButtonType("Edit SSH Keys", ButtonBar.ButtonData.OK_DONE);

        dialog.getDialogPane().getButtonTypes().setAll(editSSHKeysButtonType, ButtonType.CANCEL);

        Node editSSHKeysButton = dialog.getDialogPane().lookupButton(editSSHKeysButtonType);
        editSSHKeysButton.addEventFilter(ActionEvent.ACTION, event -> {
            if (!isValidInput())
                event.consume();
        });


        dialog.resultProperty().addListener(observable -> {
            if (dialog.getResult() == editSSHKeysButtonType)
                onEditSshKeysAction();
        });


        //check which authorities support editing keys
        List<String> unsupportedAuthorityNames = new ArrayList<>();
        boolean supported = false;
        for (ExperimentPart experimentPart : experimentController.getExperiment().getParts()) {
            if (capabilityChecker.supportsEditSSHKeys(experimentPart)) {
                supported = true;
            } else {
                boolean showInHelpMessage = true;

                if (experimentPart instanceof SfaExperimentPart
                        && ((SfaExperimentPart) experimentPart).getConnectSfaAuthority().isEdgeVlan())
                    showInHelpMessage = false;

                if (showInHelpMessage)
                    unsupportedAuthorityNames.add(experimentPart.getName());
            }
        }

        this.hasSupportingAuthorities = supported;
        if (!unsupportedAuthorityNames.isEmpty()) {
            this.informationLabel.setText(
                    this.informationLabel.getText() +
                            "\n- " + Joiner.on(", ").join(unsupportedAuthorityNames)
                            + (unsupportedAuthorityNames.size() == 1 ? " does" : " do")
                            + " not support editing SSH-keys");

        }

        String initialXml = constructInitialSshKeysXml();
        xmlUsersField.setText(initialXml);

        //check whether the "Fetch SSH keys" button should be visible
        fetchSshKeysButton.managedProperty().bind(fetchSshKeysButton.visibleProperty());
        Server userAuthority =
                this.geniUserProvider.getLoggedInGeniUser().getUserAuthorityServer();
        boolean canRequestProjectMemberSshKeys =
                userAuthority.hasFlag(Server.Flag.featureFedMaRequestMemberSshKey);

//        boolean canRequestProjectMemberSshKeys = false;
//        //For now we will fix it to GPO
//        if (userAuthority.getDefaultComponentManagerAsGeniUrn().getEncodedTopLevelAuthority_withoutSubAuth().equals("ch.geni.net"))
//            canRequestProjectMemberSshKeys = true;

        fetchSshKeysButton.setVisible(canRequestProjectMemberSshKeys);

        fetchSshKeysProgressBar.visibleProperty().bind(fetchSshKeysButton.disabledProperty());

    }


    private String constructInitialSshKeysXml() {
        try {
            if (sfaModel.getUserKeys() == null || sfaModel.getUserKeys().isEmpty()) {
                //no SSH keys for the logged in user are known.
                //register at least user configured key in preferencesSshKeyInfo sshKeyInfo;
                SshKeyInfo sshKeyInfo;
                SshKeyInfo overrideSshKeyInfo = jFedPreferences.getCustomSshKeyInfo();
                if (overrideSshKeyInfo != null) {
                    LOG.info("ExternalSshTerminal: The preferences are overriding the SSH key used (" + overrideSshKeyInfo.getClass().getName() + ").");
                    sshKeyInfo = overrideSshKeyInfo;
                } else {
                    LOG.info("ExternalSshTerminal: The user's SSH key will be used.");
                    final be.iminds.ilabt.jfed.lowlevel.GeniUser geniUser = geniUserProvider.getLoggedInGeniUser();
                    sshKeyInfo = SshKeyInfoFactory.createGeniUserSshKeyInfo(geniUser);
                }

                String pemPublicKey = KeyUtil.publicKeyToOpenSshAuthorizedKeysFormat(sshKeyInfo.getPublicKey());
                sfaModel.addUserKey(pemPublicKey);
            }

            List<UserSpec> userSpecs =
                    UserSpecHelper.getMergedUserSpecs(geniUserProvider, sfaModel,
                            (FXModelRspec)slice.getRequestRspec().getModelRspec(ModelRspecType.FX));
            return userSpecToXml(userSpecs);
        } catch (Exception e) {
            LOG.error("Something went wrong trying to get initialXml for EditSshKeysDialog: " + e.getMessage(), e);
            return
                    "<jfed-ssh-keys:user-ssh-keys user=\"urn:publicid:IDN+example.com+user+tester\">\n" +
                            "    <jfed-ssh-keys:sshkey>ssh-rsa key1</jfed-ssh-keys:sshkey>\n" +
                            "    <jfed-ssh-keys:sshkey>ssh-rsa-key2</jfed-ssh-keys:sshkey>\n" +
                            "</jfed-ssh-keys:user-ssh-keys>\n";
        }
    }

    public void showDialog() {
        if (!hasSupportingAuthorities()) {
            LOG.debug("No authorities support editing SSH-keys. Showing info message");

            JFDialogs.create()
                    .owner(parentWindow)
                    .message("This experiment doesn't contain any resources that support editing SSH-keys.")
                    .masthead("Editing SSH-keys is unavailable")
                    .showWarning();

            return;
        }

        dialog.show();
    }


    private List<UserSpec> xmlToUserSpec(String xml) {
        try {
            UserSpecParser userSpecParser = new UserSpecParser(xml);

            return userSpecParser.call();
        } catch (Exception e) {
            LOG.error("ERROR parsing XML RSpec: " + e.getMessage(), e);
            return null;
        }
    }

    private String userSpecToXml(List<UserSpec> userSpecList) {
        try {
            UserSpecWriter userSpecWriter = new UserSpecWriter(userSpecList);

            return userSpecWriter.call();
        } catch (Exception e) {
            LOG.error("ERROR parsing XML RSpec: " + e.getMessage(), e);
            return null;
        }
    }

    private boolean isValidInput() {
        String xmlUserSpecs = xmlUsersField.getText();
        List<UserSpec> userspec = xmlToUserSpec(xmlUserSpecs);

        if (userspec == null || userspec.isEmpty()) {
            JFDialogs.create()
                    .owner(parentWindow)
                    .message("The provided XML could not be parsed, or doesn't contain any SSH-keys. Please check your input.")
                    .masthead("Cannot update SSH-keys!")
                    .showError();
            return false;
        } else
            return true;
    }

    public void onEditSshKeysAction() {
        String xmlUserSpecs = xmlUsersField.getText();
        List<UserSpec> userspec = xmlToUserSpec(xmlUserSpecs);
        assert userspec != null && !userspec.isEmpty();

        experimentController.updateSshKeys(userspec);
    }

    public boolean hasSupportingAuthorities() {
        return hasSupportingAuthorities;
    }

    @FXML
    private void fetchSshKeys() {
        /* ALWAYS perform a new call to fetch all slice members.
           This is because the Edit SSH keys action is typically done right after a new member was added...  */

        fetchSshKeysButton.setDisable(true);

        ListenableFuture<Collection<UserSpec>> newUserSpecs = experimentController.fetchSliceMemberSshKeys();

        Futures.addCallback(newUserSpecs, new FutureCallback<Collection<UserSpec>>() {
            @Override
            public void onSuccess(@Nullable Collection<UserSpec> result) {
                if (result != null) {
                    Platform.runLater(() -> insertSshKeysIntoEditor(result));

                } else {
                    LOG.warn("Unexpected: got null as result from fetchSliceMemberSshKeys");
                }


                Platform.runLater(() -> fetchSshKeysButton.setDisable(false));
            }

            @Override
            public void onFailure(Throwable t) {
                LOG.error("Fetching SSH keys of slice members failed, skipping processing", t);

                Platform.runLater(() -> fetchSshKeysButton.setDisable(false));
            }
        });
    }

    private void insertSshKeysIntoEditor(Collection<UserSpec> newUserSpecs) {

        String xmlUserSpecs = xmlUsersField.getText();
        List<UserSpec> currentUserSpec = xmlToUserSpec(xmlUserSpecs);

        if (newUserSpecs == null) {
            Optional<ButtonType> answer = JFDialogs.create()
                    .owner(parentWindow)
                    .message("The current XML could not be parsed. Inserting new SSH keys will incur data loss. Do you want to continue?")
                    .masthead("Cannot update SSH-keys!")
                    .buttonTypes(ButtonType.YES, ButtonType.NO)
                    .showWarning();

            if (answer.orElse(ButtonType.NO) != ButtonType.YES) {
                return;
            }
        }

        List<UserSpec> userSpecs = UserSpecHelper.mergeUserSpecs(currentUserSpec, newUserSpecs);
        xmlUsersField.setText(userSpecToXml(userSpecs));

    }
}
