package com.looker.droidify

import android.annotation.SuppressLint
import android.app.Application
import android.app.job.JobInfo
import android.app.job.JobScheduler
import android.content.*
import coil.ImageLoader
import coil.ImageLoaderFactory
import com.looker.droidify.content.Cache
import com.looker.droidify.content.Preferences
import com.looker.droidify.content.ProductPreferences
import com.looker.droidify.database.Database
import com.looker.droidify.index.RepositoryUpdater
import com.looker.droidify.network.CoilDownloader
import com.looker.droidify.network.Downloader
import com.looker.droidify.service.Connection
import com.looker.droidify.service.SyncService
import com.looker.droidify.utility.Utils.toInstalledItem
import com.looker.droidify.utility.extension.android.Android
import java.net.InetSocketAddress
import java.net.Proxy

@Suppress("unused")
class MainApplication : Application(), ImageLoaderFactory {

    override fun onCreate() {
        super.onCreate()

        val databaseUpdated = Database.init(this)
        Preferences.init(this)
        ProductPreferences.init(this)
        RepositoryUpdater.init()
        listenApplications()
        listenPreferences()

        if (databaseUpdated) {
            forceSyncAll()
        }

        Cache.cleanup(this)
        updateSyncJob(false)
    }

    private fun listenApplications() {
        registerReceiver(object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                val packageName =
                    intent.data?.let { if (it.scheme == "package") it.schemeSpecificPart else null }
                if (packageName != null) {
                    when (intent.action.orEmpty()) {
                        Intent.ACTION_PACKAGE_ADDED,
                        Intent.ACTION_PACKAGE_REMOVED -> {
                            val packageInfo = try {
                                packageManager.getPackageInfo(
                                    packageName,
                                    Android.PackageManager.signaturesFlag
                                )
                            } catch (e: Exception) {
                                null
                            }
                            if (packageInfo != null) {
                                Database.InstalledAdapter.put(packageInfo.toInstalledItem())
                            } else {
                                Database.InstalledAdapter.delete(packageName)
                            }
                        }
                    }
                }
            }
        }, IntentFilter().apply {
            addAction(Intent.ACTION_PACKAGE_ADDED)
            addAction(Intent.ACTION_PACKAGE_REMOVED)
            addDataScheme("package")
        })
        val installedItems =
            packageManager.getInstalledPackages(Android.PackageManager.signaturesFlag)
                .map { it.toInstalledItem() }
        Database.InstalledAdapter.putAll(installedItems)
    }

    private fun listenPreferences() {
        updateProxy()
        var lastAutoSync = Preferences[Preferences.Key.AutoSync]
        var lastUpdateUnstable = Preferences[Preferences.Key.UpdateUnstable]
        Preferences.observable.subscribe {
            if (it == Preferences.Key.ProxyType || it == Preferences.Key.ProxyHost || it == Preferences.Key.ProxyPort) {
                updateProxy()
            } else if (it == Preferences.Key.AutoSync) {
                val autoSync = Preferences[Preferences.Key.AutoSync]
                if (lastAutoSync != autoSync) {
                    lastAutoSync = autoSync
                    updateSyncJob(true)
                }
            } else if (it == Preferences.Key.UpdateUnstable) {
                val updateUnstable = Preferences[Preferences.Key.UpdateUnstable]
                if (lastUpdateUnstable != updateUnstable) {
                    lastUpdateUnstable = updateUnstable
                    forceSyncAll()
                }
            }
        }
    }

    private fun updateSyncJob(force: Boolean) {
        val jobScheduler = getSystemService(JOB_SCHEDULER_SERVICE) as JobScheduler
        val reschedule = force || !jobScheduler.allPendingJobs.any { it.id == Common.JOB_ID_SYNC }
        if (reschedule) {
            val autoSync = Preferences[Preferences.Key.AutoSync]
            when (autoSync) {
                Preferences.AutoSync.Never -> {
                    jobScheduler.cancel(Common.JOB_ID_SYNC)
                }
                Preferences.AutoSync.Wifi, Preferences.AutoSync.Always -> {
                    val period = 12 * 60 * 60 * 1000L // 12 hours
                    val wifiOnly = autoSync == Preferences.AutoSync.Wifi
                    jobScheduler.schedule(JobInfo
                        .Builder(
                            Common.JOB_ID_SYNC,
                            ComponentName(this, SyncService.Job::class.java)
                        )
                        .setRequiredNetworkType(if (wifiOnly) JobInfo.NETWORK_TYPE_UNMETERED else JobInfo.NETWORK_TYPE_ANY)
                        .apply {
                            if (Android.sdk(26)) {
                                setRequiresBatteryNotLow(true)
                                setRequiresStorageNotLow(true)
                            }
                            if (Android.sdk(24)) {
                                setPeriodic(period, JobInfo.getMinFlexMillis())
                            } else {
                                setPeriodic(period)
                            }
                        }
                        .build())
                    Unit
                }
            }::class.java
        }
    }

    private fun updateProxy() {
        val type = Preferences[Preferences.Key.ProxyType].proxyType
        val host = Preferences[Preferences.Key.ProxyHost]
        val port = Preferences[Preferences.Key.ProxyPort]
        val socketAddress = when (type) {
            Proxy.Type.DIRECT -> {
                null
            }
            Proxy.Type.HTTP, Proxy.Type.SOCKS -> {
                try {
                    InetSocketAddress.createUnresolved(host, port)
                } catch (e: Exception) {
                    e.printStackTrace()
                    null
                }
            }
        }
        val proxy = socketAddress?.let { Proxy(type, socketAddress) }
        Downloader.proxy = proxy
    }

    private fun forceSyncAll() {
        Database.RepositoryAdapter.getAll(null).forEach {
            if (it.lastModified.isNotEmpty() || it.entityTag.isNotEmpty()) {
                Database.RepositoryAdapter.put(it.copy(lastModified = "", entityTag = ""))
            }
        }
        Connection(SyncService::class.java, onBind = { connection, binder ->
            binder.sync(SyncService.SyncRequest.FORCE)
            connection.unbind(this)
        }).bind(this)
    }

    class BootReceiver : BroadcastReceiver() {
        @SuppressLint("UnsafeProtectedBroadcastReceiver")
        override fun onReceive(context: Context, intent: Intent) = Unit
    }

    override fun newImageLoader(): ImageLoader {
        return ImageLoader.Builder(this)
            .callFactory(CoilDownloader.Factory(Cache.getImagesDir(this)))
            .crossfade(true)
            .build()
    }
}
