package de.tsschulz.tt_tagebuch.shared.state

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.cinterop.alloc
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.ptr
import kotlinx.cinterop.refTo
import kotlinx.cinterop.memcpy
import platform.CoreFoundation.CFDictionaryRef
import platform.CoreFoundation.CFTypeRefVar
import platform.Foundation.NSData
import platform.Foundation.NSString
import platform.Foundation.NSUTF8StringEncoding
import platform.Foundation.dataUsingEncoding
import platform.Security.SecItemAdd
import platform.Security.SecItemCopyMatching
import platform.Security.SecItemDelete
import platform.Security.errSecSuccess
import platform.Security.kSecAttrAccount
import platform.Security.kSecAttrService
import platform.Security.kSecClass
import platform.Security.kSecClassGenericPassword
import platform.Security.kSecMatchLimit
import platform.Security.kSecMatchLimitOne
import platform.Security.kSecReturnData
import platform.Security.kSecValueData

class IosTokenStorage : TokenStorage {
    override suspend fun load(): AuthTokens? = withContext(Dispatchers.Default) {
        val token = loadString(KEY_TOKEN)
        val username = loadString(KEY_USERNAME)
        if (token.isNullOrBlank() || username.isNullOrBlank()) null else AuthTokens(token, username)
    }

    override suspend fun save(tokens: AuthTokens) = withContext(Dispatchers.Default) {
        saveString(KEY_TOKEN, tokens.token)
        saveString(KEY_USERNAME, tokens.username)
    }

    override suspend fun clear() = withContext(Dispatchers.Default) {
        delete(KEY_TOKEN)
        delete(KEY_USERNAME)
    }

    private fun loadString(key: String): String? = memScoped {
        val query = keychainQuery(
            account = key,
            extra = mapOf(
                kSecReturnData to true,
                kSecMatchLimit to kSecMatchLimitOne,
            ),
        )

        val result = alloc<CFTypeRefVar>()
        val status = SecItemCopyMatching(query as CFDictionaryRef, result.ptr)
        if (status != errSecSuccess) return@memScoped null

        val data = result.value as? NSData ?: return@memScoped null
        val bytes = data.bytes ?: return@memScoped null
        val length = data.length.toInt()
        val buffer = ByteArray(length)
        memcpy(buffer.refTo(0), bytes, data.length)
        buffer.decodeToString()
    }

    private fun saveString(key: String, value: String) {
        delete(key)
        val data = (value as NSString).dataUsingEncoding(NSUTF8StringEncoding) ?: return
        val query = keychainQuery(
            account = key,
            extra = mapOf(kSecValueData to data),
        )
        SecItemAdd(query as CFDictionaryRef, null)
    }

    private fun delete(key: String) {
        SecItemDelete(keychainQuery(key) as CFDictionaryRef)
    }

    private fun keychainQuery(account: String, extra: Map<Any?, Any?> = emptyMap()): Map<Any?, Any?> {
        return mapOf(
            kSecClass to kSecClassGenericPassword,
            kSecAttrService to SERVICE_NAME,
            kSecAttrAccount to account,
        ) + extra
    }

    private companion object {
        private const val SERVICE_NAME = "de.tsschulz.tt_tagebuch"
        private const val KEY_TOKEN = "token"
        private const val KEY_USERNAME = "username"
    }
}
