package com.github.nullptroma.wallenc.domain.vault.storages.encrypt

import com.github.nullptroma.wallenc.domain.errors.WallencException

import com.github.nullptroma.wallenc.domain.vault.storages.common.BaseStorage
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
import com.github.nullptroma.wallenc.domain.encrypt.Encryptor
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
import com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.withContext
import java.util.UUID

class EncryptedStorage private constructor(
    private val source: IStorage,
    private val key: EncryptKey,
    ioDispatcher: CoroutineDispatcher,
    uuid: UUID = UUID.randomUUID()
) : BaseStorage(
    uuid = uuid,
    ioDispatcher = ioDispatcher,
    metaInfoFilePostfix = STORAGE_INFO_FILE_POSTFIX,
), DisposableHandle {

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

    private val encInfo =
        source.metaInfo.value.encInfo ?: throw WallencException.Storage.NotEncrypted()

    override val isVirtualStorage: Boolean = true

    private val _accessor: EncryptedStorageAccessor =
        EncryptedStorageAccessor(
            source = source.accessor,
            pathIv = encInfo.pathIv,
            key = key,
            systemHiddenDirName = "${uuid.toString().take(8)}$SYSTEM_HIDDEN_DIRNAME_POSTFIX",
            scope = scope,
        )
    override val accessor: IStorageAccessor = _accessor

    override fun metaInfoUuidPart(): String = uuid.toString().take(8)

    override suspend fun init() {
        checkKey()
        super.init()
    }

    private fun checkKey() {
        if (!Encryptor.checkKey(key, encInfo))
            throw WallencException.Storage.IncorrectKey()
    }

    fun getKey(): EncryptKey = EncryptKey(key.bytes)

    override fun dispose() {
        _accessor.dispose()
        job.cancel()
    }

    companion object {
        suspend fun create(
            source: IStorage,
            key: EncryptKey,
            ioDispatcher: CoroutineDispatcher,
            uuid: UUID = UUID.randomUUID()
        ): EncryptedStorage = withContext(ioDispatcher) {
            val storage = EncryptedStorage(
                source = source,
                key = key,
                ioDispatcher = ioDispatcher,
                uuid = uuid
            )
            try {
                storage.init()
            } catch (e: Exception) {
                storage.dispose()
                throw e
            }
            return@withContext storage
        }

        private const val SYSTEM_HIDDEN_DIRNAME_POSTFIX = "-enc-dir"
        const val STORAGE_INFO_FILE_POSTFIX = ".enc-meta"
    }
}
