package dev.kaizen.app.auth

import android.os.Build
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyPermanentlyInvalidatedException
import android.security.keystore.KeyProperties
import android.util.Log
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec

private const val TAG = "BiometricUnlock"
private const val KEYSTORE_PROVIDER = "AndroidKeyStore"
private const val KEY_ALIAS = "kaizen_biometric_unlock"

/**
 * Hardware-enforced biometric unlock using Android KeyStore + CryptoObject.
 *
 * A symmetric AES key is generated in the TEE/StrongBox with
 * [KeyGenParameterSpec.Builder.setUserAuthenticationRequired] — the key
 * material never leaves the secure hardware and can only be used after
 * successful biometric authentication. [BiometricPrompt] receives a
 * [Cipher] backed by this key as its [BiometricPrompt.CryptoObject];
 * the Cipher is unlocked by the TEE only after the biometric check
 * passes. Even on rooted devices the key cannot be extracted or used
 * without a real biometric event.
 *
 * If biometric enrollment changes (new fingerprint added), the key is
 * invalidated ([setInvalidatedByBiometricEnrollment]) and automatically
 * re-generated on the next attempt.
 */
object BiometricUnlock {

    sealed interface Result {
        data object Success : Result
        data object NotAvailable : Result
        data class Failed(val message: String) : Result
    }

    fun isAvailable(activity: FragmentActivity): Boolean {
        val bm = BiometricManager.from(activity)
        return bm.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) ==
                BiometricManager.BIOMETRIC_SUCCESS
    }

    fun authenticate(
        activity: FragmentActivity,
        title: String,
        subtitle: String,
        negativeButtonText: String,
        onResult: (Result) -> Unit,
    ) {
        if (!isAvailable(activity)) {
            onResult(Result.NotAvailable)
            return
        }

        val cipher = try {
            getCipher()
        } catch (e: KeyPermanentlyInvalidatedException) {
            Log.w(TAG, "Key invalidated (biometric enrollment changed), regenerating")
            deleteKey()
            try {
                getCipher()
            } catch (e2: Exception) {
                Log.e(TAG, "Failed to regenerate key", e2)
                onResult(Result.Failed(e2.message ?: "KeyStore error"))
                return
            }
        } catch (e: Exception) {
            Log.e(TAG, "Failed to init cipher", e)
            onResult(Result.Failed(e.message ?: "KeyStore error"))
            return
        }

        val cryptoObject = BiometricPrompt.CryptoObject(cipher)

        val executor = ContextCompat.getMainExecutor(activity)
        val prompt = BiometricPrompt(activity, executor, object : BiometricPrompt.AuthenticationCallback() {
            override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                val authedCipher = result.cryptoObject?.cipher
                if (authedCipher != null) {
                    onResult(Result.Success)
                } else {
                    onResult(Result.Failed("CryptoObject missing after auth"))
                }
            }

            override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                if (errorCode == BiometricPrompt.ERROR_USER_CANCELED ||
                    errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON ||
                    errorCode == BiometricPrompt.ERROR_CANCELED
                ) {
                    onResult(Result.Failed("cancelled"))
                } else {
                    onResult(Result.Failed(errString.toString()))
                }
            }

            override fun onAuthenticationFailed() {
                // Called on each failed attempt (wrong finger), prompt stays open.
                // Only onAuthenticationError terminates the flow.
            }
        })

        val promptInfo = BiometricPrompt.PromptInfo.Builder()
            .setTitle(title)
            .setSubtitle(subtitle)
            .setNegativeButtonText(negativeButtonText)
            .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
            .build()

        prompt.authenticate(promptInfo, cryptoObject)
    }

    private fun getOrCreateKey(): SecretKey {
        val keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER).apply { load(null) }
        keyStore.getKey(KEY_ALIAS, null)?.let { return it as SecretKey }

        val builder = KeyGenParameterSpec.Builder(
            KEY_ALIAS,
            KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT,
        )
            .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
            .setUserAuthenticationRequired(true)
            .setInvalidatedByBiometricEnrollment(true)

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            builder.setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG)
        }

        KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER)
            .apply { init(builder.build()) }
            .generateKey()

        return keyStore.getKey(KEY_ALIAS, null) as SecretKey
    }

    private fun getCipher(): Cipher {
        val key = getOrCreateKey()
        return Cipher.getInstance("${KeyProperties.KEY_ALGORITHM_AES}/${KeyProperties.BLOCK_MODE_CBC}/${KeyProperties.ENCRYPTION_PADDING_PKCS7}")
            .apply { init(Cipher.ENCRYPT_MODE, key) }
    }

    private fun deleteKey() {
        try {
            val keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER).apply { load(null) }
            keyStore.deleteEntry(KEY_ALIAS)
        } catch (_: Exception) { }
    }
}
