package com.github.nullptroma.wallenc.ui.screens.main.screens.remotes

import androidx.lifecycle.viewModelScope
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
import com.github.nullptroma.wallenc.domain.tasks.ITaskOrchestrator
import com.github.nullptroma.wallenc.domain.tasks.TaskLogLevel
import com.github.nullptroma.wallenc.domain.tasks.TaskProgressLabel
import com.github.nullptroma.wallenc.domain.tasks.VaultTaskStep
import com.github.nullptroma.wallenc.ui.R
import com.github.nullptroma.wallenc.ui.ViewModelBase
import com.github.nullptroma.wallenc.ui.resources.UiStringResolver
import com.github.nullptroma.wallenc.vault.contract.DescribedVault
import com.github.nullptroma.wallenc.vault.contract.RemoteVaultAuthenticator
import com.github.nullptroma.wallenc.vault.contract.VaultDescriptor
import com.github.nullptroma.wallenc.vault.contract.VaultRegistrar
import com.github.nullptroma.wallenc.vault.contract.VaultRegistration
import com.github.nullptroma.wallenc.vault.contract.described
import com.github.nullptroma.wallenc.vault.contract.remotes
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
import javax.inject.Inject

@HiltViewModel
@OptIn(ExperimentalCoroutinesApi::class)
class RemoteVaultsViewModel @Inject constructor(
    private val vaultsManager: IVaultsManager,
    private val vaultRegistrar: VaultRegistrar,
    val remoteAuthenticator: RemoteVaultAuthenticator,
    private val taskOrchestrator: ITaskOrchestrator,
    private val uiStrings: UiStringResolver,
) : ViewModelBase<RemoteVaultsScreenState>(RemoteVaultsScreenState()) {

    private val remoteItems = vaultsManager.vaults
        .flatMapLatest { all ->
            val remotes = all.described().remotes
            if (remotes.isEmpty()) {
                flowOf(emptyList())
            } else {
                combine(remotes.map { vault ->
                    combine(vault.isAvailable, vault.storagesScanInProgress) { available, scanInProgress ->
                        toRemoteItem(vault, available, scanInProgress)
                    }
                }) { arr -> arr.toList().filterNotNull() }
            }
        }

    val uiState = combine(
        remoteItems,
        state,
    ) { items, base ->
        base.copy(
            vaults = items,
        )
    }.stateIn(
        viewModelScope,
        SharingStarted.Eagerly,
        RemoteVaultsScreenState(),
    )

    fun setAddChoiceVisible(visible: Boolean) {
        updateState(state.value.copy(addChoiceVisible = visible))
    }

    fun setBusy(busy: Boolean) {
        updateState(state.value.copy(isBusy = busy))
    }

    fun onLinkSucceeded(registration: VaultRegistration) {
        setBusy(true)
        taskOrchestrator.enqueue(
            title = uiStrings(R.string.task_title_add_remote_vault),
            dispatcher = Dispatchers.IO,
            work = { ctx ->
                try {
                    ctx.reportProgress(null, TaskProgressLabel.VaultTask(VaultTaskStep.AddRemoteVault))
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_adding_vault))
                    vaultRegistrar.register(registration)
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_vault_added))
                } catch (e: Exception) {
                    ctx.log(TaskLogLevel.Error, uiStrings(R.string.task_log_add_vault_failed))
                } finally {
                    withContext(Dispatchers.Main.immediate) {
                        setBusy(false)
                        setAddChoiceVisible(false)
                    }
                }
            },
        )
    }

    fun requestDeleteVault(item: RemoteVaultListItem) {
        updateState(state.value.copy(vaultPendingDelete = item))
    }

    fun dismissDeleteVault() {
        updateState(state.value.copy(vaultPendingDelete = null))
    }

    fun confirmDeleteVault() {
        val pending = state.value.vaultPendingDelete ?: return
        val uuid = pending.uuid
        setBusy(true)
        taskOrchestrator.enqueue(
            title = uiStrings(R.string.task_title_remove_remote_vault),
            dispatcher = Dispatchers.IO,
            work = { ctx ->
                try {
                    ctx.reportProgress(null, TaskProgressLabel.VaultTask(VaultTaskStep.RemoveRemoteVault))
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_removing_remote_vault))
                    vaultRegistrar.unregister(uuid)
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_remote_vault_removed))
                } catch (e: Exception) {
                    ctx.log(TaskLogLevel.Error, uiStrings(R.string.task_log_remove_vault_failed))
                } finally {
                    withContext(Dispatchers.Main.immediate) {
                        setBusy(false)
                        dismissDeleteVault()
                    }
                }
            },
        )
    }

    fun retryVault(vaultUuid: java.util.UUID) {
        setBusy(true)
        taskOrchestrator.enqueue(
            title = uiStrings(R.string.task_title_retry_remote_vault),
            dispatcher = Dispatchers.IO,
            work = { ctx ->
                try {
                    ctx.reportProgress(null, TaskProgressLabel.VaultTask(VaultTaskStep.RetryRemoteVault))
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_retrying_vault))
                    vaultRegistrar.retry(vaultUuid)
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_retry_requested))
                } catch (e: Exception) {
                    ctx.log(TaskLogLevel.Error, uiStrings(R.string.task_log_retry_vault_failed))
                } finally {
                    withContext(Dispatchers.Main.immediate) {
                        setBusy(false)
                    }
                }
            },
        )
    }

    private fun toRemoteItem(
        vault: DescribedVault,
        isAvailable: Boolean,
        isRefreshing: Boolean,
    ): RemoteVaultListItem? {
        val descriptor = vault.descriptor as? VaultDescriptor.LinkedRemote ?: return null
        return RemoteVaultListItem(
            uuid = descriptor.uuid,
            brand = descriptor.brand,
            label = descriptor.accountDisplayName,
            isAvailable = isAvailable,
            isRefreshing = isRefreshing,
        )
    }
}
