package com.dtourolle.jellytau.security

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

/**
 * Secure storage for credentials using Android Keystore.
 * Provides encrypted storage for sensitive data like API tokens.
 */
class SecureStorage private constructor(context: Context) {
    companion object {
        private const val TAG = "SecureStorage"
        private const val KEYSTORE_PROVIDER = "AndroidKeyStore"
        private const val KEY_ALIAS = "jellytau_credentials_key"
        private const val TRANSFORMATION = "AES/GCM/NoPadding"
        private const val PREFS_NAME = "jellytau_secure_prefs"

        @Volatile
        private var instance: SecureStorage? = null

        @JvmStatic
        fun initialize(context: Context) {
            if (instance == null) {
                synchronized(this) {
                    if (instance == null) {
                        instance = SecureStorage(context.applicationContext)
                    }
                }
            }
        }

        @JvmStatic
        fun getInstance(): SecureStorage {
            return instance ?: throw IllegalStateException("SecureStorage not initialized")
        }
    }

    private val keyStore: KeyStore = KeyStore.getInstance(KEYSTORE_PROVIDER).apply {
        load(null)
    }

    private val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)

    init {
        // Ensure encryption key exists
        if (!keyStore.containsAlias(KEY_ALIAS)) {
            generateKey()
        }
    }

    private fun generateKey() {
        val keyGenerator = KeyGenerator.getInstance(
            KeyProperties.KEY_ALGORITHM_AES,
            KEYSTORE_PROVIDER
        )

        val keyGenParameterSpec = KeyGenParameterSpec.Builder(
            KEY_ALIAS,
            KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
        )
            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
            .setRandomizedEncryptionRequired(true)
            .build()

        keyGenerator.init(keyGenParameterSpec)
        keyGenerator.generateKey()
    }

    private fun getSecretKey(): SecretKey {
        return keyStore.getKey(KEY_ALIAS, null) as SecretKey
    }

    fun saveCredential(key: String, value: String) {
        try {
            val cipher = Cipher.getInstance(TRANSFORMATION)
            cipher.init(Cipher.ENCRYPT_MODE, getSecretKey())

            val iv = cipher.iv
            val encrypted = cipher.doFinal(value.toByteArray(Charsets.UTF_8))

            // Store IV + encrypted data as base64
            val combined = iv + encrypted
            val encoded = Base64.encodeToString(combined, Base64.DEFAULT)

            prefs.edit().putString(key, encoded).apply()
            Log.d(TAG, "Saved credential: $key")
        } catch (e: Exception) {
            Log.e(TAG, "Failed to save credential: $key", e)
            throw e
        }
    }

    /**
     * Read a credential.
     *
     * Returns null for both "nothing stored" and "stored but undecryptable", but
     * treats them as distinct events. The second happens after a backup restore
     * or a device-to-device transfer: SharedPreferences travel, the Android
     * Keystore key that encrypted them never does, so the ciphertext can never
     * be read again on this install. That blob is discarded here rather than
     * left to fail on every subsequent read, which turns a permanently broken
     * credential into a clean logged-out state. (The app also declares
     * allowBackup="false" plus data-extraction rules so this should no longer
     * arise - this is the belt to that manifest's braces.)
     */
    fun getCredential(key: String): String? {
        val encoded = prefs.getString(key, null)
        if (encoded == null) {
            Log.d(TAG, "No credential stored for: $key")
            return null
        }

        return try {
            val combined = Base64.decode(encoded, Base64.DEFAULT)

            // Extract IV (first 12 bytes for GCM)
            val iv = combined.copyOfRange(0, 12)
            val encrypted = combined.copyOfRange(12, combined.size)

            val cipher = Cipher.getInstance(TRANSFORMATION)
            val spec = GCMParameterSpec(128, iv)
            cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), spec)

            val decrypted = cipher.doFinal(encrypted)
            String(decrypted, Charsets.UTF_8)
        } catch (e: Exception) {
            Log.w(
                TAG,
                "Credential '$key' is present but cannot be decrypted; discarding it and " +
                    "reporting no credential. Signing in again will store a fresh one.",
                e
            )
            prefs.edit().remove(key).apply()
            null
        }
    }

    fun deleteCredential(key: String) {
        prefs.edit().remove(key).apply()
        Log.d(TAG, "Deleted credential: $key")
    }

    // JNI-compatible methods (called from Rust)

    /**
     * Save a token (JNI-compatible version).
     * @return true if successful, false otherwise
     */
    @JvmOverloads
    fun saveToken(key: String, value: String): Boolean {
        return try {
            saveCredential(key, value)
            true
        } catch (e: Exception) {
            Log.e(TAG, "saveToken failed for key: $key", e)
            false
        }
    }

    /**
     * Get a token (JNI-compatible version).
     * @return token string or null if not found
     */
    fun getToken(key: String): String? {
        return getCredential(key)
    }

    /**
     * Delete a token (JNI-compatible version).
     * @return true if successful, false otherwise
     */
    fun deleteToken(key: String): Boolean {
        return try {
            deleteCredential(key)
            true
        } catch (e: Exception) {
            Log.e(TAG, "deleteToken failed for key: $key", e)
            false
        }
    }
}
