package com.github.nullptroma.wallenc.ui.screens.main.screens.storage.secrets

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.github.nullptroma.wallenc.domain.datatypes.TextSecretEntryRecord
import com.github.nullptroma.wallenc.domain.datatypes.TextSecretRecord
import com.github.nullptroma.wallenc.domain.tasks.ITaskOrchestrator
import com.github.nullptroma.wallenc.domain.tasks.TaskProgressLabel
import com.github.nullptroma.wallenc.domain.tasks.VaultTaskStep
import com.github.nullptroma.wallenc.domain.tasks.TaskId
import com.github.nullptroma.wallenc.domain.tasks.TaskRunState
import com.github.nullptroma.wallenc.ui.R
import com.github.nullptroma.wallenc.domain.errors.WallencException
import com.github.nullptroma.wallenc.ui.ViewModelBase
import com.github.nullptroma.wallenc.ui.resources.toUserNotification
import com.github.nullptroma.wallenc.ui.resources.UiStringResolver
import com.github.nullptroma.wallenc.ui.screens.main.screens.storage.optionalSecretId
import com.github.nullptroma.wallenc.ui.screens.main.screens.storage.requireStorageUuid
import com.github.nullptroma.wallenc.usecases.FindStorageUseCase
import com.github.nullptroma.wallenc.usecases.ManageTextSecretsUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.util.UUID
import javax.inject.Inject

@HiltViewModel
class TextSecretEditViewModel @Inject constructor(
    savedStateHandle: SavedStateHandle,
    private val findStorageUseCase: FindStorageUseCase,
    private val manageTextSecretsUseCase: ManageTextSecretsUseCase,
    private val taskOrchestrator: ITaskOrchestrator,
    private val uiStrings: UiStringResolver,
) : ViewModelBase<TextSecretEditScreenState>(TextSecretEditScreenState()) {

    private val storageUuid = savedStateHandle.requireStorageUuid()
    private val secretId: String? = savedStateHandle.optionalSecretId()

    init {
        load()
    }

    private fun load() {
        viewModelScope.launch {
            val storage = findStorageUseCase.find(storageUuid)
            if (storage == null) {
                updateState(
                    state.value.copy(
                        isLoading = false,
                        errorNotification = WallencException.Feature.StorageNotFound().toUserNotification(),
                    ),
                )
                return@launch
            }
            if (storage.metaInfo.value.encInfo != null && !storage.isVirtualStorage) {
                updateState(
                    state.value.copy(
                        isLoading = false,
                        isAvailable = false,
                        errorNotification = WallencException.Feature.NeedsDecryptedView().toUserNotification(),
                    ),
                )
                return@launch
            }
            val initial = secretId?.let { id -> manageTextSecretsUseCase.get(storage, id) }
            combine(
                storage.isAvailable,
                taskOrchestrator.pipelineState.map { pipe ->
                    pipe.tasks.any { t ->
                        t.busyStorageUuid == storage.uuid && isTaskActive(t.state)
                    }
                },
                flowOf(initial),
            ) { available, isMutating, currentSecret ->
                state.value.copy(
                    isLoading = false,
                    isAvailable = available,
                    isMutating = isMutating,
                    initialSecret = currentSecret,
                    errorNotification = null,
                )
            }.collect { ui ->
                updateState(ui)
            }
        }
    }

    fun save(
        title: String,
        items: List<TextSecretEntryRecord>,
        onSaved: (secretId: String) -> Unit,
    ) {
        viewModelScope.launch {
            val storage = findStorageUseCase.find(storageUuid) ?: return@launch
            if (storage.metaInfo.value.encInfo != null && !storage.isVirtualStorage) {
                updateState(
                    state.value.copy(
                        errorNotification = WallencException.Feature.NeedsDecryptedView().toUserNotification(),
                    ),
                )
                return@launch
            }
            val existingId = secretId
            val targetSecretId = existingId ?: UUID.randomUUID().toString()
            val taskId = taskOrchestrator.enqueue(
                title = uiStrings(R.string.task_title_save_text_secret),
                dispatcher = Dispatchers.IO,
                busyStorageUuid = storage.uuid,
                work = { ctx ->
                    ctx.reportProgress(null, TaskProgressLabel.VaultTask(VaultTaskStep.SaveTextSecret))
                    if (existingId == null) {
                        manageTextSecretsUseCase.create(
                            storageInfo = storage,
                            title = title,
                            items = items,
                            id = targetSecretId,
                        )
                    } else {
                        manageTextSecretsUseCase.update(
                            storageInfo = storage,
                            secret = TextSecretRecord(
                                id = targetSecretId,
                                title = title,
                                items = items,
                            ),
                        )
                    }
                },
            )
            if (awaitTaskTerminalState(taskId) is TaskRunState.Completed) {
                onSaved(targetSecretId)
            }
        }
    }

    private fun isTaskActive(state: TaskRunState): Boolean =
        state is TaskRunState.Queued || state is TaskRunState.Running

    private suspend fun awaitTaskTerminalState(taskId: TaskId): TaskRunState {
        return taskOrchestrator.pipelineState
            .map { pipe -> pipe.tasks.firstOrNull { it.id == taskId }?.state }
            .filterNotNull()
            .first { state ->
                state is TaskRunState.Completed ||
                    state is TaskRunState.Cancelled ||
                    state is TaskRunState.Failed
            }
    }
}
