package com.tomhempel.rbarometer.security

import android.content.Context
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Base64
import java.nio.ByteBuffer
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.Mac
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec

/**
 * Field-level encryption for client codes and answer text at rest.
 * Keys live in Android Keystore and never leave the device.
 */
object SensitiveDataCrypto {

    private const val KEYSTORE = "AndroidKeyStore"
    private const val ENC_KEY_ALIAS = "qdb_sensitive_enc_v1"
    private const val HMAC_KEY_ALIAS = "qdb_sensitive_hmac_v1"
    private const val PREFIX = "v1:"
    private const val GCM_TAG_BITS = 128

    @Volatile
    private var appContext: Context? = null

    @Volatile
    private var cachedEncKey: SecretKey? = null

    @Volatile
    private var cachedHmacKey: SecretKey? = null

    fun init(context: Context) {
        appContext = context.applicationContext
        ensureKeys()
        cachedEncKey = null
        cachedHmacKey = null
    }

    private fun ctx(): Context =
        appContext ?: error("SensitiveDataCrypto.init() must run in Application.onCreate")

    fun encrypt(plaintext: String): String {
        if (plaintext.isEmpty()) return plaintext
        val cipher = Cipher.getInstance("AES/GCM/NoPadding")
        cipher.init(Cipher.ENCRYPT_MODE, encryptionKey())
        val iv = cipher.iv
        val ciphertext = cipher.doFinal(plaintext.toByteArray(Charsets.UTF_8))
        val packed = ByteBuffer.allocate(iv.size + ciphertext.size)
            .put(iv)
            .put(ciphertext)
            .array()
        return PREFIX + Base64.encodeToString(packed, Base64.NO_WRAP)
    }

    fun decrypt(ciphertext: String): String {
        if (ciphertext.isEmpty()) return ciphertext
        if (!ciphertext.startsWith(PREFIX)) {
            throw IllegalArgumentException("Unsupported ciphertext format")
        }
        val packed = Base64.decode(ciphertext.removePrefix(PREFIX), Base64.NO_WRAP)
        val ivLen = 12
        if (packed.size <= ivLen) throw IllegalArgumentException("Ciphertext too short")
        val iv = packed.copyOfRange(0, ivLen)
        val enc = packed.copyOfRange(ivLen, packed.size)
        val cipher = Cipher.getInstance("AES/GCM/NoPadding")
        cipher.init(Cipher.DECRYPT_MODE, encryptionKey(), GCMParameterSpec(GCM_TAG_BITS, iv))
        return String(cipher.doFinal(enc), Charsets.UTF_8)
    }

    /** Stable lookup key for Room FKs without storing plaintext client codes. */
    fun clientCodeKey(clientCode: String): String {
        val normalized = clientCode.trim()
        val mac = Mac.getInstance("HmacSHA256")
        mac.init(hmacKey())
        val digest = mac.doFinal(normalized.toByteArray(Charsets.UTF_8))
        return Base64.encodeToString(digest, Base64.NO_WRAP)
    }

    private fun ensureKeys() {
        val ks = KeyStore.getInstance(KEYSTORE).apply { load(null) }
        if (!ks.containsAlias(ENC_KEY_ALIAS)) {
            KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE).apply {
                init(
                    KeyGenParameterSpec.Builder(
                        ENC_KEY_ALIAS,
                        KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT,
                    )
                        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                        .setKeySize(256)
                        .build(),
                )
                generateKey()
            }
        }
        if (!ks.containsAlias(HMAC_KEY_ALIAS)) {
            KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_HMAC_SHA256, KEYSTORE).apply {
                init(
                    KeyGenParameterSpec.Builder(
                        HMAC_KEY_ALIAS,
                        KeyProperties.PURPOSE_SIGN,
                    )
                        .setDigests(KeyProperties.DIGEST_SHA256)
                        .build(),
                )
                generateKey()
            }
        }
    }

    private fun encryptionKey(): SecretKey =
        cachedEncKey ?: synchronized(this) {
            cachedEncKey ?: run {
                val ks = KeyStore.getInstance(KEYSTORE).apply { load(null) }
                (ks.getKey(ENC_KEY_ALIAS, null) as SecretKey).also { cachedEncKey = it }
            }
        }

    private fun hmacKey(): SecretKey =
        cachedHmacKey ?: synchronized(this) {
            cachedHmacKey ?: run {
                val ks = KeyStore.getInstance(KEYSTORE).apply { load(null) }
                (ks.getKey(HMAC_KEY_ALIAS, null) as SecretKey).also { cachedHmacKey = it }
            }
        }
}
