package be.iminds.ilabt.jfed.experimenter_gui.preferences.subsections;

import be.iminds.ilabt.jfed.experimenter_gui.util.ui.TextFieldWithStatus;
import be.iminds.ilabt.jfed.lowlevel.GeniUserProvider;
import be.iminds.ilabt.jfed.preferences.GuiPreferenceKey;
import be.iminds.ilabt.jfed.preferences.JFedGuiPreferences;
import be.iminds.ilabt.jfed.ui.javafx.dialogs.JFDialogs;
import be.iminds.ilabt.jfed.ui.javafx.util.InformationLabel;
import be.iminds.ilabt.jfed.util.IOUtils;
import be.iminds.ilabt.jfed.util.KeyUtil;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.stage.FileChooser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

public class UnixSshAuthenticationPane extends SshAuthenticationPane {
    private final static Logger LOG = LoggerFactory.getLogger(UnixSshAuthenticationPane.class);

    @FXML protected TextFieldWithStatus pubKeyFileTextField;
    @FXML protected Button pubKeyBrowseButton;
    @FXML protected InformationLabel warnNoLoginKeyLabel;
    @FXML protected Label pubKeyLabel;
    @FXML protected Label privateKeyLabel;
    @FXML protected InformationLabel pubKeyInPrivKeyLabel;
    @FXML protected TextArea pubKeyInPrivKeyField;
    @FXML
    private BorderPane root;

    @Override
    public Node getRoot() {
        return root;
    }
    @Inject
    UnixSshAuthenticationPane(GeniUserProvider geniUserProvider, JFedGuiPreferences jFedPreferences) {
        super(geniUserProvider, jFedPreferences);

    }

    @FXML
    protected void initialize() {
        super.initialize();
        File pubKeyFile = jFedPreferences.getFile(GuiPreferenceKey.PREF_SSH_PUBLIC_KEY_FILE);
        if (pubKeyFile != null)
            pubKeyFileTextField.setText(pubKeyFile.getAbsolutePath());
        else
            pubKeyFileTextField.setText("");


        pubKeyFileTextField.disableProperty().bind(useCustomKeyPairCheckBox.selectedProperty().not());
        pubKeyBrowseButton.disableProperty().bind(useCustomKeyPairCheckBox.selectedProperty().not());
        pubKeyInPrivKeyField.disableProperty().bind(useCustomKeyPairCheckBox.selectedProperty().not());
        pubKeyInPrivKeyLabel.disableProperty().bind(useCustomKeyPairCheckBox.selectedProperty().not());
        privateKeyLabel.disableProperty().bind(useCustomKeyPairCheckBox.selectedProperty().not());
        pubKeyLabel.disableProperty().bind(useCustomKeyPairCheckBox.selectedProperty().not());

        warnNoLoginKeyLabel.managedProperty().bind(warnNoLoginKeyLabel.visibleProperty());
        pubKeyFileTextField.managedProperty().bind(pubKeyFileTextField.visibleProperty());
        pubKeyLabel.managedProperty().bind(pubKeyLabel.visibleProperty());
        pubKeyBrowseButton.managedProperty().bind(pubKeyBrowseButton.visibleProperty());
        pubKeyInPrivKeyLabel.managedProperty().bind(pubKeyInPrivKeyLabel.visibleProperty());
        pubKeyInPrivKeyField.managedProperty().bind(pubKeyInPrivKeyField.visibleProperty());

        pubKeyBrowseButton.visibleProperty().bind(pubKeyFileTextField.visibleProperty());

        //uncomment the next line to show the public key. This works fine, but the layout of dialog gets messed up :-(
        //pubKeyInPrivKeyField.visibleProperty().bind(pubKeyInPrivKeyLabel.visibleProperty());

        pubKeyFileTextField.setVisible(false);
        pubKeyInPrivKeyLabel.setVisible(false);

        testPublicKey();
    }

    @FXML
    @Override
    protected void onBrowsePrivateKeyButtonAction(ActionEvent actionEvent) {
        FileChooser fc = new FileChooser();
        fc.setTitle("Select your Private Key");
        if (getLoggedInUserPrivateKeyFile() != null)
            fc.setInitialDirectory(getLoggedInUserPrivateKeyFile().getParentFile());

        File selectedFile = fc.showOpenDialog(fileTextField.getScene().getWindow());
        if (selectedFile != null) {
            fileTextField.setText(selectedFile.getAbsolutePath());
            pubKeyFileTextField.setText("");

            testPrivateKey();
        }

    }

    @Override
    protected boolean testPrivateKey() {
        boolean validPrivateKey =  super.testPrivateKey();
        boolean needPublicKey = showPublicKeyFieldIfNeeded(); //don't remove! mainly here for side effect!

        if (validPrivateKey && needPublicKey && pubKeyFileTextField.getText().isEmpty()) {
            //guess public key automatically
            File privKeyFile = new File(fileTextField.getText());
            String privKeyFileName = privKeyFile.getAbsolutePath();
            String pubKeyFileName = privKeyFileName + ".pub";
            File pubKeyFile = new File(pubKeyFileName);
            if (pubKeyFile.exists() && pubKeyFile.isFile()) {
                try {
                    if (KeyUtil.isOpenSshRsaKey(pubKeyFile)) {
                        pubKeyFileTextField.setText(pubKeyFileName);
                        testPublicKey();
                    }
                } catch (IOException e) {
                    LOG.debug("Opening automatically guessed public key file threw an error: " + e.getMessage(), e);
                    //ignore
                }
            }
        }
        return validPrivateKey;
    }



    /**
     * returns true if valid public key is needed, false if public key is not required.
     * valid public key is only needed if:
     * - a valid private key is already entered
     * - this private key does not contain a public key inside
     */
    protected boolean showPublicKeyFieldIfNeeded() {
        File privKeyFile = new File(fileTextField.getText());
        if (testPrivateKeyIncludesPublicKey(privKeyFile)) {
            pubKeyFileTextField.setVisible(false);
            pubKeyLabel.setVisible(true);
            pubKeyInPrivKeyLabel.setVisible(true);
            pubKeyInPrivKeyField.setVisible(true);

            try {
                String privateKeyText = IOUtils.fileToString(privKeyFile);
                RSAPrivateKey privKey = KeyUtil.pemToRsaPrivateKey(privateKeyText, null);
                RSAPublicKey pubKey = KeyUtil.rsaPrivateKeyToRsaPublicKey(privKey);
                String pubKeyOpenSshString = KeyUtil.rsaPublicKeyToOpenSshAuthorizedKeysFormat(pubKey);

                pubKeyInPrivKeyField.setText(pubKeyOpenSshString);
            } catch (KeyUtil.PEMDecodingException | IOException e) {
                LOG.error("Could not extract public key file from private, while it should be possible! " + e.getMessage(), e);
                pubKeyInPrivKeyField.setText("Something went wrong trying to get the public key: " + e.getMessage());
            }
            return false;
        } else {
            try {
                pubKeyLabel.setVisible(true);
                pubKeyInPrivKeyLabel.setVisible(false);
                pubKeyInPrivKeyField.setVisible(false);

                pubKeyInPrivKeyLabel.setVisible(false);

                if (testPrivateKeyFile(privKeyFile)) {
                    pubKeyFileTextField.setVisible(true);
                    return true;
                } else {
                    pubKeyFileTextField.setVisible(false);
                    return false;
                }
            } catch (IOException e) {
                pubKeyFileTextField.setVisible(false);
                return false;
            }
        }
    }

    protected boolean testPrivateKeyIncludesPublicKey(File file) {
        if (file.exists() && file.isFile()) {
            try {
                //check if it is an putty private key
                BufferedReader reader = new BufferedReader(new FileReader(file));
                StringBuilder fileContents = new StringBuilder();

                String line;
                while ((line = reader.readLine()) != null) {
                    fileContents.append(line);
                }

                reader.close();

                boolean isRsa = KeyUtil.hasRsaPrivateKey(fileContents.toString());
                boolean isEncrypted = KeyUtil.hasEncryptedRsaPrivateKey(fileContents.toString());

                return isRsa && !isEncrypted;
            } catch (IOException e) {
                LOG.error("Error reading private key file (will be ignored here): " + e.getMessage(), e);
                return false;
            }
        } else
            return false;
    }

    @Override
    protected boolean testPrivateKeyFile(File file) throws IOException {
        if (file.exists() && file.isFile()) {
            //check if it is an putty private key
            BufferedReader reader = new BufferedReader(new FileReader(file));
            StringBuilder fileContents = new StringBuilder();

            String line;
            while ((line = reader.readLine()) != null) {
                fileContents.append(line);
            }

            reader.close();


            return KeyUtil.hasAnyPrivateKey(fileContents.toString());
        } else
            return false;
    }


    protected boolean testPublicKeyFile(File file) throws IOException {
        return KeyUtil.isOpenSshRsaKey(IOUtils.fileToString(file));

    }

    @FXML
    protected void onPubKeyBrowseButtonAction(ActionEvent actionEvent) {
        FileChooser fc = new FileChooser();
        fc.setTitle("Select your Public Key");
        fc.setInitialDirectory(getLoggedInUserPrivateKeyFile().getParentFile());


        File selectedFile = fc.showOpenDialog(pubKeyFileTextField.getScene().getWindow());
        if (selectedFile != null) {
            pubKeyFileTextField.setText(selectedFile.getAbsolutePath());
            testPublicKey();
        }

    }

    protected void testPublicKey() {
        boolean needPublicKey = showPublicKeyFieldIfNeeded();
        if (!needPublicKey) return;

        if (pubKeyFileTextField.getText().length() == 0) {
            pubKeyFileTextField.setStatus(TextFieldWithStatus.Status.NONE);
            return;
        }

        try {
            if (testPublicKeyFile(new File(pubKeyFileTextField.getText()))) {
                pubKeyFileTextField.setStatus(TextFieldWithStatus.Status.OK);
            } else {
                JFDialogs.create()
                        .owner(pubKeyFileTextField.getScene().getWindow())
                        .message(" Please select a valid public key.")
                        .masthead("The provided key is not valid.")
                        .title("Invalid key file")
                        .showWarning();
                pubKeyFileTextField.setStatus(TextFieldWithStatus.Status.ERROR);
            }
        } catch (IOException ex) {
            if (pubKeyFileTextField != null &&
                    pubKeyFileTextField.getScene() != null
                    && pubKeyFileTextField.getScene().getWindow() != null)
            JFDialogs.create()
                    .owner(pubKeyFileTextField.getScene().getWindow())
                    .message("An error occured while processing the key file")
                    .masthead("Error")
                    .title("Error")
                    .showException(ex);

            pubKeyFileTextField.setStatus(TextFieldWithStatus.Status.ERROR);
        }
    }

    @Override
    public boolean check() {
        File privKeyFile = new File(fileTextField.getText());
        boolean privKeyFileIncludesPubKey = testPrivateKeyIncludesPublicKey(privKeyFile);


        if (useCustomKeyPairCheckBox.isSelected() && !privKeyFileIncludesPubKey && pubKeyFileTextField.getStatus() != TextFieldWithStatus.Status.OK) {
            JFDialogs.create()
                    .owner(root)
                    .message("The provided public key file is not valid")
                    .showError();
            return false;
        }

        return super.check();

    }

    @Override
    public boolean save() {
        File privKeyFile = new File(fileTextField.getText());
        boolean privKeyFileIncludesPubKey = testPrivateKeyIncludesPublicKey(privKeyFile);

        //only continue to save if parent-save is also OK
        if (!super.save())
            return false;

        if (useCustomKeyPairCheckBox.isSelected() && !privKeyFileIncludesPubKey && pubKeyFileTextField.getStatus() == TextFieldWithStatus.Status.OK) {
            File pubKeyFile = new File(pubKeyFileTextField.getText());

            jFedPreferences.setFile(GuiPreferenceKey.PREF_SSH_PUBLIC_KEY_FILE, pubKeyFile);
        } else {
            jFedPreferences.clear(GuiPreferenceKey.PREF_SSH_PUBLIC_KEY);
        }

        return true;
    }
}
