package com.github.nullptroma.wallenc.domain.vault.vaults

import com.github.nullptroma.wallenc.domain.vault.model.YandexAccount
import com.github.nullptroma.wallenc.domain.vault.network.yandexdisk.repository.YandexDiskRepositoryFactory
import com.github.nullptroma.wallenc.domain.vault.network.yandexuserinfo.repository.YandexUserInfoRepository
import com.github.nullptroma.wallenc.domain.vault.ports.StorageKeyMapStore
import com.github.nullptroma.wallenc.domain.vault.ports.YandexAccountStore
import com.github.nullptroma.wallenc.domain.vault.storages.UnlockManager
import com.github.nullptroma.wallenc.domain.vault.vaults.yandex.YandexRegistration
import com.github.nullptroma.wallenc.domain.vault.vaults.yandex.YandexVault
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager
import com.github.nullptroma.wallenc.domain.interfaces.IVault
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
import com.github.nullptroma.wallenc.vault.contract.VaultRegistrar
import com.github.nullptroma.wallenc.vault.contract.VaultRegistration
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
import java.util.UUID

@OptIn(ExperimentalCoroutinesApi::class)
class VaultsManager(
    private val ioDispatcher: CoroutineDispatcher,
    private val localVault: IVault,
    keyRepo: StorageKeyMapStore,
    private val yandexAccountStore: YandexAccountStore,
    private val yandexUserInfoRepository: YandexUserInfoRepository,
    private val yandexDiskRepositoryFactory: YandexDiskRepositoryFactory,
) : IVaultsManager, VaultRegistrar {

    private val scope = CoroutineScope(
        SupervisorJob() +
            ioDispatcher +
            CoroutineExceptionHandler { _, throwable ->
                System.err.println("VaultsManager: uncaught coroutine failure: ${throwable.message}")
                throwable.printStackTrace()
            },
    )

    private val yandexVaults: StateFlow<List<IVault>> = yandexAccountStore.observeAll()
        .map { rows ->
            rows.map { row ->
                val vaultUuid = UUID.fromString(row.vaultUuid)
                YandexVault(
                    uuid = vaultUuid,
                    accountEmail = row.email,
                    repo = yandexDiskRepositoryFactory.create(vaultUuid),
                    ioDispatcher = ioDispatcher,
                    parentScope = scope,
                )
            }
        }
        .stateIn(scope, SharingStarted.Eagerly, emptyList())

    override val vaults: StateFlow<List<IVault>> = yandexVaults
        .map { remote -> listOf(localVault) + remote }
        .stateIn(scope, SharingStarted.Eagerly, listOf(localVault))

    override val allStorages: StateFlow<List<IStorage>> = vaults
        .flatMapLatest { vs ->
            if (vs.isEmpty()) flowOf(emptyList())
            else combine(vs.map { it.storages }) { arr -> arr.toList().flatten() }
        }
        .stateIn(scope, SharingStarted.Eagerly, emptyList())

    override val unlockManager: IUnlockManager = UnlockManager(
        keymapRepository = keyRepo,
        ioDispatcher = ioDispatcher,
        vaultsManager = this,
    )

    override suspend fun register(registration: VaultRegistration) = withContext(ioDispatcher) {
        when (registration) {
            is YandexRegistration -> registerYandex(registration)
            else -> throw IllegalArgumentException(
                "Unknown VaultRegistration type: ${registration::class.qualifiedName}",
            )
        }
    }

    override suspend fun unregister(vaultUuid: UUID): Unit = withContext(ioDispatcher) {
        yandexAccountStore.deleteByVaultUuid(vaultUuid.toString())
    }

    override suspend fun retry(vaultUuid: UUID): Boolean = withContext(ioDispatcher) {
        val account = yandexAccountStore.getByVaultUuid(vaultUuid.toString()) ?: return@withContext false
        yandexAccountStore.updateCredentials(
            vaultUuid = account.vaultUuid,
            email = account.email,
            token = account.oauthToken,
        )
        true
    }

    private suspend fun registerYandex(registration: YandexRegistration) {
        val token = registration.oauthToken
        val info = yandexUserInfoRepository.userInfo(token)
        val email = info.defaultEmail?.takeIf { it.isNotBlank() }
            ?: "${info.login}@yandex.ru"
        val existing = yandexAccountStore.getByYandexUserId(info.id)
        val vaultUuid = existing?.vaultUuid ?: UUID.randomUUID().toString()
        if (existing != null) {
            yandexAccountStore.updateCredentials(vaultUuid, email, token)
        } else {
            yandexAccountStore.insert(
                YandexAccount(
                    vaultUuid = vaultUuid,
                    yandexUserId = info.id,
                    email = email,
                    oauthToken = token,
                ),
            )
        }
    }
}
