package app.closer.crypto

import android.content.Context
import app.closer.data.local.SecurePreferencesFactory
import com.google.crypto.tink.CleartextKeysetHandle
import com.google.crypto.tink.HybridDecrypt
import com.google.crypto.tink.HybridEncrypt
import com.google.crypto.tink.JsonKeysetReader
import com.google.crypto.tink.JsonKeysetWriter
import com.google.crypto.tink.KeysetHandle
import com.google.crypto.tink.hybrid.HybridKeyTemplates
import dagger.hilt.android.qualifiers.ApplicationContext
import java.io.ByteArrayOutputStream
import java.util.Base64
import javax.inject.Inject
import javax.inject.Singleton

/**
 * Manages the per-user ECIES keypair used for sealed-answer key release.
 *
 * Private key: persisted in EncryptedSharedPreferences (Keystore-backed).
 * Public key: extracted on demand and published to Firestore as "pub:v1:{base64}" by
 * [UserKeySetupManager]. Only the public keyset JSON is base64-encoded — no secret
 * material ever leaves the device.
 *
 * KNOWN LIMITATION — Single-device only:
 *   One keypair per user, stored only on the device that created it. If a user signs
 *   in on a second device, that device generates a NEW keypair and overwrites
 *   users/{uid}/devices/primary with the new public key. Any sealed answers whose
 *   one-time keys were wrapped for the OLD public key become permanently undecryptable
 *   on both devices (partner encrypted to stale key; private key is on original device).
 *
 *   Symptoms: "This answer cannot be revealed from this device" on the new device,
 *   and the original device can no longer complete the reveal (its pending key was
 *   removed after key release, but the partner encrypted to the new key).
 *
 *   Fix path (not implemented): multi-device key distribution, e.g. key agreement
 *   via iCloud/Google Drive backup or a per-device public key stored under
 *   users/{uid}/devices/{deviceId} so partners encrypt to ALL of the user's known
 *   public keys simultaneously.
 */
@Singleton
class UserKeyManager @Inject constructor(
    @ApplicationContext context: Context
) {
    private val prefs = SecurePreferencesFactory.encryptedSharedPreferences(context, PREFS_NAME)

    /**
     * Returns the existing private keypair, or generates and persists one if absent.
     */
    fun getOrCreatePrivateKey(): KeysetHandle {
        return loadPrivateKey() ?: run {
            val handle = KeysetHandle.generateNew(HybridKeyTemplates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM)
            savePrivateKey(handle)
            handle
        }
    }

    fun loadPrivateKey(): KeysetHandle? = runCatching {
        val json = prefs.getString(PRIVATE_KEY_PREF, null) ?: return null
        CleartextKeysetHandle.read(JsonKeysetReader.withString(json))
    }.getOrNull()

    /**
     * Serialises just the public portion of [privateKey] and returns it in the
     * "pub:v1:{urlsafe-base64-no-padding}" wire format for Firestore storage.
     */
    fun publicKeyB64(privateKey: KeysetHandle): String = publicKeyB64Companion(privateKey)

    private fun savePrivateKey(handle: KeysetHandle) {
        val baos = ByteArrayOutputStream()
        CleartextKeysetHandle.write(handle, JsonKeysetWriter.withOutputStream(baos))
        prefs.edit().putString(PRIVATE_KEY_PREF, baos.toString(Charsets.UTF_8.name())).apply()
    }

    companion object {
        private const val PREFS_NAME = "user_key_secure"
        private const val PRIVATE_KEY_PREF = "user_private_keyset"
        const val PUB_PREFIX = "pub:v1:"

        /**
         * Stateless: returns a [HybridDecrypt] backed by [privateKey].
         * Companion so [ReleaseKeyEncryptor] can call this without a Context instance.
         */
        fun hybridDecryptFor(privateKey: KeysetHandle): HybridDecrypt =
            privateKey.getPrimitive(HybridDecrypt::class.java)

        /**
         * Stateless: parses [publicKeyB64] (as stored in Firestore) and returns a [HybridEncrypt].
         * Companion so [ReleaseKeyEncryptor] can call this without a Context instance.
         */
        fun hybridEncryptFrom(publicKeyB64: String): HybridEncrypt {
            require(publicKeyB64.startsWith(PUB_PREFIX)) { "Unexpected public key format" }
            val bytes = Base64.getUrlDecoder().decode(publicKeyB64.removePrefix(PUB_PREFIX))
            val handle = CleartextKeysetHandle.read(JsonKeysetReader.withBytes(bytes))
            return handle.getPrimitive(HybridEncrypt::class.java)
        }

        /**
         * Stateless: serialises just the public portion of [privateKey] and returns it
         * in the "pub:v1:{urlsafe-base64-no-padding}" wire format.
         * Available as a companion function so tests can use it without a Context.
         */
        fun publicKeyB64Companion(privateKey: KeysetHandle): String {
            val publicHandle = privateKey.publicKeysetHandle
            val baos = java.io.ByteArrayOutputStream()
            CleartextKeysetHandle.write(publicHandle, JsonKeysetWriter.withOutputStream(baos))
            return PUB_PREFIX + Base64.getUrlEncoder().withoutPadding().encodeToString(baos.toByteArray())
        }
    }
}
