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

import androidx.annotation.StringRes
import androidx.lifecycle.viewModelScope
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
import com.github.nullptroma.wallenc.domain.datatypes.StorageMetaLoadState
import com.github.nullptroma.wallenc.domain.datatypes.Tree
import com.github.nullptroma.wallenc.domain.errors.WallencException
import com.github.nullptroma.wallenc.domain.errors.toWallencException
import java.io.IOException
import com.github.nullptroma.wallenc.domain.interfaces.ILogger
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo
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.TaskRunState
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.ui.resources.UserNotification
import com.github.nullptroma.wallenc.ui.resources.toUserNotification
import com.github.nullptroma.wallenc.usecases.GetOpenedStoragesUseCase
import com.github.nullptroma.wallenc.usecases.ManageStoragesEncryptionUseCase
import com.github.nullptroma.wallenc.usecases.ManageVaultUseCase
import com.github.nullptroma.wallenc.usecases.RemoveStorageUseCase
import com.github.nullptroma.wallenc.usecases.RenameStorageUseCase
import com.github.nullptroma.wallenc.usecases.StorageFileManagementUseCase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.util.UUID

/**
 * Общая логика дерева storages для локального и удалённого vault (presentation).
 */
abstract class AbstractVaultBrowserViewModel(
    storagesFlow: Flow<List<IStorage>>,
    private val storagesScanInProgressFlow: Flow<Boolean> = flowOf(false),
    private val vaultHeaderFlow: Flow<VaultBrowserHeader?> = flowOf(null),
    private val vaultAvailabilityFlow: Flow<Boolean>,
    private val resolveCreateVaultUuid: () -> UUID?,
    private val removeStorageUseCase: RemoveStorageUseCase,
    private val getOpenedStoragesUseCase: GetOpenedStoragesUseCase,
    private val storageFileManagementUseCase: StorageFileManagementUseCase,
    private val manageStoragesEncryptionUseCase: ManageStoragesEncryptionUseCase,
    private val renameStorageUseCase: RenameStorageUseCase,
    private val manageVaultUseCase: ManageVaultUseCase,
    private val taskOrchestrator: ITaskOrchestrator,
    private val uiStrings: UiStringResolver,
    private val logger: ILogger,
) : ViewModelBase<VaultBrowserScreenState>(
    VaultBrowserScreenState(
        storagesList = emptyList(),
        storagesRefreshing = true,
        busyStorageUuids = emptySet(),
        vaultListMutationActive = false,
        addStorageFabEnabled = false,
    ),
) {

    private val _userNotifications = MutableSharedFlow<UserNotification>(extraBufferCapacity = 8)
    val userNotifications: SharedFlow<UserNotification> = _userNotifications

    /** Удалённый vault: показать кнопку повторного сканирования storages на Диске. */
    open val supportsStorageRescan: Boolean = false

    init {
        collectStoragesFlow(storagesFlow)
        collectVaultHeaderFlow()
        collectPipelineBusyFlags()
        viewModelScope.launch {
            vaultAvailabilityFlow
                .distinctUntilChanged()
                .collect { available ->
                    updateState(state.value.copy(addStorageFabEnabled = available))
                    logger.debug(TAG, "vault availability → add FAB enabled=$available")
                }
        }
    }

    private fun isPipelineTaskActive(state: TaskRunState): Boolean =
        when (state) {
            is TaskRunState.Queued,
            is TaskRunState.Running,
            -> true
            else -> false
        }

    private fun isStorageTaskActive(storageUuid: UUID): Boolean =
        taskOrchestrator.pipelineState.value.tasks.any { t ->
            t.busyStorageUuid == storageUuid && isPipelineTaskActive(t.state)
        }

    private fun isVaultListMutationActive(): Boolean =
        taskOrchestrator.pipelineState.value.tasks.any { t ->
            t.locksVaultStorageList && isPipelineTaskActive(t.state)
        }

    private fun collectVaultHeaderFlow() {
        viewModelScope.launch {
            vaultHeaderFlow.collect { header ->
                updateState(state.value.copy(header = header))
            }
        }
    }

    private fun collectStoragesFlow(storagesFlow: Flow<List<IStorage>>) {
        viewModelScope.launch {
            combine(
                storagesFlow,
                storagesScanInProgressFlow,
                getOpenedStoragesUseCase.openedStorages,
            ) { storages, scanInProgress, opened ->
                Triple(storages, scanInProgress, opened)
            }.collect { (storages, scanInProgress, opened) ->
                val list = mutableListOf<Tree<IStorageInfo>>()
                for (storage in storages) {
                    var tree = Tree<IStorageInfo>(storage)
                    list.add(tree)
                    while (opened.containsKey(tree.value.uuid)) {
                        val child = opened.getValue(tree.value.uuid)
                        val nextTree = Tree(child)
                        tree.children = listOf(nextTree)
                        tree = nextTree
                    }
                }
                updateState(
                    state.value.copy(
                        storagesList = list,
                        storagesRefreshing = scanInProgress,
                    ),
                )
            }
        }
    }

    private fun collectPipelineBusyFlags() {
        viewModelScope.launch {
            taskOrchestrator.pipelineState
                .map { pipe ->
                    val activeTasks = pipe.tasks.filter { isPipelineTaskActive(it.state) }
                    val busyUuids = activeTasks.mapNotNull { it.busyStorageUuid }.toSet()
                    val listMut = activeTasks.any { it.locksVaultStorageList }
                    busyUuids to listMut
                }
                .distinctUntilChanged()
                .collect { (busyUuids, listMut) ->
                    updateState(
                        state.value.copy(
                            busyStorageUuids = busyUuids,
                            vaultListMutationActive = listMut,
                        ),
                    )
                }
        }
    }

    private fun notifyUser(@StringRes messageRes: Int) {
        viewModelScope.launch {
            _userNotifications.emit(UserNotification.TextRes(messageRes))
        }
    }

    fun rescanStorages() {
        if (!supportsStorageRescan) return
        if (state.value.storagesRefreshing) {
            notifyUser(R.string.vault_msg_rescan_already_in_progress)
            return
        }
        if (isVaultListMutationActive()) {
            notifyUser(R.string.vault_msg_vault_list_mutation_busy)
            return
        }
        val vaultUuid = resolveCreateVaultUuid() ?: return
        taskOrchestrator.enqueue(
            title = uiStrings(R.string.task_title_rescan_vault_storages),
            dispatcher = Dispatchers.IO,
            locksVaultStorageList = true,
            work = { ctx ->
                try {
                    ctx.reportProgress(null, TaskProgressLabel.VaultTask(VaultTaskStep.RescanVaultStorages))
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_rescanning_vault_storages))
                    manageVaultUseCase.rescanStorages(vaultUuid)
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_rescan_vault_storages_done))
                    val vault = manageVaultUseCase.find(vaultUuid)
                    if (vault != null && !vault.isAvailable.value) {
                        emitTaskError(WallencException.Network.IoFailed(IOException("Vault unreachable")))
                    }
                } catch (e: Exception) {
                    logger.debug(TAG, "rescanStorages failed: ${e.stackTraceToString()}")
                    ctx.log(TaskLogLevel.Error, uiStrings(R.string.task_log_rescan_vault_storages_failed))
                    emitTaskError(e)
                }
            },
        )
    }

    fun createStorage() {
        if (!state.value.addStorageFabEnabled) {
            logger.debug(TAG, "createStorage ignored (vault unavailable or FAB disabled)")
            return
        }
        if (isVaultListMutationActive()) {
            logger.debug(TAG, "createStorage ignored (vault list mutation already running)")
            notifyUser(R.string.vault_msg_vault_list_mutation_busy)
            return
        }
        logger.debug(TAG, "createStorage: enqueue task")
        taskOrchestrator.enqueue(
            title = uiStrings(R.string.task_title_create_storage),
            dispatcher = Dispatchers.IO,
            locksVaultStorageList = true,
            work = { ctx ->
                try {
                    ctx.reportProgress(null, TaskProgressLabel.VaultTask(VaultTaskStep.CreateStorage))
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_creating_storage))
                    val uuid = resolveCreateVaultUuid()
                        ?: throw IllegalStateException("Vault is not available")
                    logger.debug(TAG, "createStorage: vaultUuid=$uuid")
                    val storage = manageVaultUseCase.createStorage(uuid)
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_storage_created))
                    logger.debug(TAG, "createStorage: done storageUuid=${storage.uuid}")
                } catch (e: Exception) {
                    logger.debug(TAG, "createStorage failed: ${e.stackTraceToString()}")
                    ctx.log(TaskLogLevel.Error, uiStrings(R.string.task_log_add_vault_failed))
                    emitTaskError(e)
                }
            },
        )
    }

    fun enableEncryption(
        storage: IStorageInfo,
        password: String,
        encryptPath: Boolean,
        rememberPassword: Boolean,
    ) {
        val id = storage.uuid
        if (isStorageTaskActive(id)) {
            notifyUser(R.string.vault_msg_storage_pipeline_busy)
            return
        }
        val key = EncryptKey(password)
        taskOrchestrator.enqueue(
            title = uiStrings(R.string.task_title_enable_encryption),
            dispatcher = Dispatchers.IO,
            busyStorageUuid = id,
            work = { ctx ->
                try {
                    ctx.reportProgress(null, TaskProgressLabel.VaultTask(VaultTaskStep.EnableEncryption))
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_checking_storage))
                    when (manageStoragesEncryptionUseCase.canEncrypt(storage)) {
                        ManageStoragesEncryptionUseCase.CanEncryptResult.Allowed -> {
                            ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_encrypting))
                            manageStoragesEncryptionUseCase.enableEncryption(storage, key, encryptPath)
                            if (rememberPassword) {
                                manageStoragesEncryptionUseCase.rememberStorageKey(storage, key)
                            }
                            try {
                                manageStoragesEncryptionUseCase.openStorage(
                                    storage,
                                    key,
                                    rememberPassword = false,
                                )
                                ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_encryption_enabled))
                                _userNotifications.emit(UserNotification.TextRes(R.string.msg_encryption_enabled))
                            } catch (openError: Exception) {
                                logger.debug(TAG, "open after encrypt failed: ${openError.stackTraceToString()}")
                                ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_encryption_enabled))
                                _userNotifications.emit(
                                    UserNotification.TextRes(R.string.msg_encryption_enabled_open_failed),
                                )
                            }
                        }
                        ManageStoragesEncryptionUseCase.CanEncryptResult.AlreadyEncrypted -> {
                            ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_already_encrypted))
                            _userNotifications.emit(UserNotification.TextRes(R.string.msg_storage_already_encrypted))
                        }
                        ManageStoragesEncryptionUseCase.CanEncryptResult.StorageIsNotEmpty -> {
                            ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_not_empty))
                            _userNotifications.emit(UserNotification.TextRes(R.string.msg_storage_not_empty))
                        }
                        ManageStoragesEncryptionUseCase.CanEncryptResult.StorageStateUnknown -> {
                            ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_empty_unknown))
                            _userNotifications.emit(UserNotification.TextRes(R.string.msg_storage_empty_state_unknown))
                        }
                        ManageStoragesEncryptionUseCase.CanEncryptResult.UnsupportedStorageType -> {
                            ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_unsupported_type))
                            _userNotifications.emit(UserNotification.TextRes(R.string.msg_unsupported_storage_type))
                        }
                    }
                } catch (e: Exception) {
                    ctx.log(TaskLogLevel.Error, uiStrings(R.string.task_log_enable_encryption_failed))
                    emitTaskError(e)
                }
            },
        )
    }

    fun openEncryptedStorage(storage: IStorageInfo, password: String, rememberPassword: Boolean) {
        val id = storage.uuid
        if (isStorageTaskActive(id)) {
            notifyUser(R.string.vault_msg_storage_pipeline_busy)
            return
        }
        val key = EncryptKey(password)
        taskOrchestrator.enqueue(
            title = uiStrings(R.string.task_title_open_encrypted_storage),
            dispatcher = Dispatchers.IO,
            busyStorageUuid = id,
            work = { ctx ->
                try {
                    ctx.reportProgress(null, TaskProgressLabel.VaultTask(VaultTaskStep.DecryptRunning))
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_opening_storage))
                    manageStoragesEncryptionUseCase.openStorage(storage, key, rememberPassword)
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_storage_opened))
                } catch (e: Exception) {
                    ctx.log(TaskLogLevel.Error, uiStrings(R.string.task_log_open_storage_failed))
                    emitTaskError(e)
                }
            },
        )
    }

    fun closeEncryptedStorage(storage: IStorageInfo) {
        val id = storage.uuid
        if (isStorageTaskActive(id)) {
            notifyUser(R.string.vault_msg_storage_pipeline_busy)
            return
        }
        taskOrchestrator.enqueue(
            title = uiStrings(R.string.task_title_close_encrypted_storage),
            dispatcher = Dispatchers.IO,
            busyStorageUuid = id,
            work = { ctx ->
                try {
                    ctx.reportProgress(null, TaskProgressLabel.VaultTask(VaultTaskStep.CloseStorage))
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_closing_storage))
                    manageStoragesEncryptionUseCase.closeStorage(storage)
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_storage_closed))
                } catch (e: Exception) {
                    ctx.log(TaskLogLevel.Error, uiStrings(R.string.task_log_close_storage_failed))
                    emitTaskError(e)
                }
            },
        )
    }

    fun disableEncryption(storage: IStorageInfo) {
        val id = storage.uuid
        if (isStorageTaskActive(id)) {
            notifyUser(R.string.vault_msg_storage_pipeline_busy)
            return
        }
        taskOrchestrator.enqueue(
            title = uiStrings(R.string.task_title_disable_encryption),
            dispatcher = Dispatchers.IO,
            busyStorageUuid = id,
            work = { ctx ->
                try {
                    ctx.reportProgress(null, TaskProgressLabel.VaultTask(VaultTaskStep.DisableEncryption))
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_disabling_encryption))
                    manageStoragesEncryptionUseCase.clearAndDisableEncryption(storage) { p ->
                        ctx.reportProgress(
                            p.fraction,
                            p.label ?: TaskProgressLabel.VaultTask(VaultTaskStep.DisableEncryption),
                        )
                    }
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_encryption_disabled))
                    _userNotifications.emit(UserNotification.TextRes(R.string.msg_encryption_disabled))
                } catch (e: Exception) {
                    ctx.log(TaskLogLevel.Error, uiStrings(R.string.task_log_disable_encryption_failed))
                    emitTaskError(e)
                }
            },
        )
    }

    fun rename(storage: IStorageInfo, newName: String) {
        val id = storage.uuid
        if (isStorageTaskActive(id)) {
            notifyUser(R.string.vault_msg_storage_pipeline_busy)
            return
        }
        taskOrchestrator.enqueue(
            title = uiStrings(R.string.task_title_rename_storage),
            dispatcher = Dispatchers.IO,
            busyStorageUuid = id,
            work = { ctx ->
                try {
                    ctx.reportProgress(null, TaskProgressLabel.VaultTask(VaultTaskStep.RenameStorage))
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_renaming))
                    renameStorageUseCase.rename(storage, newName)
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_renamed))
                } catch (e: Exception) {
                    ctx.log(TaskLogLevel.Error, uiStrings(R.string.task_log_rename_failed))
                }
            },
        )
    }

    fun remove(storage: IStorageInfo) {
        val id = storage.uuid
        if (isStorageTaskActive(id)) {
            notifyUser(R.string.vault_msg_storage_pipeline_busy)
            return
        }
        taskOrchestrator.enqueue(
            title = uiStrings(R.string.task_title_remove_storage),
            dispatcher = Dispatchers.IO,
            busyStorageUuid = id,
            locksVaultStorageList = true,
            work = { ctx ->
                try {
                    ctx.reportProgress(null, TaskProgressLabel.VaultTask(VaultTaskStep.RemoveStorage))
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_removing_storage))
                    removeStorageUseCase.remove(storage)
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_removed))
                } catch (e: Exception) {
                    ctx.log(TaskLogLevel.Error, uiStrings(R.string.task_log_remove_failed))
                }
            },
        )
    }

    @StringRes
    fun getStorageStatusRes(storage: IStorageInfo): Int {
        if (storage.metaLoadState.value == StorageMetaLoadState.Unavailable) {
            return R.string.storage_status_meta_unavailable
        }
        val encrypted = storage.metaInfo.value.encInfo != null
        if (!encrypted) return R.string.storage_status_not_encrypted
        val opened = isEncryptionSessionOpen(storage)
        return if (opened) R.string.storage_status_encrypted_open else R.string.storage_status_encrypted_closed
    }

    fun isEncryptionSessionOpen(storage: IStorageInfo): Boolean {
        val openedMap = getOpenedStoragesUseCase.openedStorages.value
        return openedMap.containsKey(storage.uuid)
    }

    suspend fun isStorageSyncLockHeld(storage: IStorageInfo): Boolean {
        val s = storage as? IStorage ?: return false
        return try {
            s.accessor.readSyncLock() != null
        } catch (_: Exception) {
            false
        }
    }

    fun clearStorageSyncLock(storage: IStorageInfo) {
        val id = storage.uuid
        if (isStorageTaskActive(id)) {
            notifyUser(R.string.vault_msg_storage_pipeline_busy)
            return
        }
        taskOrchestrator.enqueue(
            title = uiStrings(R.string.task_title_clear_sync_lock),
            dispatcher = Dispatchers.IO,
            busyStorageUuid = id,
            work = { ctx ->
                try {
                    val s = storage as? IStorage
                    if (s == null) {
                        ctx.log(TaskLogLevel.Error, uiStrings(R.string.task_log_invalid_storage))
                        _userNotifications.emit(UserNotification.TextRes(R.string.msg_invalid_storage_for_sync_lock))
                        return@enqueue
                    }
                    ctx.reportProgress(null, TaskProgressLabel.VaultTask(VaultTaskStep.ClearSyncLock))
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_clearing_sync_lock))
                    s.accessor.forceClearSyncLock()
                    ctx.log(TaskLogLevel.Info, uiStrings(R.string.task_log_sync_lock_cleared))
                    _userNotifications.emit(UserNotification.TextRes(R.string.msg_sync_lock_cleared))
                } catch (e: Exception) {
                    ctx.log(TaskLogLevel.Error, uiStrings(R.string.task_log_clear_sync_lock_failed))
                    emitTaskError(e)
                }
            },
        )
    }

    private suspend fun emitTaskError(e: Exception) {
        _userNotifications.emit(e.toWallencException().toUserNotification())
    }

    private companion object {
        private const val TAG = "VaultBrowser"
    }
}
