package com.nasageek.utexasutilities;

import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.UserManager;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.webkit.CookieSyncManager;
import android.widget.Toast;

import com.crashlytics.android.Crashlytics;
import com.crashlytics.android.core.CrashlyticsCore;
import com.nasageek.utexasutilities.fragments.UTilitiesPreferenceFragment;
import com.securepreferences.SecurePreferences;
import com.squareup.leakcanary.LeakCanary;
import com.squareup.okhttp.OkHttpClient;
import com.tozny.crypto.android.AesCbcWithIntegrity;

import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookieStore;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.HashMap;
import java.util.Map;

import javax.net.ssl.SSLContext;

import io.fabric.sdk.android.Fabric;

public class UTilitiesApplication extends Application {
    private static UTilitiesApplication sInstance;

    public static final String UTD_AUTH_COOKIE_KEY = "utd_auth_cookie";

    private Map<String, AuthCookie> authCookies = new HashMap<>();
    private OkHttpClient client = new OkHttpClient();
    private Map<String, AsyncTask> asyncTaskCache = new HashMap<>();
    private SharedPreferences securePreferences;
    private SharedPreferences sharedPreferences;

    static {
        Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return;
        }
        sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
        boolean sendCrashes = sharedPreferences.getBoolean("sendcrashes", false);
        Crashlytics crashlytics = new Crashlytics.Builder()
                .core(new CrashlyticsCore.Builder().disabled(!sendCrashes).build())
                .build();
        Fabric.with(this, crashlytics);
        sInstance = this;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            try {
                // Attempt to remedy a system memory leak
                UserManager.class.getMethod("get", Context.class).invoke(null, this);
            } catch (Exception e) {
                // Catch Exception because we need API 19 to do multi-catch for the necessary
                // exceptions.
                // Do nothing on failure.
            }
        }
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
            // Bug in P causes excessive leaks, just disable for now
            LeakCanary.install(this);
        }
        try {
            AesCbcWithIntegrity.SecretKeys myKey = AesCbcWithIntegrity.generateKeyFromPassword(
                    Utility.id(this), Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID).getBytes(), 10);
            securePreferences = new SecurePreferences(this, myKey, null);
        } catch (GeneralSecurityException gse) {
            // no clue what to do here
            gse.printStackTrace();
            Crashlytics.logException(gse);
        }
        upgradePasswordEncryption();

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            // UT's servers only support TLS 1.2 now. TLS 1.2 is supported but not enabled by
            // default in Android 4.1 to 5.0. Here we enable it. Unfortunately, this means we have
            // jto drop support for 4.0.
            try {
                SSLContext sc = SSLContext.getInstance("TLSv1.2");
                sc.init(null, null, null);
                client.setSslSocketFactory(new Tls12SocketFactory(sc.getSocketFactory()));
            } catch (NoSuchAlgorithmException | KeyManagementException e) {
                e.printStackTrace();
            }
        }

        authCookies.put(UTD_AUTH_COOKIE_KEY, new UtdAuthCookie(this));

        CookieManager cookieManager = new CookieManager();
        CookieHandler.setDefault(cookieManager);
        client.setCookieHandler(cookieManager);
        CookieSyncManager.createInstance(this);

        AnalyticsHandler.initTrackerIfNeeded(this,
                sharedPreferences.getBoolean(getString(R.string.pref_analytics_key), false));
    }

    private void upgradePasswordEncryption() {
        LegacySecurePreferences lsp = new LegacySecurePreferences(this,
                UTilitiesPreferenceFragment.OLD_PASSWORD_PREF_FILE, false);
        if (lsp.containsKey("password")) {
            try {
                String password = lsp.getString("password");
                securePreferences.edit().putString("password", password).apply();
            } catch (LegacySecurePreferences.SecurePreferencesException spe) {
                spe.printStackTrace();
                Crashlytics.logException(spe);
                Toast.makeText(this, "Your saved password has been wiped for security purposes" +
                        " and will need to be re-entered just this once", Toast.LENGTH_LONG).show();
            } finally {
                lsp.clear();
            }
        }
    }

    public AuthCookie getAuthCookie(String key) {
        return authCookies.get(key);
    }

    public void putAuthCookie(String key, AuthCookie cookie) {
        authCookies.put(key, cookie);
    }

    public boolean allCookiesSet() {
        for (AuthCookie cookie : authCookies.values()) {
            if (!cookie.hasCookieBeenSet()) {
                return false;
            }
        }
        return !authCookies.isEmpty();
    }

    public boolean anyCookiesSet() {
        for (AuthCookie cookie : authCookies.values()) {
            if (cookie.hasCookieBeenSet()) {
                return true;
            }
        }
        return false;
    }

    public String getUtdAuthCookieVal() {
        return authCookies.get(UTD_AUTH_COOKIE_KEY).getAuthCookieVal();
    }

    public void logoutAll() {
        for (AuthCookie authCookie : authCookies.values()) {
            authCookie.logout();
        }
        CookieStore cookies = ((CookieManager) CookieHandler.getDefault()).getCookieStore();
        cookies.removeAll();
    }

    public OkHttpClient getHttpClient() {
        return client;
    }

    public void cacheTask(String tag, AsyncTask task) {
        if (asyncTaskCache.containsKey(tag)) {
            throw new IllegalStateException("Task already cached");
        }
        asyncTaskCache.put(tag, task);
    }

    public AsyncTask getCachedTask(String tag) {
        return asyncTaskCache.get(tag);
    }

    public void removeCachedTask(String tag) {
        asyncTaskCache.remove(tag);
    }

    public SharedPreferences getSecurePreferences() {
        return securePreferences;
    }

    public static UTilitiesApplication getInstance() {
        return sInstance;
    }

    public static UTilitiesApplication getInstance(Context context) {
        return context != null ? (UTilitiesApplication) context.getApplicationContext() : sInstance;
    }
}