package com.github.nullptroma.wallenc.infrastructure.android.db.app.repository

import com.github.nullptroma.wallenc.domain.interfaces.IStorageSyncGroupStore
import com.github.nullptroma.wallenc.domain.interfaces.StorageSyncGroup
import com.github.nullptroma.wallenc.domain.interfaces.StorageSyncGroupEncryptionKind
import com.github.nullptroma.wallenc.infrastructure.android.db.app.dao.StorageSyncGroupDao
import com.github.nullptroma.wallenc.infrastructure.android.db.app.model.DbStorageSyncGroup
import com.github.nullptroma.wallenc.domain.vault.utils.IProvider
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import java.util.UUID

class StorageSyncGroupRepository(
    private val dao: StorageSyncGroupDao,
    private val ioDispatcher: CoroutineDispatcher,
) : IStorageSyncGroupStore {
    private val allGroupsProvider: IProvider<List<StorageSyncGroup>> = object : IProvider<List<StorageSyncGroup>> {
        override suspend fun get(): List<StorageSyncGroup> = dao.getAll().mapNotNull(::toDomain)

        override suspend fun set(value: List<StorageSyncGroup>) {
            val desiredIds = value.map { it.id }.toSet()
            val current = dao.getAll()
            current
                .asSequence()
                .filter { it.id !in desiredIds }
                .forEach { dao.deleteById(it.id) }
            value.forEach { group ->
                dao.upsert(toDb(group))
            }
        }

        override suspend fun clear() {
            dao.getAll().forEach { dao.deleteById(it.id) }
        }
    }

    override suspend fun getGroups(): List<StorageSyncGroup> = withContext(ioDispatcher) {
        allGroupsProvider.get().orEmpty()
    }

    override suspend fun putGroup(group: StorageSyncGroup) = withContext(ioDispatcher) {
        val all = allGroupsProvider.get().orEmpty().toMutableList()
        val idx = all.indexOfFirst { it.id == group.id }
        if (idx >= 0) {
            all[idx] = group
        } else {
            all.add(group)
        }
        allGroupsProvider.set(all)
    }

    override suspend fun removeGroup(groupId: String) = withContext(ioDispatcher) {
        val all = allGroupsProvider.get().orEmpty().filterNot { it.id == groupId }
        allGroupsProvider.set(all)
    }

    private fun toDb(group: StorageSyncGroup): DbStorageSyncGroup = DbStorageSyncGroup(
        id = group.id,
        storageUuidsCsv = group.storageUuids.joinToString(",") { it.toString() },
        encryptionKind = group.encryptionKind.name,
        encryptionSecret = group.encryptionSecret,
    )

    private fun toDomain(db: DbStorageSyncGroup): StorageSyncGroup? {
        val kind = runCatching {
            StorageSyncGroupEncryptionKind.valueOf(db.encryptionKind)
        }.getOrElse {
            StorageSyncGroupEncryptionKind.UNSET
        }
        if (db.id.isBlank()) {
            return null
        }
        val uuids = db.storageUuidsCsv
            .split(",")
            .mapNotNull { token ->
                val value = token.trim()
                if (value.isBlank()) {
                    null
                } else {
                    runCatching { UUID.fromString(value) }.getOrNull()
                }
            }
            .toSet()
        return StorageSyncGroup(
            id = db.id,
            storageUuids = uuids,
            encryptionKind = kind,
            encryptionSecret = db.encryptionSecret?.takeIf { it.isNotBlank() },
        )
    }
}
