package com.hmg.hmgDr.globalErrorHandler

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.ref.WeakReference;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayDeque;
import java.util.Date;
import java.util.Deque;
import java.util.Locale;
import kotlin.system.exitProcess

class UCEHandler(val builder: Builder) {

    val EXTRA_STACK_TRACE = "EXTRA_STACK_TRACE"
    val EXTRA_ACTIVITY_LOG = "EXTRA_ACTIVITY_LOG"
    private val TAG = "UCEHandler"
    private val UCE_HANDLER_PACKAGE_NAME = "com.rohitss.uceh"
    private val DEFAULT_HANDLER_PACKAGE_NAME = "com.android.internal.os"
    private val MAX_STACK_TRACE_SIZE = 131071 //128 KB - 1

    private val MAX_ACTIVITIES_IN_LOG = 50
    private val SHARED_PREFERENCES_FILE = "uceh_preferences"
    private val SHARED_PREFERENCES_FIELD_TIMESTAMP = "last_crash_timestamp"
    private val activityLog: Deque<String> = ArrayDeque(MAX_ACTIVITIES_IN_LOG)
    var COMMA_SEPARATED_EMAIL_ADDRESSES: String? = null

    @SuppressLint("StaticFieldLeak")
    private var application: Application? = null
    private var isInBackground = true
    private var isBackgroundMode = false
    private var isUCEHEnabled = false
    private var isTrackActivitiesEnabled = false
    private var lastActivityCreated: WeakReference<Activity?> = WeakReference(null)

    fun UCEHandler(builder: Builder) {
        isUCEHEnabled = builder.isUCEHEnabled
        isTrackActivitiesEnabled = builder.isTrackActivitiesEnabled
        isBackgroundMode = builder.isBackgroundModeEnabled
        COMMA_SEPARATED_EMAIL_ADDRESSES = builder.commaSeparatedEmailAddresses
        setUCEHandler(builder.context)
    }

    private fun setUCEHandler(context: Context?) {
        try {
            if (context != null) {
                val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
                if (oldHandler != null && oldHandler.javaClass.name.startsWith(
                        UCE_HANDLER_PACKAGE_NAME
                    )
                ) {
                    Log.e(TAG, "UCEHandler was already installed, doing nothing!")
                } else {
                    if (oldHandler != null && !oldHandler.javaClass.name.startsWith(
                            DEFAULT_HANDLER_PACKAGE_NAME
                        )
                    ) {
                        Log.e(
                            TAG,
                            "You already have an UncaughtExceptionHandler. If you use a custom UncaughtExceptionHandler, it should be initialized after UCEHandler! Installing anyway, but your original handler will not be called."
                        )
                    }
                    application = context.getApplicationContext() as Application
                    //Setup UCE Handler.
                    Thread.setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler { thread, throwable ->
                        if (isUCEHEnabled) {
                            Log.e(
                                TAG,
                                "App crashed, executing UCEHandler's UncaughtExceptionHandler",
                                throwable
                            )
                            if (hasCrashedInTheLastSeconds(application!!)) {
                                Log.e(
                                    TAG,
                                    "App already crashed recently, not starting custom error activity because we could enter a restart loop. Are you sure that your app does not crash directly on init?",
                                    throwable
                                )
                                if (oldHandler != null) {
                                    oldHandler.uncaughtException(thread, throwable)
                                    return@UncaughtExceptionHandler
                                }
                            } else {
                                setLastCrashTimestamp(application!!, Date().getTime())
                                if (!isInBackground || isBackgroundMode) {
                                    val intent = Intent(application, UCEDefaultActivity::class.java)
                                    val sw = StringWriter()
                                    val pw = PrintWriter(sw)
                                    throwable.printStackTrace(pw)
                                    var stackTraceString: String = sw.toString()
                                    if (stackTraceString.length > MAX_STACK_TRACE_SIZE) {
                                        val disclaimer = " [stack trace too large]"
                                        stackTraceString = stackTraceString.substring(
                                            0,
                                            MAX_STACK_TRACE_SIZE - disclaimer.length
                                        ) + disclaimer
                                    }
                                    intent.putExtra(EXTRA_STACK_TRACE, stackTraceString)
                                    if (isTrackActivitiesEnabled) {
                                        val activityLogStringBuilder = StringBuilder()
                                        while (!activityLog.isEmpty()) {
                                            activityLogStringBuilder.append(activityLog.poll())
                                        }
                                        intent.putExtra(
                                            EXTRA_ACTIVITY_LOG,
                                            activityLogStringBuilder.toString()
                                        )
                                    }
                                    intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
                                    application!!.startActivity(intent)
                                } else {
                                    if (oldHandler != null) {
                                        oldHandler.uncaughtException(thread, throwable)
                                        return@UncaughtExceptionHandler
                                    }
                                    //If it is null (should not be), we let it continue and kill the process or it will be stuck
                                }
                            }
                            val lastActivity: Activity? = lastActivityCreated.get()
                            if (lastActivity != null) {
                                lastActivity.finish()
                                lastActivityCreated.clear()
                            }
                            killCurrentProcess()
                        } else oldHandler?.uncaughtException(thread, throwable)
                    })
                    application!!.registerActivityLifecycleCallbacks(object :
                        Application.ActivityLifecycleCallbacks {
                        val dateFormat: DateFormat =
                            SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
                        var currentlyStartedActivities = 0

                        override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
                            if (activity.javaClass !== UCEDefaultActivity::class.java) {
                                lastActivityCreated = WeakReference(activity)
                            }
                            if (isTrackActivitiesEnabled) {
                                activityLog.add(
                                    dateFormat.format(Date())
                                        .toString() + ": " + activity.javaClass
                                        .getSimpleName() + " created\n"
                                )
                            }
                        }

                        override fun onActivityStarted(activity: Activity) {
                            currentlyStartedActivities++
                            isInBackground = currentlyStartedActivities == 0
                        }

                        override fun onActivityResumed(activity: Activity) {
                            if (isTrackActivitiesEnabled) {
                                activityLog.add(
                                    dateFormat.format(Date())
                                        .toString() + ": " + activity.javaClass
                                        .simpleName + " resumed\n"
                                )
                            }
                        }

                        override fun onActivityPaused(activity: Activity) {
                            if (isTrackActivitiesEnabled) {
                                activityLog.add(
                                    dateFormat.format(Date())
                                        .toString() + ": " + activity.javaClass
                                        .simpleName + " paused\n"
                                )
                            }
                        }

                        override fun onActivityStopped(activity: Activity) {
                            currentlyStartedActivities--
                            isInBackground = currentlyStartedActivities == 0
                        }

                        override fun onActivitySaveInstanceState(
                            activity: Activity,
                            outState: Bundle
                        ) {}
                        override fun onActivityDestroyed(activity: Activity) {
                            if (isTrackActivitiesEnabled) {
                                activityLog.add(
                                    dateFormat.format(Date())
                                        .toString() + ": " + activity.javaClass
                                        .simpleName + " destroyed\n"
                                )
                            }
                        }
                    })
                }
                Log.i(TAG, "UCEHandler has been installed.")
            } else {
                Log.e(TAG, "Context can not be null")
            }
        } catch (throwable: Throwable) {
            Log.e(
                TAG,
                "UCEHandler can not be initialized. Help making it better by reporting this as a bug.",
                throwable
            )
        }
    }

    /**
     * INTERNAL method that tells if the app has crashed in the last seconds.
     * This is used to avoid restart loops.
     *
     * @return true if the app has crashed in the last seconds, false otherwise.
     */
    private fun hasCrashedInTheLastSeconds(context: Context): Boolean {
        val lastTimestamp = getLastCrashTimestamp(context)
        val currentTimestamp: Long = Date().getTime()
        return lastTimestamp <= currentTimestamp && currentTimestamp - lastTimestamp < 3000
    }

    @SuppressLint("ApplySharedPref")
    private fun setLastCrashTimestamp(context: Context, timestamp: Long) {
        context.getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE).edit()
            .putLong(SHARED_PREFERENCES_FIELD_TIMESTAMP, timestamp).commit()
    }

    private fun killCurrentProcess() {
//        Process.killProcess(Process.myPid())
        exitProcess(10)
    }

    private fun getLastCrashTimestamp(context: Context): Long {
        return context.getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE)
            .getLong(SHARED_PREFERENCES_FIELD_TIMESTAMP, -1)
    }

    fun closeApplication(activity: Activity) {
        activity.finish()
        killCurrentProcess()
    }

    inner class Builder(context: Context) {
        val context: Context
        var isUCEHEnabled = true
        var commaSeparatedEmailAddresses: String? = null
        var isTrackActivitiesEnabled = false
        var isBackgroundModeEnabled = true
        fun setUCEHEnabled(isUCEHEnabled: Boolean): Builder {
            this.isUCEHEnabled = isUCEHEnabled
            return this
        }

        fun setTrackActivitiesEnabled(isTrackActivitiesEnabled: Boolean): Builder {
            this.isTrackActivitiesEnabled = isTrackActivitiesEnabled
            return this
        }

        fun setBackgroundModeEnabled(isBackgroundModeEnabled: Boolean): Builder {
            this.isBackgroundModeEnabled = isBackgroundModeEnabled
            return this
        }

        fun addCommaSeparatedEmailAddresses(commaSeparatedEmailAddresses: String?): Builder {
            this.commaSeparatedEmailAddresses = commaSeparatedEmailAddresses ?: ""
            return this
        }

        fun build() {
            return UCEHandler(this)
        }

        init {
            this.context = context
        }
    }

}