package app.closer.crypto

import android.content.Context
import android.content.SharedPreferences
import app.closer.data.local.SecurePreferencesFactory
import com.google.crypto.tink.Aead
import com.google.crypto.tink.CleartextKeysetHandle
import com.google.crypto.tink.JsonKeysetReader
import com.google.crypto.tink.JsonKeysetWriter
import com.google.crypto.tink.KeysetHandle
import dagger.hilt.android.qualifiers.ApplicationContext
import java.io.ByteArrayOutputStream
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
import javax.inject.Singleton

/**
 * Persists Tink keyset handles in EncryptedSharedPreferences (Keystore-backed).
 * Keys are namespaced by coupleId ("keyset_{coupleId}") or invite slot
 * ("keyset_invite_{inviteCode}") for the inviter reconciliation path.
 */
@Singleton
class CoupleKeyStore @Inject constructor(
    @ApplicationContext context: Context
) {
    private val prefs: SharedPreferences =
        SecurePreferencesFactory.encryptedSharedPreferences(context, PREFS_NAME)

    private val aeadCache = ConcurrentHashMap<String, Aead>()

    fun hasKeyset(coupleId: String): Boolean =
        prefs.contains(prefKey(coupleId))

    fun hasInviteKeyset(inviteCode: String): Boolean =
        prefs.contains(invitePrefKey(inviteCode))

    fun storeKeyset(coupleId: String, handle: KeysetHandle) {
        val json = serialize(handle)
        prefs.edit().putString(prefKey(coupleId), json).apply()
        aeadCache[coupleId] = handle.getPrimitive(Aead::class.java)
    }

    fun storeInviteKeyset(inviteCode: String, handle: KeysetHandle) {
        val json = serialize(handle)
        prefs.edit().putString(invitePrefKey(inviteCode), json).apply()
    }

    fun storeInvitePhrase(inviteCode: String, phrase: String) {
        prefs.edit().putString(invitePhrasePrefKey(inviteCode), phrase).apply()
    }

    fun storeRecoveryPhrase(coupleId: String, phrase: String) {
        prefs.edit().putString(recoveryPhraseKey(coupleId), phrase).apply()
    }

    fun loadRecoveryPhrase(coupleId: String): String? =
        prefs.getString(recoveryPhraseKey(coupleId), null)

    fun loadKeyset(coupleId: String): KeysetHandle? =
        load(prefKey(coupleId))

    fun loadInviteKeyset(inviteCode: String): KeysetHandle? =
        load(invitePrefKey(inviteCode))

    /** Moves the invite-slot keyset (and phrase) to the coupleId slot and removes the invite slots. */
    fun reconcileInviteKeyset(inviteCode: String, coupleId: String): Boolean {
        val handle = loadInviteKeyset(inviteCode) ?: return false
        storeKeyset(coupleId, handle)
        prefs.edit().remove(invitePrefKey(inviteCode)).also { editor ->
            val phrase = prefs.getString(invitePhrasePrefKey(inviteCode), null)
            if (phrase != null) {
                editor.putString(recoveryPhraseKey(coupleId), phrase)
                editor.remove(invitePhrasePrefKey(inviteCode))
            }
        }.apply()
        return true
    }

    fun deleteKeyset(coupleId: String) {
        prefs.edit()
            .remove(prefKey(coupleId))
            .remove(recoveryPhraseKey(coupleId))
            .apply()
        aeadCache.remove(coupleId)
    }

    fun aeadFor(coupleId: String): Aead? {
        aeadCache[coupleId]?.let { return it }
        val handle = loadKeyset(coupleId) ?: return null
        val aead = handle.getPrimitive(Aead::class.java)
        aeadCache[coupleId] = aead
        return aead
    }

    private fun prefKey(coupleId: String) = "keyset_$coupleId"
    private fun invitePrefKey(inviteCode: String) = "keyset_invite_$inviteCode"
    private fun invitePhrasePrefKey(inviteCode: String) = "phrase_invite_$inviteCode"
    private fun recoveryPhraseKey(coupleId: String) = "recovery_phrase_$coupleId"

    private fun serialize(handle: KeysetHandle): String {
        val baos = ByteArrayOutputStream()
        CleartextKeysetHandle.write(handle, JsonKeysetWriter.withOutputStream(baos))
        return baos.toString(Charsets.UTF_8.name())
    }

    private fun load(key: String): KeysetHandle? =
        runCatching {
            val json = prefs.getString(key, null) ?: return null
            CleartextKeysetHandle.read(JsonKeysetReader.withString(json))
        }.getOrNull()

    private companion object {
        const val PREFS_NAME = "couple_crypto_secure"
    }
}
