package de.sebse.fuplanner.services.kvv;

import android.content.Context;
import android.util.Pair;

import org.jetbrains.annotations.NotNull;

import androidx.annotation.Nullable;
import de.sebse.fuplanner.R;
import de.sebse.fuplanner.services.fulogin.AccountGeneral;
import de.sebse.fuplanner.services.kvv.sync.BBLogin;
import de.sebse.fuplanner.services.kvv.sync.FULogin;
import de.sebse.fuplanner.services.kvv.sync.KVVLogin;
import de.sebse.fuplanner.services.kvv.types.LoginTokenBB;
import de.sebse.fuplanner.services.kvv.types.LoginTokenKVV;
import de.sebse.fuplanner.tools.CustomAccountManager;
import de.sebse.fuplanner.tools.NetworkCallbackCollector;
import de.sebse.fuplanner.tools.Preferences;
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 static final int RESTORE_STATUS_SUCCESS = 1;
    public static final int RESTORE_STATUS_ERROR = 2;
    public static final int RESTORE_STATUS_INVALID_PASSWORD = 3;

    public static final int LOGOUT_KVV = 2;
    public static final int LOGOUT_BB = 1;
    public static final int RELOGIN = 0;

    private final KVVListener mListener;
    @Nullable private LoginTokenKVV mTokenKVV;
    @Nullable private LoginTokenBB mTokenBB;
    private boolean mLoginPending = false;
    private final NetworkCallbackCollector<Pair<LoginTokenKVV, LoginTokenBB>> mRefreshCallbacks = new NetworkCallbackCollector<>();
    private final NetworkCallbackCollector<Integer> mRestoreCallbacks = new NetworkCallbackCollector<>();

    Login(KVVListener listener, Context context) {
        super(context);
        this.mListener = listener;
    }

    public void restoreOnlineLogin(IntegerInterface callback) {
        mRestoreCallbacks.add(new Pair<>(callback::run, null));
        if (mLoginPending) {
            return;
        }
        mLoginPending = true;
        LoginTokenKVV.load(mListener.getAccountManager(), tokenKVV -> {
            LoginTokenBB.load(mListener.getAccountManager(), tokenBB -> {
                boolean result = setToken(tokenKVV, tokenBB);
                mLoginPending = false;
                mRestoreCallbacks.responseResponse(result ? RESTORE_STATUS_SUCCESS : RESTORE_STATUS_INVALID_PASSWORD);
            }, e -> {
                mLoginPending = false;
                mRestoreCallbacks.responseResponse(RESTORE_STATUS_ERROR);
            });
        }, e -> {
            mLoginPending = false;
            mRestoreCallbacks.responseResponse(RESTORE_STATUS_ERROR);
        });
    }

    public void isOfflineStoredAvailable(BooleanInterface callback) {
        LoginTokenKVV.load(mListener.getAccountManager(), tokenKVV -> {
            LoginTokenBB.load(mListener.getAccountManager(), tokenBB -> {
                callback.run(tokenKVV != null && tokenBB != null);
            }, e -> callback.run(false));
        }, e -> callback.run(false));
    }

    public boolean logout(boolean delete) {
        if (mLoginPending)
            return false;
        if (mTokenKVV == null || mTokenBB == null)
            return true;
        if (delete) {
            Preferences.setString(getContext(), R.string.pref_shib_idp_session, "");
            mTokenKVV.delete(mListener.getAccountManager());
            mTokenBB.delete(mListener.getAccountManager());
        }
        mTokenKVV = null;
        mTokenBB = null;
        return handleCallbacks(false);
    }

    public boolean isLoginPending() {
        return mLoginPending;
    }

    public boolean isLoggedIn() {
        return mTokenKVV != null && mTokenBB != null;
    }

    public boolean isInOnlineMode() {
        return isLoggedIn();
    }

    void testLoginToken(@NotNull NetworkCallback<Pair<LoginTokenKVV, LoginTokenBB>> callback, @NotNull NetworkErrorCallback errorCallback) {
        if (mTokenKVV == null) {
            errorCallback.onError(new NetworkError(100173, -1, "Not logged in!"));
            return;
        }
        if (mTokenBB == null) {
            errorCallback.onError(new NetworkError(100174, -1, "Not logged in!"));
            return;
        }
        testLoginToken(mTokenKVV, mTokenBB, callback, errorCallback);
    }

    private void testLoginToken(@NotNull LoginTokenKVV tokenKVV, @NotNull LoginTokenBB tokenBB, @NotNull NetworkCallback<Pair<LoginTokenKVV, LoginTokenBB>> callback, @NotNull NetworkErrorCallback errorCallback) {
        FULogin mFULogin = new FULogin(getContext());
        new KVVLogin(getContext(), mFULogin).testLoginToken(tokenKVV, tokenKVV1 -> {
            new BBLogin(getContext(), mFULogin).testLoginToken(tokenBB, tokenBB1 -> {
                callback.onResponse(new Pair<>(tokenKVV1, tokenBB1));
            }, errorCallback);
        }, errorCallback);
    }

    @Nullable public LoginTokenKVV getLoginTokenKVV() {
        return mTokenKVV;
    }

    @Nullable public LoginTokenBB getLoginTokenBB() {
        return mTokenBB;
    }

    void refreshLogin(NetworkCallback<Pair<LoginTokenKVV, LoginTokenBB>> success, NetworkErrorCallback error, int flags) {
        boolean isFirst = mRefreshCallbacks.isEmpty();
        mRefreshCallbacks.add(success, error);
        if (!isFirst)
            return;
        refreshLoginRunner(success, error, flags);
    }

    private void refreshLoginRunner(NetworkCallback<Pair<LoginTokenKVV, LoginTokenBB>> success, NetworkErrorCallback error, int flags) {
        CustomAccountManager manager = mListener.getAccountManager();
        if ((flags & LOGOUT_KVV) == LOGOUT_KVV) {
            manager.doInvalidateToken(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_KVV, ignored -> {
                refreshLoginRunner(success, error, flags & ~LOGOUT_KVV);
            });
        } else if ((flags & LOGOUT_BB) == LOGOUT_BB) {
            manager.doInvalidateToken(AccountGeneral.ACCOUNT_TYPE, AccountGeneral.AUTHTOKEN_TYPE_BLACKBOARD, ignored -> {
                refreshLoginRunner(success, error, flags & ~LOGOUT_BB);
            });
        } else if (flags == RELOGIN) {
            restoreOnlineLogin(restoreResult -> {
                if (restoreResult == RESTORE_STATUS_SUCCESS)
                    testLoginToken(mRefreshCallbacks::responseResponse, mRefreshCallbacks::responseError);
                else if (restoreResult == RESTORE_STATUS_INVALID_PASSWORD) {
                    logout(true);
                    mRefreshCallbacks.responseError(new NetworkError(100180, 403, "Re-login failed (password)!"));
                } else {
                    mRefreshCallbacks.responseError(new NetworkError(100181, 403, "Re-login failed (error)!"));
                }
            });
        } else {
            throw new IllegalArgumentException("Invalid flag in refreshLoginRunner: "+flags);
        }
    }



    private boolean handleCallbacks(boolean isOnlyRefresh) {
        if (mTokenKVV != null) {
            mListener.onLogin(mTokenKVV, isOnlyRefresh);
            return true;
        } else {
            mListener.onLogout();
            return false;
        }
    }

    private boolean setToken(@Nullable LoginTokenKVV tokenKVV, @Nullable LoginTokenBB tokenBB) {
        if (tokenKVV == null || tokenBB == null)
            return false;
        boolean isOnlyRefresh = mTokenKVV != null;
        mTokenKVV = tokenKVV;
        mTokenBB = tokenBB;
        return isOnlyRefresh || handleCallbacks(isOnlyRefresh);
    }

    public interface BooleanInterface {
        void run(boolean bool);
    }

    public interface IntegerInterface {
        void run(int integer);
    }
}
