package com.yorvana

import android.app.Application
import android.net.Uri
import com.yorvana.data.billing.BillingManager
import com.yorvana.data.billing.BillingManagerImpl
import com.yorvana.data.billing.DebugBillingOverride
import com.yorvana.data.billing.DefaultBillingClientProvider
import com.yorvana.data.preferences.AppPreferencesImpl
import com.yorvana.data.preferences.AppPreferencesStore
import com.yorvana.data.preferences.dataStore
import com.yorvana.data.repository.CategoryRepository
import com.yorvana.data.repository.CategoryRepositoryImpl
import com.yorvana.data.repository.RecordRepository
import com.yorvana.data.repository.RecordRepositoryImpl
import com.yorvana.data.repository.VehicleRepository
import com.yorvana.data.repository.VehicleRepositoryImpl
import com.yorvana.data.storage.VaultStorage
import com.yorvana.data.storage.VaultStorageImpl
import com.yorvana.domain.AppGate
import com.yorvana.util.CrashReporter
import com.yorvana.util.SentryInitParams
import com.yorvana.util.SentryWrapper
import com.yorvana.util.SentryWrapperImpl
import io.sentry.Hint
import io.sentry.Sentry
import io.sentry.SentryEvent
import io.sentry.android.core.SentryAndroid
import io.sentry.android.core.SentryAndroidOptions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

open class YorvanaApplication : Application() {
    protected open var applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

    private val _vaultMigrationEvents =
        MutableSharedFlow<Unit>(
            extraBufferCapacity = 1,
            onBufferOverflow = BufferOverflow.DROP_OLDEST,
        )
    val vaultMigrationEvents: SharedFlow<Unit> = _vaultMigrationEvents.asSharedFlow()

    suspend fun notifyVaultMigrated() {
        _vaultMigrationEvents.emit(Unit)
    }

    open lateinit var preferences: AppPreferencesStore
        protected set

    open lateinit var vaultStorage: VaultStorage
        protected set

    open lateinit var vehicleRepository: VehicleRepository
        protected set

    open lateinit var recordRepository: RecordRepository
        protected set

    open lateinit var categoryRepository: CategoryRepository
        protected set

    open lateinit var billingManager: BillingManager
        protected set

    open var debugBillingOverride: DebugBillingOverride? = null
        protected set

    open lateinit var appGate: AppGate
        protected set

    override fun onCreate() {
        super.onCreate()
        installCrashReporter()
        initDependencies()
        initSentry()
    }

    protected open val sentryDsn: String get() = BuildConfig.SENTRY_DSN

    protected open fun initSentry() {
        val enabled = preferences.isCrashReportingEnabledSync()
        val dsn = sentryDsn
        if (enabled && dsn.isNotEmpty()) {
            doSentryInit(dsn)
        }
    }

    protected open fun doSentryInit(dsn: String) {
        SentryAndroid.init(this) { options ->
            configureSentryOptions(options)
        }
    }

    internal open val sentryInitParams: SentryInitParams
        get() =
            SentryInitParams(
                dsn = sentryDsn,
                environment = sentryEnvironment,
                release = sentryRelease,
            )

    internal open val sentryWrapper: SentryWrapper by lazy {
        SentryWrapperImpl(sentryInitParams)
    }

    internal fun configureSentryOptions(options: SentryAndroidOptions) {
        val params = sentryInitParams
        options.dsn = params.dsn
        options.environment = params.environment
        options.release = params.release
        options.sampleRate = 1.0
        options.tracesSampleRate = 0.0
        options.isSendDefaultPii = false
        options.isEnableUserInteractionTracing = false
        options.isEnableAutoSessionTracking = false
        options.isAttachScreenshot = false
        options.isAttachViewHierarchy = false
        options.setBeforeSend(::beforeSend)
    }

    /**
     * Sentry tag values, internal-only because ReportBugViewModel.performSubmission
     * needs them for the lazy-init path.
     */
    internal open val sentryEnvironment: String
        get() = if (BuildConfig.DEBUG) "debug" else "release"

    internal open val sentryRelease: String
        get() = "${BuildConfig.APPLICATION_ID}@${BuildConfig.VERSION_NAME}+${BuildConfig.VERSION_CODE}"

    fun enableSentryNow() {
        val dsn = sentryDsn
        if (dsn.isNotEmpty() && !Sentry.isEnabled()) {
            doSentryInit(dsn)
        }
    }

    /** Closes Sentry and flushes pending events on [Dispatchers.IO]; may block up to ~2 s. */
    suspend fun disableSentryNow() {
        if (Sentry.isEnabled()) {
            withContext(Dispatchers.IO) {
                Sentry.close()
            }
        }
    }

    @Suppress("UnusedParameter")
    internal fun beforeSend(
        event: SentryEvent,
        hint: Hint,
    ): SentryEvent? {
        if (!preferences.isCrashReportingEnabledSync()) {
            return null
        }
        return event.apply {
            user = null
            serverName = null
        }
    }

    protected open fun installCrashReporter() {
        CrashReporter.install(this)
    }

    protected open fun initDependencies() {
        preferences = AppPreferencesImpl(this, this.dataStore)

        val storage = VaultStorageImpl(this)
        vaultStorage = storage

        categoryRepository = CategoryRepositoryImpl(this, storage)
        vehicleRepository = VehicleRepositoryImpl(storage)
        recordRepository = RecordRepositoryImpl(storage, categoryRepository, vehicleRepository)

        if (BuildConfig.DEBUG) {
            debugBillingOverride = DebugBillingOverride(preferences, applicationScope)
        }
        val clientProvider = DefaultBillingClientProvider(this)
        billingManager =
            BillingManagerImpl(
                preferences = preferences,
                coroutineScope = applicationScope,
                billingClientProvider = clientProvider,
                debugOverride = debugBillingOverride,
            )
        appGate = AppGate(vehicleRepository, billingManager, applicationScope)
    }

    open fun applyVaultUri(uriString: String?) {
        val uri = uriString?.let { Uri.parse(it) }
        vaultStorage.setVaultUri(uri)

        // Refresh all repositories to load data from the new vault
        applicationScope.launch {
            vehicleRepository.refresh()
            recordRepository.refresh()
            categoryRepository.refresh()
        }
    }
}
