package de.sebse.fuplanner.services.KVV;

import android.content.Context;

import java.io.IOException;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import de.sebse.fuplanner.services.KVV.types.LoginToken;
import de.sebse.fuplanner.tools.network.HTTPService;
import de.sebse.fuplanner.tools.network.NetworkCallback;
import de.sebse.fuplanner.tools.network.NetworkError;
import de.sebse.fuplanner.tools.network.NetworkErrorCallback;

import static de.sebse.fuplanner.services.KVV.TestLogin.testLogin;

/**
 * Created by sebastian on 24.10.17.
 */

class KVVLogin extends HTTPService {
    private LoginToken loginToken;

    KVVLogin(Context context) {
        super(context);
        try {
            this.loginToken = LoginToken.load(context);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    LoginToken easyLogin() {
        return this.loginToken;
    }

    void login(String username, String password, NetworkCallback<LoginToken> callback, NetworkErrorCallback errorCallback) {
        if (this.loginToken != null) {
            if (this.loginToken.getUsername().equals(username)) {
                testLogin(getContext(), this.loginToken, success -> callback.onResponse(this.loginToken), error -> {
                    this.loginToken = null;
                    login(username, password, callback, errorCallback);
                });
            } else {
                this.loginToken = null;
                login(username, password, callback, errorCallback);
            }
        } else {
            doLogin(username, password, token -> {
                this.loginToken = token;
                testLogin(getContext(), this.loginToken, success -> callback.onResponse(this.loginToken), errorCallback);
            }, errorCallback);
        }
    }

    void deleteOffline() {
        if (this.loginToken != null)
            this.loginToken.delete(getContext());
    }

    void saveOffline() throws IOException {
        if (this.loginToken != null)
            this.loginToken.save(getContext());
    }






    private void doLogin(String username, String password, NetworkCallback<LoginToken> callback, NetworkErrorCallback error) {
        startKVVSession(success -> {
            String kvvJSESSIONID = success.get("JSESSIONID");
            getSAMLRequest(kvvJSESSIONID, success1 -> startIdentSession(success1.get("Location"), success11 -> {
                String identJSESSIONID = success11.get("JSESSIONID");
                String ident_idp_authn_lc_key = success11.get("_idp_authn_lc_key");
                String identROUTEID = success11.get("ROUTEID");
                loginIdent(true, username, password, identJSESSIONID, ident_idp_authn_lc_key, identROUTEID, success111 -> loginIdent(false, username, password, identJSESSIONID, ident_idp_authn_lc_key, identROUTEID, success11112 -> {
                    String ident_idp_session = success11112.get("_idp_session");
                    getSAMLResponse(identJSESSIONID, ident_idp_authn_lc_key, identROUTEID, ident_idp_session, success1111 -> loginKVV(success1111.get("RelayState"), success1111.get("SAMLResponse"), kvvJSESSIONID, success111112 -> {
                        LoginToken token = new LoginToken(username, success111112.get("shibsessionKey"), success111112.get("shibsessionName"), kvvJSESSIONID);
                        finishKVVlogin(token, success11111 -> callback.onResponse(token), error);
                    }, error), error);
                }, error), error);
            }, error), error);
        }, error);
    }

    /*
    GET https://kvv.imp.fu-berlin.de/portal/login
    -> JSESSIONID 5c10406f-588c-4c16-96e9-c80d115417de.tomcat1
     */
    private void startKVVSession(final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
        get("https://kvv.imp.fu-berlin.de/portal/login", null, response -> {
            String cookies = response.getHeaders().get("Set-Cookie");
            if (cookies==null) {
                errorCallback.onError(new NetworkError(100101, -1, "Error on starting KVV session!"));
                return;
            }
            HashMap<String, String> object;
            try {
                object = getCookie(cookies, new String[]{"JSESSIONID"});
            } catch (NoSuchFieldException e) {
                errorCallback.onError(new NetworkError(100102, -1, "Error on starting KVV session!"));
                return;
            }
            callback.onResponse(object);
        }, error -> errorCallback.onError(new NetworkError(100100, error.networkResponse.statusCode, "Error on starting KVV session!")));
    }

    /*
    GET https://kvv.imp.fu-berlin.de/sakai-login-tool/container
    <- JSESSIONID
    -> (Location-Header) https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO
    ?SAMLRequest=fZLLb.....Q8yre3X1IHwkJKE0Mnpy/V9TH4A
    &RelayState=ss:mem:7ea01e29157b8bd906f7002176.....0d1a505f2c8bf
     */
    private void getSAMLRequest(String JSESSIONID, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
        HashMap<String, String> cookies = new HashMap<>();
        cookies.put("JSESSIONID", JSESSIONID);
        get("https://kvv.imp.fu-berlin.de/sakai-login-tool/container", cookies, response -> {
            String location = response.getHeaders().get("Location");
            if (location==null) {
                errorCallback.onError(new NetworkError(100111, -1, "Error on getting SAML request!"));
                return;
            }
            HashMap<String, String> object = new HashMap<>();
            object.put("Location", location);
            callback.onResponse(object);
        }, error -> errorCallback.onError(new NetworkError(100110, error.networkResponse.statusCode, "Error on getting SAML request!")));
    }

    /*
    GET https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO
    ?SAMLRequest=fZLLbsIwEEV/JfI+cWJAUIsgpbAoEi2IpF10UznxUKw6dupxaPn7hkdb2LD29bkzRzNGUeuGZ63fmjV8toA++K61QX58SEnrDLcCFXIjakDuK55njwvOopg3znpbWU2CDBGcV9ZMrcG2BpeD26kKnteLlGy9b5BT+rHbRapuok0bluC0MpEEmm9VWVoNfhshWnpgM7pa5gUJZt0wyogD9h+iJBiv/P6aomQTbtqSdhNtlIYzZg1SOag8zfMlCeazlLyNqpHsy1gO2V1fVsNBMuqJoUyAJaxXDUaiiyG2MDfohfEpYXEyDJM4ZKxgCe/FPI5fSbA6L36vjFTm/bal8hRC/lAUq/C02gs4PK7VBchkfHDNj8Xuwv5trPhVTiY3BeOf4DG96DmVNvypA89nK6tVtQ8yre3X1IHwkJKE0Mnpy/V9TH4A
    &RelayState=ss:mem:7ea01e29157b8bd906f7002176213b6db5e1f45ebb88716a9820d1a505f2c8bf
    -> JSESSIONID			C4B6A428BA1F50746235D03F5D107A57
    -> _idp_authn_lc_key	57a6ae26067f374cc3d0ccfc47e27b04b47752d2a3d4eb2782af0d3994535395
    -> ROUTEID				.1
    */
    private void startIdentSession(String url, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
        get(url, null, response -> {
            String cookies = response.getHeaders().get("Set-Cookie");
            if (cookies==null) {
                errorCallback.onError(new NetworkError(100121, -1, "Error on starting Ident session!"));
                return;
            }
            HashMap<String, String> object;
            try {
                object = getCookie(cookies, new String[]{"JSESSIONID", "_idp_authn_lc_key", "ROUTEID"});
            } catch (NoSuchFieldException e) {
                errorCallback.onError(new NetworkError(100122, -1, "Error on starting Ident session!"));
                return;
            }
            callback.onResponse(object);
        }, error -> errorCallback.onError(new NetworkError(100120, error.networkResponse.statusCode, "Error on starting Ident session!")));
    }

    /*
    POST https://identity.fu-berlin.de/idp-fub/Authn/UserPassword
    <- j_username			seedorf96
    <- j_password			neinhieristpatrick
    <- (Header-"Content-Type") application/x-www-form-urlencoded
    <- JSESSIONID
    <- _idp_authn_lc_key
    <- ROUTEID
    -> _idp_session			OTMuMTkzLjg1LjMz|LQ==|OGYxOWI4MjA2NTQ4YWUwYzJkOWM4Mjk4YzcwZDMwZmJiZjBmMTdmMzkyZGU2OWIwY2JkNmZlNjlmNTRmNzBlMQ==|wLlzQal7VqyntmG2vLNn06wt8wQ=
     */
    private void loginIdent(final boolean first, String username, String password, String JSESSIONID, String _idp_authn_lc_key, String ROUTEID, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
        HashMap<String, String> cookies = new HashMap<>();
        cookies.put("JSESSIONID", JSESSIONID);
        cookies.put("_idp_authn_lc_key", _idp_authn_lc_key);
        cookies.put("ROUTEID", ROUTEID);
        HashMap<String, String> body = new HashMap<>();
        body.put("j_username", username);
        body.put("j_password", password);
        post("https://identity.fu-berlin.de/idp-fub/Authn/UserPassword", cookies, body, response -> {
            if (first) {
                callback.onResponse(new HashMap<>());
                return;
            }

            String cookies1 = response.getHeaders().get("Set-Cookie");
            if (cookies1 ==null) {
                errorCallback.onError(new NetworkError(100131, -1, "Error on logging in to Identity Server!"));
                return;
            }
            HashMap<String, String> object;
            try {
                object = getCookie(cookies1, new String[]{"_idp_session"});
            } catch (NoSuchFieldException e) {
                errorCallback.onError(new NetworkError(100132, -1, "Error on logging in to Identity Server!"));
                return;
            }
            callback.onResponse(object);
        }, error -> errorCallback.onError(new NetworkError(100130, error.networkResponse.statusCode, "Error on logging in to Identity Server!")));
    }

    /*
    GET https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO
    <- JSESSIONID
    <- _idp_authn_lc_key
    <- ROUTEID
    <- _idp_session
    -> (BODY) RelayState	7ea01e29157b8bd906f7002176213b6db5e1f45ebb88716a9820d1a505f2c8bf
    -> (BODY) SAMLResponse	PD94bWwgdmVyc2lvbj0...........wvc2FtbDJwOlJlc3BvbnNlPg==
    */
    private void getSAMLResponse(String JSESSIONID, String _idp_authn_lc_key, String ROUTEID, String _idp_session, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
        HashMap<String, String> cookies = new HashMap<>();
        cookies.put("JSESSIONID", JSESSIONID);
        cookies.put("_idp_authn_lc_key", _idp_authn_lc_key);
        cookies.put("ROUTEID", ROUTEID);
        cookies.put("_idp_session", _idp_session);
        get("https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO", cookies, response -> {
            HashMap<String, String> object = new HashMap<>();

            Pattern pattern = Pattern.compile("ss&#x3a;mem&#x3a;([0-9a-f]+)");
            Matcher matcher = pattern.matcher(response.getParsed());
            if (!matcher.find()) {
                errorCallback.onError(new NetworkError(100142, -1, "Error on getting SAML response!"));
                return;
            }
            object.put("RelayState", "ss:mem:"+matcher.group(1));

            pattern = Pattern.compile("name=\"SAMLResponse\" value=\"([0-9a-zA-Z+]+=*)");
            matcher = pattern.matcher(response.getParsed());
            if (!matcher.find()) {
                errorCallback.onError(new NetworkError(100141, -1, "Error on getting SAML response!"));
                return;
            }
            object.put("SAMLResponse", matcher.group(1));

            callback.onResponse(object);
        }, error -> errorCallback.onError(new NetworkError(100140, error.networkResponse.statusCode, "Error on getting SAML response!")));
    }


    /*
    POST https://kvv.imp.fu-berlin.de/Shibboleth.sso/SAML2/POST
    <- RelayState			7ea01e29157b8bd906f7002176213b6db5e1f45ebb88716a9820d1a505f2c8bf
    <- SAMLResponse			PD94bWwgdmVyc2lvbj0...........wvc2FtbDJwOlJlc3BvbnNlPg==
    <- JSESSIONID
    -> _shibsession_64656661756c7468747470733a2f2f6b76762e696d702e66752d6265726c696e2e64652f73686962626f6c657468
			_b1912c5a03d733a80bd3fee772bf68d4
	*/
    private void loginKVV(String RelayState, String SAMLResponse, String JSESSIONID, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
        HashMap<String, String> cookies = new HashMap<>();
        cookies.put("JSESSIONID", JSESSIONID);
        HashMap<String, String> body = new HashMap<>();
        body.put("RelayState", RelayState);
        body.put("SAMLResponse", SAMLResponse);
        post("https://kvv.imp.fu-berlin.de/Shibboleth.sso/SAML2/POST", cookies, body, response -> {
            String cookies1 = response.getHeaders().get("Set-Cookie");
            if (cookies1 ==null) {
                errorCallback.onError(new NetworkError(100151, -1, "Error on starting KVV session!"));
                return;
            }
            HashMap<String, String> object = new HashMap<>();


            Pattern pattern = Pattern.compile("(_shibsession_[0-9a-f]+)=([^;]+);");
            Matcher matcher = pattern.matcher(cookies1);
            if (!matcher.find()) {
                errorCallback.onError(new NetworkError(100152, -1, "Error on starting Ident session!"));
            }
            object.put("shibsessionKey", matcher.group(1));
            object.put("shibsessionName", matcher.group(2));

            callback.onResponse(object);
        }, error -> errorCallback.onError(new NetworkError(100150, error.networkResponse.statusCode, "Error on starting Ident session!")));
    }


    /*
    GET https://kvv.imp.fu-berlin.de/sakai-login-tool/container
    <- JSESSIONID
    <- _shibsession_64656661756c7468747470733a2f2f6b76762e696d702e66752d6265726c696e2e64652f73686962626f6c657468
			_b1912c5a03d733a80bd3fee772bf68d4
	*/
    private void finishKVVlogin(LoginToken loginToken, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
        get("https://kvv.imp.fu-berlin.de/sakai-login-tool/container", loginToken.getCookies(), response -> callback.onResponse(new HashMap<>()), error -> errorCallback.onError(new NetworkError(100160, error.networkResponse.statusCode, "Cannot finish login process!")));
    }












    private String getCookie(String cookies, String name) throws NoSuchFieldException {
        Pattern pattern = Pattern.compile(name+"=([^;]+);");
        Matcher matcher = pattern.matcher(cookies);
        if (!matcher.find()) {
            log.e("GETcookie failed", name);
            log.e("GETcookie failed", cookies);
            throw new NoSuchFieldException();
        }
        return matcher.group(1);
    }

    private HashMap<String, String> getCookie(String cookies, String[] names) throws NoSuchFieldException {
        HashMap<String, String> result = new HashMap<>();
        for (String name: names) {
            result.put(name,this.getCookie(cookies, name));
        }
        return result;
    }
}


