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

import be.iminds.ilabt.jfed.experimenter_gui.preferences.AbstractPreferencesSubPane;
import be.iminds.ilabt.jfed.experimenter_gui.ui.PasswordInputDialog;
import be.iminds.ilabt.jfed.lowlevel.GeniUser;
import be.iminds.ilabt.jfed.lowlevel.GeniUserProvider;
import be.iminds.ilabt.jfed.util.GeniUrn;
import be.iminds.ilabt.jfed.util.IOUtils;
import be.iminds.ilabt.jfed.util.KeyUtil;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.stage.FileChooser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Optional;

/**
 * LoginPane
 */
public class LoginPane extends AbstractPreferencesSubPane {
    private final static Logger LOG = LoggerFactory.getLogger(LoginPane.class);
    private static final String PKCS12_FILE_EXTENSION = ".p12";
    private static final String PEM_FILE_EXTENSION = ".pem";

    @FXML
    private Button pkcs12Button;
    @FXML
    private Button pemButton;
    @FXML
    private TextField urnTextField;

    @FXML
    private BorderPane root;

    @Override
    public Node getRoot() {
        return root;
    }

    private final GeniUserProvider geniUserProvider;

    @Inject
    LoginPane(GeniUserProvider geniUserProvider) {
        super("User Details", false/*advanced*/);
        this.geniUserProvider = geniUserProvider;
    }

    @FXML
    private void initialize() {
        urnTextField.setText(this.geniUserProvider.getLoggedInGeniUser().getUserUrnString());
    }

    @FXML
    protected void onSavePKCS12Action() {
        assert (geniUserProvider.isUserLoggedIn());

        final GeniUser user = geniUserProvider.getLoggedInGeniUser();
        final GeniUrn userUrn = user.getUserUrn();
        final String niceName = userUrn.getEncodedTopLevelAuthority() + "_" + userUrn.getEncodedResourceName();

        Path defaultPath = Paths.get(System.getProperty("user.home"), niceName + PKCS12_FILE_EXTENSION);

        FileChooser fc = new FileChooser();
        fc.setInitialFileName(defaultPath.toString());
        fc.setInitialDirectory(new File(System.getProperty("user.home")));
        fc.getExtensionFilters().add(new FileChooser.ExtensionFilter("PKCS #12 file (*" + PKCS12_FILE_EXTENSION + ")", "*" + PKCS12_FILE_EXTENSION));
        fc.setTitle("Export your credentials as a PKCS #12 certificate");

        File pemFile = fc.showSaveDialog(getRoot().getScene().getWindow());

        if (pemFile == null) {
            LOG.debug("User cancelled file picker dialog while saving PKCS#12");
            return;
        }

        PasswordInputDialog pid = new PasswordInputDialog();
        pid.setHeaderText("Please enter a password to secure your PKCS#12-file:");
        pid.setTitle("Secure your PKCS#12-file");
        Optional<String> password = pid.showAndWait();

        if (!password.isPresent()) {
            LOG.debug("User cancelled password dialog while saving PKCS#12");
            return;
        }

        if (password.get().trim().isEmpty()) {
            new Alert(Alert.AlertType.ERROR, "You must specify a password to secure your PKCS#12-file!").show();
            return;
        }

        if (savePKCS12File(pemFile, password.get())) {
            new Alert(Alert.AlertType.INFORMATION, "Your PKCS#12 key was succesfully saved to " + pemFile.getAbsolutePath()).show();
        } else {
            new Alert(Alert.AlertType.ERROR, "A problem occurred while saving your certificate. Please try again!").show();
        }
    }

    private boolean savePKCS12File(File pkcs12File, String passwordString) {
        assert (geniUserProvider.isUserLoggedIn());

        final GeniUser user = geniUserProvider.getLoggedInGeniUser();
        final GeniUrn userUrn = user.getUserUrn();
        final String niceName = userUrn.getEncodedTopLevelAuthority() + "_" + userUrn.getEncodedResourceName();

        //build the PKCS12-file
        final PrivateKey pk = user.getPrivateKey();
        final List<X509Certificate> certs = user.getClientCertificateChain();
        final Certificate[] chain = new Certificate[certs.size()];
        for (int i = 0; i < certs.size(); i++)
            chain[i] = certs.get(i);


        char[] keyStorePass = passwordString.toCharArray();
        KeyStore keyStore;
        try {
            keyStore = KeyStore.getInstance("jks");
            keyStore.load(null/*inputstream*/, null/*pass*/);
            keyStore.setKeyEntry(niceName, pk, keyStorePass/*pass*/, chain);

            byte[] pkcs12 = KeyUtil.keyStoreToPKCS12Bytes(keyStore, passwordString, passwordString);
            IOUtils.byteArrayToFile(pkcs12File, pkcs12);

            LOG.info("Saved user login as PKCS12 to " + pkcs12File + "  (" + pkcs12.length + " bytes)");

            return true;

        } catch (Exception e) {
            LOG.error("Failed to convert and save PK12: " + e.getMessage(), e);
            return false;
        }
    }

    @FXML
    protected void onSavePEMAction() {

        assert (geniUserProvider.isUserLoggedIn());

        final GeniUser user = geniUserProvider.getLoggedInGeniUser();
        final GeniUrn userUrn = user.getUserUrn();
        final String niceName = userUrn.getEncodedTopLevelAuthority() + "_" + userUrn.getEncodedResourceName();

        Path defaultPath = Paths.get(System.getProperty("user.home"), niceName + PEM_FILE_EXTENSION);


        FileChooser fc = new FileChooser();
        fc.setInitialFileName(defaultPath.toString());
        fc.setInitialDirectory(new File(System.getProperty("user.home")));
        fc.getExtensionFilters().add(new FileChooser.ExtensionFilter("PEM file (*" + PEM_FILE_EXTENSION + ")", "*" + PEM_FILE_EXTENSION));
        fc.setTitle("Export your credentials as a PEM certificate");

        File pemFile = fc.showSaveDialog(getRoot().getScene().getWindow());

        if (pemFile == null) {
            LOG.debug("User cancelled file picker dialog while saving PEM");
            return;
        }

        PasswordInputDialog pid = new PasswordInputDialog();
        pid.setHeaderText("Please enter a password to secure your PEM-file:");
        pid.setTitle("Secure your PEM-file");
        Optional<String> password = pid.showAndWait();


        if (!password.isPresent()) {
            LOG.debug("User cancelled password dialog while saving PEM");
            return;
        }

        if (password.get().trim().isEmpty()) {
            new Alert(Alert.AlertType.ERROR, "You must specify a password to secure your PEM-file!").show();
            return;
        }


        if (savePEMFile(pemFile, password.get())) {
            new Alert(Alert.AlertType.INFORMATION, "Your PEM key was succesfully saved to " + pemFile.getAbsolutePath()).show();
        } else {
            new Alert(Alert.AlertType.ERROR, "A problem occurred while saving your certificate. Please try again!").show();
        }
    }

    private boolean savePEMFile(File pemFile, String passwordString) {
        assert (geniUserProvider.isUserLoggedIn());

        final GeniUser user = geniUserProvider.getLoggedInGeniUser();

        //build the PKCS12-file
        try {
            final PrivateKey pk = user.getPrivateKey();
            final List<X509Certificate> certs = user.getClientCertificateChain();

            char[] pemKey = KeyUtil.privateKeyToAnyPem(pk, passwordString.toCharArray());
            String pemCerts = KeyUtil.x509certificateChainToPem(certs);
            String pem = new String(pemKey) + "" + pemCerts;
            IOUtils.stringToFile(pemFile, pem);


            LOG.info("Saved user login as pem to " + pemFile + "  (" + pem.length() + " chars)");

            return true;

        } catch (Exception e) {
            LOG.error("Failed to convert and save PEM: " + e.getMessage(), e);
            return false;
        }
    }


    @Override
    public boolean check() {
        return true;
    }

    @Override
    public boolean save() {
        return true;
    }
}
