package ch.threema.app.startup

import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.launch
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import ch.threema.android.buildActivityIntent
import ch.threema.android.disableEnterTransition
import ch.threema.android.disableExitTransition
import ch.threema.android.getParcelableExtraCompat
import ch.threema.android.showToast
import ch.threema.app.R
import ch.threema.app.compose.theme.ThreemaTheme
import ch.threema.app.passphrase.PassphraseUnlockContract
import ch.threema.app.startup.components.AppStartupScreen
import ch.threema.app.startup.components.ErrorState
import ch.threema.app.startup.components.LoadingState
import ch.threema.app.startup.components.UnlockRetryDialog
import ch.threema.app.usecases.ShareDebugLogUseCase
import ch.threema.app.utils.IntentDataUtil
import ch.threema.app.utils.logScreenVisibility
import ch.threema.base.utils.getThreemaLogger
import ch.threema.localcrypto.MasterKeyManager
import kotlin.system.exitProcess
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject

private val logger = getThreemaLogger("AppStartupActivity")

/**
 * This activity is shown when the app is not (yet) ready to display the screen that the user requested,
 * e.g. because database or system updates need to wrap up first, the master key is locked, or because an irrecoverable error occurred.
 */
class AppStartupActivity : AppCompatActivity() {
    init {
        logScreenVisibility(logger)
    }

    private val appStartupMonitor: AppStartupMonitor by inject()
    private val masterKeyManager: MasterKeyManager by inject()
    private val shareDebugLogUseCase: ShareDebugLogUseCase by inject()
    private val remoteSecretMonitorRetryController: RemoteSecretMonitorRetryController by inject()

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

        lifecycleScope.launch {
            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                logger.info("Waiting for app to be ready")
                appStartupMonitor.awaitAll()
                logger.info("App is ready")

                returnToOriginalActivityIfNeeded()
            }
        }

        setContent {
            var showLoading by rememberSaveable { mutableStateOf(false) }
            var showUnlockRetryDialog by rememberSaveable { mutableStateOf(false) }
            val masterKeyUnlocker = rememberLauncherForActivityResult(PassphraseUnlockContract) { unlocked ->
                showLoading = false
                if (!unlocked) {
                    showUnlockRetryDialog = true
                }
            }
            val pendingSystems by appStartupMonitor.watchPendingSystems().collectAsStateWithLifecycle()
            val errors by appStartupMonitor.watchErrors().collectAsStateWithLifecycle()

            LaunchedEffect(Unit) {
                if (errors.isEmpty() && !showUnlockRetryDialog && masterKeyManager.isProtected() && masterKeyManager.isLockedWithPassphrase()) {
                    masterKeyUnlocker.launch()
                }
            }

            ThreemaTheme {
                AppStartupScreen {
                    when {
                        showUnlockRetryDialog -> {
                            UnlockRetryDialog(
                                onConfirm = {
                                    showUnlockRetryDialog = false
                                    masterKeyUnlocker.launch()
                                },
                                onDismissRequest = {
                                    showUnlockRetryDialog = false
                                    finish()
                                },
                            )
                        }
                        errors.isNotEmpty() -> {
                            ErrorState(
                                errors = errors,
                                onClickExportLogs = ::exportLogs,
                                onClickCloseApp = {
                                    exitProcess(0)
                                },
                                onClickRetryRemoteSecrets = remoteSecretMonitorRetryController::requestRetry,
                            )
                        }
                        pendingSystems.isNotEmpty() -> {
                            LaunchedEffect(showLoading) {
                                if (!showLoading) {
                                    // We delay the displaying of the loading state a bit to avoid it flickering up,
                                    // as in most cases it would only be visible very briefly anyway.
                                    delay(500.milliseconds)
                                    showLoading = true
                                }
                            }
                            if (showLoading) {
                                LoadingState(pendingSystems)
                            }
                        }
                    }
                }
            }
        }
    }

    private fun returnToOriginalActivityIfNeeded() {
        try {
            val originalIntent = intent.getOriginalIntent()
            if (!IntentDataUtil.hideAfterUnlock(originalIntent)) {
                startActivity(originalIntent)
            }
        } catch (e: Exception) {
            // In some cases, re-invoking the original intent isn't so straightforward. E.g. when a file was shared with the app,
            // by the time the app is ready we might no longer have permission to access the shared file, which would lead to
            // a SecurityException here. Under the assumption that the AppStartupActivity only has to be shown very rarely,
            // we accept this shortcoming for now, meaning that the user might have to share the file again or navigate
            // to the original activity manually.
            logger.error("Failed to return to original activity", e)
        }
        finish()
        disableExitTransition()
    }

    private fun exportLogs() {
        logger.info("Export logs button clicked")
        lifecycleScope.launch {
            try {
                shareDebugLogUseCase.call()
            } catch (e: Exception) {
                logger.error("Failed to export debug log", e)
                showToast(R.string.an_error_occurred)
            }
        }
    }

    companion object {
        private const val EXTRA_ORIGINAL_INTENT = "original_intent"

        fun createIntent(context: Context, originalIntent: Intent) =
            buildActivityIntent<AppStartupActivity>(context) {
                putExtra(EXTRA_ORIGINAL_INTENT, originalIntent)
                addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
            }

        fun Intent.getOriginalIntent(): Intent = getParcelableExtraCompat(EXTRA_ORIGINAL_INTENT)!!
    }
}
