package com.fabledsword.minstrel.update.ui

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.fabledsword.minstrel.api.ErrorCopy
import com.fabledsword.minstrel.models.UpdateInfo
import com.fabledsword.minstrel.update.data.ApkInstaller
import com.fabledsword.minstrel.update.data.InstallStage
import com.fabledsword.minstrel.update.data.UpdateBannerController
import com.fabledsword.minstrel.update.data.isBusy
import com.fabledsword.minstrel.update.data.message
import com.fabledsword.minstrel.update.data.stage
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import java.io.File
import javax.inject.Inject

private const val SHARE_STOP_TIMEOUT_MS = 5_000L

data class UpdateBannerUiState(
    val info: UpdateInfo? = null,
    val stage: InstallStage = InstallStage.IDLE,
    val message: String? = null,
)

/**
 * Thin VM over [UpdateBannerController]. Surfaces the available update
 * and runs the download → install handoff via [ApkInstaller], mirroring
 * the About card's flow (route to "install unknown apps" settings first
 * when the permission is missing).
 */
@HiltViewModel
class UpdateBannerViewModel @Inject constructor(
    private val controller: UpdateBannerController,
    private val installer: ApkInstaller,
) : ViewModel() {

    private val installState = MutableStateFlow(InstallSnapshot(InstallStage.IDLE, null))

    val uiState: StateFlow<UpdateBannerUiState> =
        combine(controller.available, installState) { info, install ->
            UpdateBannerUiState(info = info, stage = install.stage, message = install.message)
        }.stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(SHARE_STOP_TIMEOUT_MS),
            initialValue = UpdateBannerUiState(),
        )

    fun dismiss(version: String) = controller.dismiss(version)

    fun install(info: UpdateInfo) {
        if (installState.value.stage.isBusy()) return
        if (!installer.canInstall()) {
            installer.requestInstallPermission()
            installState.value = InstallSnapshot(
                InstallStage.IDLE,
                "Allow installs for Minstrel, then tap Install again.",
            )
            return
        }
        viewModelScope.launch {
            installState.value = InstallSnapshot(InstallStage.DOWNLOADING, null)
            val apk = download(info.apkUrl)
            if (apk != null) {
                // Await the platform's verdict rather than firing an intent and
                // assuming it worked. On a silent install this suspends until
                // the process is replaced, so the line below is only reached
                // when the install did NOT simply succeed.
                installState.value = InstallSnapshot(InstallStage.INSTALLING, null)
                val outcome = installer.install(apk)
                installState.value = InstallSnapshot(outcome.stage(), outcome.message())
            }
        }
    }

    private suspend fun download(apkUrl: String): File? =
        runCatching { installer.downloadApk(apkUrl) }
            .onFailure { e ->
                installState.value = InstallSnapshot(
                    InstallStage.ERROR,
                    "Couldn't download update: ${ErrorCopy.fromThrowable(e)}",
                )
            }
            .getOrNull()
}

private data class InstallSnapshot(val stage: InstallStage, val message: String?)
