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

import be.iminds.ilabt.jfed.preferences.GuiPreferenceKey;
import be.iminds.ilabt.jfed.preferences.JFedGuiPreferences;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.HBox;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.controlsfx.dialog.ExceptionDialog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import java.io.IOException;
import java.net.CookieHandler;
import java.net.HttpCookie;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * User: twalcari
 * Date: 8/11/2015
 * Time: 15:06
 */
public class URLLoginController {
    private static final Logger LOG = LoggerFactory.getLogger(URLLoginController.class);

    public final static String GENI_DOWNLOAD_URL = "https://portal.geni.net/secure/kmcert.php";
    public final static String FED4FIRE_DOWNLOAD_URL = "https://authority.ilabt.iminds.be/getsslcert_jfed.php3";
    protected static final URI GENI_URI = URI.create("https://portal.geni.net/");

    @FXML
    private WebView loginWebView;

    @FXML
    private HBox loadingHBox;
    @FXML
    private ProgressBar loadingProgressBar;

    @FXML
    private Label loadingLabel;


    private final JFedGuiPreferences preferences;
    private final java.net.CookieManager manager = new java.net.CookieManager(null, null);

    private Stage stage = null;
    private String result = null;

    @Inject
    URLLoginController(JFedGuiPreferences preferences) {
        this.preferences = preferences;
        java.net.CookieHandler.setDefault(manager);
    }

    @FXML
    public void initialize() {
        loadingProgressBar.progressProperty().bind(
                loginWebView.getEngine().getLoadWorker().progressProperty());

        loadingHBox.visibleProperty().bind(
                loginWebView.getEngine().getLoadWorker().runningProperty());

        /*Cookies need to be set after the webview has initialized, and before making the first call
         see  http://stackoverflow.com/questions/14385233/setting-a-cookie-using-javafxs-webengine-webview */
        if (preferences.containsPreference(GuiPreferenceKey.PREF_GENI_LOGIN_COOKIE)) {
            try {
                final String cookie = preferences.getString(GuiPreferenceKey.PREF_GENI_LOGIN_COOKIE);
                CookieHandler.getDefault().put(GENI_URI,
                        Collections.singletonMap("Set-Cookie", Collections.singletonList(cookie)));
            } catch (IOException e) {
                LOG.error("Error while setting Geni cookie", e);
            }
        }

        loginWebView.getEngine().setConfirmHandler(param -> new Alert(Alert.AlertType.CONFIRMATION, param)
                .showAndWait()
                .filter(buttonType -> buttonType == ButtonType.YES).isPresent());

        loginWebView.getEngine().setOnAlert(text ->
                new Alert(Alert.AlertType.WARNING, text.toString()).show());

        loginWebView.getEngine().setOnError(error -> {
            LOG.info("Received ERROR from webview: {}", error.getMessage(), error.getException());

            ExceptionDialog proxyAlert = new ExceptionDialog(error.getException());
            proxyAlert.setHeaderText("Error while retrieving login web page");
            proxyAlert.setContentText("jFed could not load the login web page. Please check whether your browser proxy settings are valid, and try again.");
            proxyAlert.showAndWait();
        });

        loginWebView.getEngine().getLoadWorker().exceptionProperty().addListener(observable1 -> {
            final Throwable exception = loginWebView.getEngine().getLoadWorker().getException();
            LOG.warn("Login webview has error: {}",
                    exception.getMessage(),
                    exception);

            ExceptionDialog proxyAlert = new ExceptionDialog(exception);
            proxyAlert.setHeaderText("Error while retrieving login web page");
            proxyAlert.setContentText("jFed could not load the login web page.\n\nProbably you have a proxy server enabled in any of your browsers, which blocks some of the ports needed. Can you try to turn off the proxy server and try again?");
            proxyAlert.showAndWait();
        });


        loadingLabel.textProperty().bind(
                loginWebView.getEngine().getLoadWorker().messageProperty());

        loginWebView.getEngine().documentProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue != null) {
                String text = (String) loginWebView.getEngine().executeScript("document.documentElement.outerText");
                if (text != null && text.startsWith("-----BEGIN")) {
                    processCertificate(text);
                }
            }
        });


        loginWebView.getEngine().locationProperty().addListener((observable, oldValue, newValue) -> {
            if (Objects.equals(newValue, GENI_DOWNLOAD_URL)) {
                String geniCert = downloadGeniCert();
                if (geniCert != null)
                    processCertificate(geniCert);
                else {
                    LOG.warn("Downloading GENI certificate failed");
                }
            }
        });
    }

    public void setStage(Stage stage) {
        this.stage = stage;
    }

    @Nullable
    private String downloadGeniCert() {
        try {
            DefaultHttpClient client = new DefaultHttpClient();
            client.setCookieStore(createApacheCookieStore());

            HttpPost downloadPost = new HttpPost(GENI_DOWNLOAD_URL);
            List<NameValuePair> nvps = new ArrayList<>();
            nvps.add(new BasicNameValuePair("download", "y"));
            downloadPost.setEntity(new UrlEncodedFormEntity(nvps));

            HttpResponse response = client.execute(downloadPost);

            if (response.getStatusLine().getStatusCode() != 200)
                return null; //no 200 code, we are getting an error or a redirect

            if (response.getEntity().getContentLength() == 0)
                return null; //the certificate has not been generated yet

            return EntityUtils.toString(response.getEntity());


        } catch (IOException e) {
            LOG.error("Error while downloading GENI certificate", e);
            return null;
        }
    }

    @Nonnull
    private BasicCookieStore createApacheCookieStore() {
        BasicCookieStore cookieStore = new BasicCookieStore();
        for (HttpCookie httpCookie : manager.getCookieStore().getCookies()) {
            final BasicClientCookie cookie =
                    new BasicClientCookie(httpCookie.getName(), httpCookie.getValue());
            cookie.setDomain(httpCookie.getDomain());
            cookie.setPath(httpCookie.getPath());
            cookie.setComment(httpCookie.getComment());
            cookie.setSecure(httpCookie.getSecure());
            cookie.setVersion(httpCookie.getVersion());

            cookieStore.addCookie(cookie);
        }
        return cookieStore;
    }

    private void processCertificate(String certificate) {
        result = certificate;

        //try to save geni-cookie
        manager.getCookieStore().get(GENI_URI).stream()
                .filter(cookie -> Objects.equals(cookie.getName(), "_saml_idp")).findFirst()
                .ifPresent(cookie -> preferences.setString(GuiPreferenceKey.PREF_GENI_LOGIN_COOKIE, cookie.toString()));

        stage.close();
    }

    public void loadUrl(String initialUrl) {
        loginWebView.getEngine().load(initialUrl);
    }

    public String getResult() {
        return result;
    }
}
