package com.fsck.k9

import android.app.Application
import android.content.res.Configuration
import android.content.res.Resources
import app.k9mail.ui.widget.list.MessageListWidgetManager
import com.fsck.k9.activity.LauncherShortcuts
import com.fsck.k9.activity.MessageCompose
import com.fsck.k9.controller.MessagingController
import com.fsck.k9.job.WorkManagerConfigurationProvider
import com.fsck.k9.notification.NotificationChannelManager
import com.fsck.k9.provider.UnreadWidgetProvider
import com.fsck.k9.ui.base.AppLanguageManager
import com.fsck.k9.ui.base.ThemeManager
import com.fsck.k9.ui.base.extensions.currentLocale
import com.fsck.k9.widget.list.MessageListWidgetProvider
import java.util.Locale
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.plus
import org.koin.android.ext.android.inject
import timber.log.Timber
import androidx.work.Configuration as WorkManagerConfiguration

class App : Application(), WorkManagerConfiguration.Provider {
    private val messagingController: MessagingController by inject()
    private val messagingListenerProvider: MessagingListenerProvider by inject()
    private val themeManager: ThemeManager by inject()
    private val appLanguageManager: AppLanguageManager by inject()
    private val notificationChannelManager: NotificationChannelManager by inject()
    private val messageListWidgetManager: MessageListWidgetManager by inject()
    private val workManagerConfigurationProvider: WorkManagerConfigurationProvider by inject()

    private val appCoroutineScope: CoroutineScope = GlobalScope + Dispatchers.Main
    private var appLanguageManagerInitialized = false

    override fun onCreate() {
        Core.earlyInit()

        super.onCreate()

        DI.start(this, coreModules + uiModules + appModules)

        K9.init(this)
        Core.init(this)
        initializeAppLanguage()
        updateNotificationChannelsOnAppLanguageChanges()
        themeManager.init()
        messageListWidgetManager.init()

        messagingListenerProvider.listeners.forEach { listener ->
            messagingController.addListener(listener)
        }
    }

    private fun initializeAppLanguage() {
        appLanguageManager.init()
        applyOverrideLocaleToConfiguration()
        appLanguageManagerInitialized = true
        listenForAppLanguageChanges()
    }

    private fun applyOverrideLocaleToConfiguration() {
        appLanguageManager.getOverrideLocale()?.let { overrideLocale ->
            updateConfigurationWithLocale(superResources.configuration, overrideLocale)
        }
    }

    private fun listenForAppLanguageChanges() {
        appLanguageManager.overrideLocale
            .drop(1) // We already applied the initial value
            .onEach { overrideLocale ->
                val locale = overrideLocale ?: Locale.getDefault()
                updateConfigurationWithLocale(superResources.configuration, locale)
            }
            .launchIn(appCoroutineScope)
    }

    override fun onConfigurationChanged(newConfiguration: Configuration) {
        applyOverrideLocaleToConfiguration()
        super.onConfigurationChanged(superResources.configuration)
    }

    private fun updateConfigurationWithLocale(configuration: Configuration, locale: Locale) {
        Timber.d("Updating application configuration with locale '$locale'")

        val newConfiguration = Configuration(configuration).apply {
            currentLocale = locale
        }

        @Suppress("DEPRECATION")
        superResources.updateConfiguration(newConfiguration, superResources.displayMetrics)
    }

    private val superResources: Resources
        get() = super.getResources()

    // Creating a WebView instance triggers something that will cause the configuration of the Application's Resources
    // instance to be reset to the default, i.e. not containing our locale override. Unfortunately, we're not notified
    // about this event. So we're checking each time someone asks for the Resources instance whether we need to change
    // the configuration again. Luckily, right now (Android 11), the platform is calling this method right after
    // resetting the configuration.
    override fun getResources(): Resources {
        val resources = super.getResources()

        if (appLanguageManagerInitialized) {
            appLanguageManager.getOverrideLocale()?.let { overrideLocale ->
                if (resources.configuration.currentLocale != overrideLocale) {
                    Timber.w("Resources configuration was reset. Re-applying locale override.")
                    appLanguageManager.applyOverrideLocale()
                    applyOverrideLocaleToConfiguration()
                }
            }
        }

        return resources
    }

    private fun updateNotificationChannelsOnAppLanguageChanges() {
        appLanguageManager.appLocale
            .distinctUntilChanged()
            .onEach { notificationChannelManager.updateChannels() }
            .launchIn(appCoroutineScope)
    }

    override fun getWorkManagerConfiguration(): WorkManagerConfiguration {
        return workManagerConfigurationProvider.getConfiguration()
    }

    companion object {
        val appConfig = AppConfig(
            componentsToDisable = listOf(
                MessageCompose::class.java,
                LauncherShortcuts::class.java,
                UnreadWidgetProvider::class.java,
                MessageListWidgetProvider::class.java,
            ),
        )
    }
}
