/*
 * Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 */

package com.etesync.syncadapter.ui

import android.Manifest
import android.accounts.Account
import android.accounts.AccountManager
import android.annotation.SuppressLint
import android.app.LoaderManager
import android.content.*
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.PowerManager
import android.provider.CalendarContract
import android.provider.ContactsContract
import android.text.TextUtils
import android.view.Menu
import android.view.MenuItem
import android.widget.TextView
import androidx.core.content.ContextCompat
import at.bitfire.ical4android.TaskProvider.ProviderName
import at.bitfire.vcard4android.ContactsStorageException
import com.etesync.syncadapter.*
import com.etesync.syncadapter.Constants.KEY_ACCOUNT
import com.etesync.journalmanager.Exceptions.HttpException
import com.etesync.syncadapter.log.Logger
import com.etesync.syncadapter.model.EntryEntity
import com.etesync.syncadapter.model.JournalEntity
import com.etesync.syncadapter.model.ServiceDB
import com.etesync.syncadapter.model.ServiceEntity
import com.etesync.syncadapter.resource.LocalAddressBook
import org.acra.ACRA
import org.apache.commons.lang3.exception.ExceptionUtils
import org.apache.commons.lang3.text.WordUtils
import java.io.File
import java.util.logging.Level

class DebugInfoActivity : BaseActivity(), LoaderManager.LoaderCallbacks<String> {

    internal lateinit var tvReport: TextView
    internal lateinit var report: String

    internal var reportFile: File? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_debug_info)
        tvReport = findViewById(R.id.text_report)

        loaderManager.initLoader(0, intent.extras, this)
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.activity_debug_info, menu)
        return true
    }


    fun onShare(item: MenuItem) {
        ACRA.getErrorReporter().putCustomData("debug_info", report)
        ACRA.getErrorReporter().handleSilentException(null)
        ACRA.getErrorReporter().removeCustomData("debug_info")
    }

    override fun onCreateLoader(id: Int, args: Bundle?): Loader<String> {
        return ReportLoader(this, args)
    }

    override fun onLoadFinished(loader: Loader<String>, data: String?) {
        if (data != null) {
            report = data
            tvReport.setText(report)
        }
    }

    override fun onLoaderReset(loader: Loader<String>) {}


    internal class ReportLoader(context: Context, val extras: Bundle?) : AsyncTaskLoader<String>(context) {

        override fun onStartLoading() {
            forceLoad()
        }

        @SuppressLint("MissingPermission")
        override fun loadInBackground(): String {
            var throwable: Throwable? = null
            var caller: String? = null
            var logs: String? = null
            var authority: String? = null
            var account: Account? = null
            var phase: String? = null

            if (extras != null) {
                throwable = extras.getSerializable(KEY_THROWABLE) as Throwable?
                caller = extras.getString(KEY_CALLER)
                logs = extras.getString(KEY_LOGS)
                account = extras.getParcelable(KEY_ACCOUNT)
                authority = extras.getString(KEY_AUTHORITY)
                phase = if (extras.containsKey(KEY_PHASE)) context.getString(extras.getInt(KEY_PHASE)) else null
            }

            val report = StringBuilder("--- BEGIN DEBUG INFO ---\n")

            // begin with most specific information

            if (phase != null)
                report.append("SYNCHRONIZATION INFO\nSynchronization phase: ").append(phase).append("\n")
            if (account != null)
                report.append("Account name: ").append(account.name).append("\n")
            if (authority != null)
                report.append("Authority: ").append(authority).append("\n")
            if (caller != null)
                report.append("Debug activity source: ").append(caller).append("\n")

            if (throwable is HttpException) {
                val http = throwable as HttpException?
                if (http!!.request != null)
                    report.append("\nHTTP REQUEST:\n").append(http.request).append("\n\n")
                if (http.request != null)
                    report.append("HTTP RESPONSE:\n").append(http.request).append("\n")
            }

            if (throwable != null)
                report.append("\nEXCEPTION:\n")
                        .append(ExceptionUtils.getStackTrace(throwable))

            if (logs != null)
                report.append("\nLOGS:\n").append(logs).append("\n")

            val context = context

            try {
                val pm = context.packageManager
                var installedFrom = pm.getInstallerPackageName(BuildConfig.APPLICATION_ID)
                if (TextUtils.isEmpty(installedFrom))
                    installedFrom = "APK (directly)"
                report.append("\nSOFTWARE INFORMATION\n" + "EteSync version: ").append(BuildConfig.VERSION_NAME).append(" (").append(BuildConfig.VERSION_CODE).append(") ").append("\n")
                        .append("Installed from: ").append(installedFrom).append("\n")
            } catch (ex: Exception) {
                Logger.log.log(Level.SEVERE, "Couldn't get software information", ex)
            }

            report.append("CONFIGURATION\n")
            // power saving
            val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager?
            if (powerManager != null && Build.VERSION.SDK_INT >= 23)
                report.append("Power saving disabled: ")
                        .append(if (powerManager.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID)) "yes" else "no")
                        .append("\n")
            // permissions
            for (permission in arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR) + ProviderName.OpenTasks.permissions + ProviderName.TasksOrg.permissions)
                report.append(permission).append(" permission: ")
                        .append(if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED) "granted" else "denied")
                        .append("\n")
            // system-wide sync settings
            report.append("System-wide synchronization: ")
                    .append(if (ContentResolver.getMasterSyncAutomatically()) "automatically" else "manually")
                    .append("\n")
            // main accounts
            val accountManager = AccountManager.get(context)
            for (acct in accountManager.getAccountsByType(context.getString(R.string.account_type)))
                try {
                    val settings = AccountSettings(context, acct)
                    report.append("Account: ").append(acct.name).append("\n" + "  Address book sync. interval: ").append(syncStatus(settings, App.addressBooksAuthority)).append("\n" + "  Calendar     sync. interval: ").append(syncStatus(settings, CalendarContract.AUTHORITY)).append("\n" + "  OpenTasks    sync. interval: ").append(syncStatus(settings, ProviderName.OpenTasks.authority)).append("\n" + "  Tasks.org    sync. interval: ").append(syncStatus(settings, ProviderName.TasksOrg.authority)).append("\n" + "  WiFi only: ").append(settings.syncWifiOnly)
                    if (settings.syncWifiOnlySSID != null)
                        report.append(", SSID: ").append(settings.syncWifiOnlySSID)
                    report.append("\n  [CardDAV] Contact group method: ").append(settings.groupMethod)
                            .append("\n           Manage calendar colors: ").append(settings.manageCalendarColors)
                            .append("\n")
                } catch (e: InvalidAccountException) {
                    report.append(acct).append(" is invalid (unsupported settings version) or does not exist\n")
                }

            // address book accounts
            for (acct in accountManager.getAccountsByType(App.addressBookAccountType))
                try {
                    val addressBook = LocalAddressBook(context, acct, null)
                    report.append("Address book account: ").append(acct.name).append("\n" + "  Main account: ").append(addressBook.mainAccount).append("\n" + "  URL: ").append(addressBook.url).append("\n" + "  Sync automatically: ").append(ContentResolver.getSyncAutomatically(acct, ContactsContract.AUTHORITY)).append("\n")
                } catch (e: ContactsStorageException) {
                    report.append(acct).append(" is invalid: ").append(e.message).append("\n")
                }

            report.append("\n")

            report.append("SQLITE DUMP\n")
            val dbHelper = ServiceDB.OpenHelper(context)
            dbHelper.dump(report)
            dbHelper.close()
            report.append("\n")

            report.append("SERVICES DUMP\n")
            val data = (getContext().applicationContext as App).data
            for (serviceEntity in data.select(ServiceEntity::class.java).get()) {
                report.append(serviceEntity.toString() + "\n")
            }
            report.append("\n")

            report.append("JOURNALS DUMP\n")
            val journals = data.select(JournalEntity::class.java).where(JournalEntity.DELETED.eq(false)).get().toList()
            for (journal in journals) {
                report.append(journal.toString() + "\n")
                val entryCount = data.count(EntryEntity::class.java).where(EntryEntity.JOURNAL.eq(journal)).get().value()
                report.append("\tEntries: " + entryCount.toString() + "\n\n")
            }
            report.append("\n")

            try {
                report.append(
                        "SYSTEM INFORMATION\n" + "Android version: ").append(Build.VERSION.RELEASE).append(" (").append(Build.DISPLAY).append(")\n" + "Device: ").append(WordUtils.capitalize(Build.MANUFACTURER)).append(" ").append(Build.MODEL).append(" (").append(Build.DEVICE).append(")\n\n"
                )
            } catch (ex: Exception) {
                Logger.log.log(Level.SEVERE, "Couldn't get system details", ex)
            }

            report.append("--- END DEBUG INFO ---\n")
            return report.toString()
        }

        protected fun syncStatus(settings: AccountSettings, authority: String): String {
            val interval = settings.getSyncInterval(authority)
            return if (interval != null)
                if (interval == AccountSettings.SYNC_INTERVAL_MANUALLY) "manually" else (interval / 60).toString() + " min"
            else
                "—"
        }
    }

    companion object {
        val KEY_CALLER = "caller"
        val KEY_THROWABLE = "throwable"
        val KEY_LOGS = "logs"
        val KEY_AUTHORITY = "authority"
        val KEY_PHASE = "phase"

        fun newIntent(context: Context?, caller: String): Intent {
            val intent = Intent(context, DebugInfoActivity::class.java)
            intent.putExtra(KEY_CALLER, caller)
            return intent
        }
    }

}
