package de.sebse.fuplanner.services.kvv.sync;

import android.content.Context;

import org.jetbrains.annotations.NotNull;
import org.json.JSONException;
import org.json.JSONObject;

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;

public class Login extends HTTPService {
    public Login(Context context) {
        super(context);
    }


    public void testLoginToken(@NotNull LoginToken token, @NotNull NetworkCallback<LoginToken> callback, @NotNull NetworkErrorCallback errorCallback) {
        get(String.format("https://kvv.imp.fu-berlin.de/direct/profile/%s.json", token.getUsername()), token.getCookies(), response -> {
            String body = response.getParsed();
            if (body == null) {
                errorCallback.onError(new NetworkError(100172, 403, "Testing login failed!"));
                return;
            }
            try {
                JSONObject json = new JSONObject(body);
                String displayName = json.getString("displayName");
                String email = json.getString("email");
                token.setAdditionals(displayName, email);
                callback.onResponse(token);
            } catch (JSONException e) {
                errorCallback.onError(new NetworkError(100171, 403, "Cannot parse profile!"));
            }
        }, error -> errorCallback.onError(new NetworkError(100170, error.networkResponse.statusCode, "Testing login failed!")));
    }















    public void doLogin(String username, String password, NetworkCallback<LoginToken> callback, NetworkErrorCallback error) {
        step1(success1 -> {
            String samlLocation = success1.get("Location");
            step2(samlLocation, success2 -> {
                String fuJSESSIONID = success2.get("JSESSIONID");
                step3(fuJSESSIONID, success3 -> {
                    step4(username, password, fuJSESSIONID, success4 -> {
                        String fuSHIBSession = success4.get("shib_idp_session");
                        String samlResponse = success4.get("SAMLResponse");
                        step5(samlResponse, success5 -> {
                            String shibsessionKey = success5.get("shibsessionKey");
                            String shibsessionName = success5.get("shibsessionName");
                            step6(shibsessionKey, shibsessionName, success6 -> {
                                String kvvJSESSIONID = success6.get("JSESSIONID");
                                LoginToken token = new LoginToken(username, shibsessionKey, shibsessionName, kvvJSESSIONID);
                                callback.onResponse(token);
                            }, error);
                        }, error);
                    }, error);
                }, error);
            }, error);
        }, error);
    }

    /*
     1= GET https://kvv.imp.fu-berlin.de/Shibboleth.sso/Login?entityID=https://identity.fu-berlin.de/idp-fub
        -> Location-Header: https://identity.fu-berlin.de:9443/idp-fub-qa/profile/SAML2/Redirect/SSO?SAMLResponse=[SAMLResponse]&RelayState=[RelayState]
     */
    private void step1(final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
        get("https://kvv.imp.fu-berlin.de/Shibboleth.sso/Login?entityID=https://identity.fu-berlin.de/idp-fub", null, 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!")));
    }

    /*
     2= GET [Location-Header 1]
        -> Set-Cookie: JSESSIONID=[JSESSION-FU]
        -> Location: /idp-fub-qa/profile/SAML2/Redirect/SSO?execution=e1s1
    */
    private void step2(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 FU session!"));
                return;
            }
            HashMap<String, String> object;
            try {
                object = getCookie(cookies, new String[]{"JSESSIONID"});
            } catch (NoSuchFieldException e) {
                errorCallback.onError(new NetworkError(100122, -1, "Error on starting FU session!"));
                return;
            }
            callback.onResponse(object);
        }, error -> errorCallback.onError(new NetworkError(100120, error.networkResponse.statusCode, "Error on starting FU session!")));
    }

    /*
     3= GET [Location-Header 2]
        + Cookie: JSESSIONID=[JSESSION-FU]
     */
    private void step3(String JSESSIONID_FU, final NetworkCallback<Boolean> callback, final NetworkErrorCallback errorCallback) {
        HashMap<String, String> cookies = new HashMap<>();
        cookies.put("JSESSIONID", JSESSIONID_FU);
        get("https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO?execution=e1s1", cookies, response -> {
            callback.onResponse(true);
        }, error -> errorCallback.onError(new NetworkError(100130, error.networkResponse.statusCode, "Error starting login page!")));
    }

    /*
     4= POST [Location-Header 2]
        + Body: j_username=[USERNAME]&j_password=[PASSWORD]&_eventId_proceed=
        + Header: Content-Type: application/x-www-form-urlencoded
        + Header: Referer: [Location-Header 2]
        + Cookie: JSESSIONID=[JSESSION-FU]
        -> Set-Cookie: shib_idp_session=[SHIB-IDP-SESSION]
        -> Body SAMLResponse-Input-value
     */
    private void step4(String username, String password, String JSESSIONID_FU, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
        HashMap<String, String> cookies = new HashMap<>();
        cookies.put("JSESSIONID", JSESSIONID_FU);
        HashMap<String, String> body = new HashMap<>();
        body.put("j_username", username);
        body.put("j_password", password);
        body.put("_eventId_proceed", "");
        post("https://identity.fu-berlin.de/idp-fub/profile/SAML2/Redirect/SSO?execution=e1s1", cookies, body, response -> {
            String cookies1 = response.getHeaders().get("Set-Cookie");
            if (cookies1 ==null) {
                errorCallback.onError(new NetworkError(100141, -1, "Error on logging in to FU Identity Server!"));
                return;
            }
            HashMap<String, String> object;
            try {
                object = getCookie(cookies1, new String[]{"shib_idp_session"});
            } catch (NoSuchFieldException e) {
                errorCallback.onError(new NetworkError(100142, -1, "Error on logging in to FU Identity Server!"));
                return;
            }

            String content = response.getParsed();
            if (content == null) {
                errorCallback.onError(new NetworkError(100143, -1, "Error on getting SAML response!"));
                return;
            }
            Pattern pattern = Pattern.compile("name=\"SAMLResponse\" value=\"([0-9a-zA-Z+]+=*)");
            Matcher matcher = pattern.matcher(content);
            if (!matcher.find()) {
                errorCallback.onError(new NetworkError(100144, -1, "Error on getting SAML response!"));
                return;
            }
            object.put("SAMLResponse", matcher.group(1));
            callback.onResponse(object);
        }, error -> errorCallback.onError(new NetworkError(100145, error.networkResponse.statusCode, "Error on logging in to FU Identity Server!")));
    }

    /*
     5= POST https://kvv.imp.fu-berlin.de/Shibboleth.sso/SAML2/POST
        + Body: SAMLResponse=[SAML-RESPONSE]
        + Header: Content-Type: application/x-www-form-urlencoded
        -> Set-Cookie: _shibsession_[SESS-NR]: [SESS-VALUE]
    */
    private void step5(String SAMLResponse, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
        HashMap<String, String> body = new HashMap<>();
        body.put("SAMLResponse", SAMLResponse);
        post("https://kvv.imp.fu-berlin.de/Shibboleth.sso/SAML2/POST", null, body, response -> {
            String cookies = response.getHeaders().get("Set-Cookie");
            if (cookies ==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(cookies);
            if (!matcher.find()) {
                errorCallback.onError(new NetworkError(100152, -1, "Error on starting KVV 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 KVV session!")));
    }


    /*
     6= https://kvv.imp.fu-berlin.de/sakai-login-tool/container
        + Cookie: _shibsession_[SESS-NR]: [SESS-VALUE]
        -> Set-Cookie: JSESSIONID: [JSESSION-KVV]
	*/
    private void step6(String shibsessionKey, String shibsessionName, final NetworkCallback<HashMap<String, String>> callback, final NetworkErrorCallback errorCallback) {
        HashMap<String, String> cookies = new HashMap<>();
        cookies.put(shibsessionKey, shibsessionName);
        get("https://kvv.imp.fu-berlin.de/sakai-login-tool/container", cookies, response -> {
            String cookies1 = response.getHeaders().get("Set-Cookie");
            if (cookies1 ==null) {
                errorCallback.onError(new NetworkError(100161, -1, "Cannot finish login process!"));
                return;
            }
            HashMap<String, String> object;
            try {
                object = getCookie(cookies1, new String[]{"JSESSIONID"});
            } catch (NoSuchFieldException e) {
                errorCallback.onError(new NetworkError(100162, -1, "Cannot finish login process!"));
                return;
            }
            callback.onResponse(object);
        }, 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;
    }
}
