package com.rosetta.messenger.crypto

import android.util.Base64
import com.google.crypto.tink.subtle.XChaCha20Poly1305
import java.io.ByteArrayOutputStream
import java.math.BigInteger
import java.security.*
import java.util.concurrent.ConcurrentHashMap
import java.util.zip.Deflater
import java.util.zip.Inflater
import javax.crypto.Cipher
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.PBEKeySpec
import javax.crypto.spec.SecretKeySpec
import org.bitcoinj.crypto.MnemonicCode
import org.bitcoinj.crypto.MnemonicException
import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.jce.spec.ECPrivateKeySpec
import org.bouncycastle.jce.spec.ECPublicKeySpec

/**
 * Cryptography module for Rosetta Messenger Implements BIP39 seed phrase generation and secp256k1
 * key derivation
 */
object CryptoManager {

    private const val PBKDF2_ITERATIONS = 1000
    private const val KEY_SIZE = 256
    private const val SALT = "rosetta"
    private const val PBKDF2_HMAC_SHA1 = "PBKDF2WithHmacSHA1"
    private const val PBKDF2_HMAC_SHA256 = "PBKDF2WithHmacSHA256"

    // 🚀 ОПТИМИЗАЦИЯ: Кэш для генерации ключей (seedPhrase -> KeyPair)
    private val keyPairCache = mutableMapOf<String, KeyPairData>()
    private val privateKeyHashCache = mutableMapOf<String, String>()

    // 🚀 КРИТИЧЕСКАЯ ОПТИМИЗАЦИЯ: Кэш для PBKDF2 производных ключей
    // PBKDF2 с 1000 итерациями - очень тяжелая операция!
    // Кэшируем производный ключ для каждого пароля чтобы не вычислять его каждый раз
    private val pbkdf2KeyCache = mutableMapOf<String, SecretKeySpec>()

    // 🚀 ОПТИМИЗАЦИЯ: Lock-free кэш для расшифрованных сообщений
    // ConcurrentHashMap вместо synchronized LinkedHashMap — убирает контention при параллельной
    // расшифровке
    private const val DECRYPTION_CACHE_SIZE = 2000
    // Не кэшируем большие payload (вложения), чтобы избежать OOM на конкатенации cache key
    // и хранения гигантских plaintext в памяти.
    private const val MAX_CACHEABLE_ENCRYPTED_CHARS = 64 * 1024
    private const val MAX_CACHEABLE_DECRYPTED_CHARS = 64 * 1024
    private val decryptionCache = ConcurrentHashMap<String, String>(DECRYPTION_CACHE_SIZE, 0.75f, 4)

    init {
        // Add BouncyCastle provider for secp256k1 support
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Security.addProvider(BouncyCastleProvider())
        }
    }

    /**
     * 🚀 Получить или вычислить PBKDF2 ключ для пароля (кэшируется) Public для pre-warm при логине
     * (чтобы кэш был горячий к моменту дешифровки)
     */
    fun getPbkdf2Key(password: String): SecretKeySpec {
        return getPbkdf2Key(password, PBKDF2_HMAC_SHA256)
    }

    private fun getPbkdf2Key(password: String, algorithm: String): SecretKeySpec {
        val cacheKey = "$algorithm::$password"
        return pbkdf2KeyCache.getOrPut(cacheKey) {
            val factory = SecretKeyFactory.getInstance(algorithm)
            val spec =
                    PBEKeySpec(
                            password.toCharArray(),
                            SALT.toByteArray(Charsets.UTF_8),
                            PBKDF2_ITERATIONS,
                            KEY_SIZE
                    )
            val secretKey = factory.generateSecret(spec)
            SecretKeySpec(secretKey.encoded, "AES")
        }
    }

    /** 🧹 Очистить кэши при logout */
    fun clearCaches() {
        pbkdf2KeyCache.clear()
        decryptionCache.clear()
        keyPairCache.clear()
        privateKeyHashCache.clear()
    }

    /** Generate a new 12-word BIP39 seed phrase */
    fun generateSeedPhrase(): List<String> {
        val secureRandom = SecureRandom()
        val entropy = ByteArray(16) // 128 bits = 12 words
        secureRandom.nextBytes(entropy)

        val mnemonicCode = MnemonicCode.INSTANCE
        return mnemonicCode.toMnemonic(entropy)
    }

    /** Validate a seed phrase */
    fun validateSeedPhrase(words: List<String>): Boolean {
        return try {
            val mnemonicCode = MnemonicCode.INSTANCE
            mnemonicCode.check(words)
            true
        } catch (e: MnemonicException) {
            false
        }
    }

    /**
     * Convert seed phrase to private key (32 bytes hex string)
     *
     * Алгоритм (совместим с Desktop desktop-rosetta):
     * 1. BIP39 mnemonicToSeed(phrase, "") → 64 байта (PBKDF2-SHA512, 2048 итераций)
     * 2. Конвертация seed в hex-строку (128 символов)
     * 3. SHA256(hexSeed) → 32 байта privateKey
     *
     * Desktop эквивалент (SetPassword.tsx + crypto.ts):
     * ```js
     * let seed = await mnemonicToSeed(phrase);           // BIP39 → 64 bytes
     * let hex = Buffer.from(seed).toString('hex');        // → 128-char hex string
     * let keypair = await generateKeyPairFromSeed(hex);   // SHA256(hex) → privateKey
     * ```
     */
    fun seedPhraseToPrivateKey(seedPhrase: List<String>): String {
        // Step 1: BIP39 mnemonicToSeed — PBKDF2-SHA512 with 2048 iterations
        // passphrase = "" (empty), salt = "mnemonic" + passphrase
        val bip39Seed = MnemonicCode.toSeed(seedPhrase, "")

        // Step 2: Convert to hex string (128 chars for 64 bytes)
        val hexSeed = bip39Seed.joinToString("") { "%02x".format(it) }

        // Step 3: SHA256(hexSeed) — matches Desktop's sha256.create().update(hex).digest()
        val digest = MessageDigest.getInstance("SHA-256")
        val hash = digest.digest(hexSeed.toByteArray(Charsets.UTF_8))

        return hash.joinToString("") { "%02x".format(it) }
    }

    /**
     * Generate key pair from seed phrase using secp256k1 curve
     *
     * Алгоритм (совместим с Desktop desktop-rosetta):
     * - privateKey = SHA256(hex(BIP39_seed(mnemonic))) - 32 байта
     * - publicKey = secp256k1.getPublicKey(privateKey, compressed=true) - 33 байта
     *
     * Desktop эквивалент (crypto.ts):
     * ```js
     * const privateKey = sha256.create().update(seed).digest().toHex().toString();
     * const publicKey = secp256k1.getPublicKey(Buffer.from(privateKey, "hex"), true);
     * ```
     *
     * Кэшируем результаты для избежания повторных вычислений
     */
    fun generateKeyPairFromSeed(seedPhrase: List<String>): KeyPairData {
        val cacheKey = seedPhrase.joinToString(" ")

        // Проверяем кэш
        keyPairCache[cacheKey]?.let {
            return it
        }

        // Генерируем приватный ключ через SHA256
        val privateKeyHex = seedPhraseToPrivateKey(seedPhrase)
        val ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1")

        // Преобразуем hex в bytes (32 байта)
        val privateKeyBytes = privateKeyHex.chunked(2).map { it.toInt(16).toByte() }.toByteArray()

        val privateKeyBigInt = BigInteger(1, privateKeyBytes)

        // Генерируем публичный ключ из приватного
        val publicKeyPoint = ecSpec.g.multiply(privateKeyBigInt)

        // ⚡ ВАЖНО: Используем СЖАТЫЙ формат (compressed=true) - 33 байта вместо 65
        // Это совместимо с crypto_new где используется: secp256k1.getPublicKey(..., true)
        val publicKeyHex = publicKeyPoint.getEncoded(true).joinToString("") { "%02x".format(it) }

        val keyPair = KeyPairData(privateKey = privateKeyHex, publicKey = publicKeyHex)

        // Сохраняем в кэш (ограничиваем размер до 5 записей)
        keyPairCache[cacheKey] = keyPair
        if (keyPairCache.size > 5) {
            keyPairCache.remove(keyPairCache.keys.first())
        }

        return keyPair
    }

    /**
     * Generate private key hash for protocol (SHA256(privateKey + "rosetta")) 🚀 ОПТИМИЗАЦИЯ:
     * Кэшируем хэши для избежания повторных вычислений
     */
    fun generatePrivateKeyHash(privateKey: String): String {
        // Проверяем кэш
        privateKeyHashCache[privateKey]?.let {
            return it
        }

        val data = (privateKey + SALT).toByteArray()
        val digest = MessageDigest.getInstance("SHA-256")
        val hash = digest.digest(data)
        val hashHex = hash.joinToString("") { "%02x".format(it) }

        // Сохраняем в кэш
        privateKeyHashCache[privateKey] = hashHex
        if (privateKeyHashCache.size > 10) {
            privateKeyHashCache.remove(privateKeyHashCache.keys.first())
        }

        return hashHex
    }

    /**
     * Encrypt data with password using PBKDF2 + AES
     *
     * ⚠️ ВАЖНО: Совместимость с Desktop (crypto-js 4.x):
     * - PBKDF2WithHmacSHA256
     * - Salt: "rosetta"
     * - Iterations: 1000
     * - Key size: 256 bit
     * - AES-256-CBC с PKCS5/PKCS7 padding
     * - Compression: zlib deflate (pako.deflate в JS)
     * - Chunking для данных > 10MB
     * - Формат single chunk: base64(iv):base64(ciphertext)
     * - Формат chunked: "CHNK:" + chunks joined by "::"
     */
    fun encryptWithPassword(data: String, password: String): String {
        // Compress data (zlib deflate - совместимо с pako.deflate в JS)
        val compressed = compress(data.toByteArray(Charsets.UTF_8))

        val CHUNK_SIZE = 10 * 1024 * 1024 // 10MB

        // Check if we need chunking
        if (compressed.size > CHUNK_SIZE) {
            // Chunk the compressed data
            val chunks = compressed.toList().chunked(CHUNK_SIZE).map { it.toByteArray() }
            val encryptedChunks = mutableListOf<String>()

            for (chunk in chunks) {
                // Desktop parity: PBKDF2-HMAC-SHA256
                val key = getPbkdf2Key(password, PBKDF2_HMAC_SHA256)

                // Generate random IV
                val iv = ByteArray(16)
                SecureRandom().nextBytes(iv)
                val ivSpec = IvParameterSpec(iv)

                // Encrypt with AES
                val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
                cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec)
                val encrypted = cipher.doFinal(chunk)

                // Store as ivBase64:ctBase64
                val ivBase64 = Base64.encodeToString(iv, Base64.NO_WRAP)
                val ctBase64 = Base64.encodeToString(encrypted, Base64.NO_WRAP)
                encryptedChunks.add("$ivBase64:$ctBase64")
            }

            // Return chunked format: "CHNK:" + chunks joined by "::"
            return "CHNK:" + encryptedChunks.joinToString("::")
        } else {
            // Single chunk (desktop parity)
            val key = getPbkdf2Key(password, PBKDF2_HMAC_SHA256)

            // Generate random IV
            val iv = ByteArray(16)
            SecureRandom().nextBytes(iv)
            val ivSpec = IvParameterSpec(iv)

            // Encrypt with AES
            val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
            cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec)
            val encrypted = cipher.doFinal(compressed)

            // Return iv:ciphertext in Base64
            val ivBase64 = Base64.encodeToString(iv, Base64.NO_WRAP)
            val ctBase64 = Base64.encodeToString(encrypted, Base64.NO_WRAP)

            return "$ivBase64:$ctBase64"
        }
    }

    /**
     * Decrypt data with password
     *
     * ⚠️ ВАЖНО: Desktop использует PBKDF2-SHA256.
     * Для обратной совместимости с legacy Android данными пробуем также SHA1.
     * - Salt: "rosetta"
     * - Iterations: 1000
     * - Key size: 256 bit
     * - AES-256-CBC с PKCS5/PKCS7 padding
     * - Decompression: zlib inflate (pako.inflate в JS)
     * - Supports old format (base64-encoded hex "iv:ciphertext")
     * - Supports new format (base64 "iv:ciphertext")
     * - Supports chunked format ("CHNK:" + chunks joined by "::")
     *
     * 🚀 ОПТИМИЗАЦИЯ: Кэширование PBKDF2 ключа и расшифрованных сообщений
     */
    fun decryptWithPassword(encryptedData: String, password: String): String? {
        val useCache = encryptedData.length <= MAX_CACHEABLE_ENCRYPTED_CHARS
        val cacheKey = if (useCache) "$password:$encryptedData" else null

        // 🚀 ОПТИМИЗАЦИЯ: Lock-free проверка кэша (ConcurrentHashMap)
        if (cacheKey != null) {
            decryptionCache[cacheKey]?.let {
                return it
            }
        }

        return try {
            val result = decryptWithPasswordInternal(encryptedData, password)

            // 🚀 Сохраняем в кэш (lock-free)
            if (cacheKey != null && result != null && result.length <= MAX_CACHEABLE_DECRYPTED_CHARS) {
                // Ограничиваем размер кэша
                if (decryptionCache.size >= DECRYPTION_CACHE_SIZE) {
                    // Удаляем ~10% самых старых записей
                    val keysToRemove = decryptionCache.keys.take(DECRYPTION_CACHE_SIZE / 10)
                    keysToRemove.forEach { decryptionCache.remove(it) }
                }
                decryptionCache[cacheKey] = result
            }

            result
        } catch (e: Exception) {
            null
        }
    }

    /** 🔐 Внутренняя функция расшифровки (без кэширования результата) */
    private fun decryptWithPasswordInternal(encryptedData: String, password: String): String? {
        val keysToTry =
                listOf(
                        getPbkdf2Key(password, PBKDF2_HMAC_SHA256),
                        getPbkdf2Key(password, PBKDF2_HMAC_SHA1)
                )

        keysToTry.forEach { key ->
            // Check for old format: base64-encoded string containing hex
            if (isOldFormat(encryptedData)) {
                try {
                    val decoded =
                            String(Base64.decode(encryptedData, Base64.NO_WRAP), Charsets.UTF_8)
                    val parts = decoded.split(":")
                    if (parts.size != 2) return@forEach

                    val iv = parts[0].chunked(2).map { it.toInt(16).toByte() }.toByteArray()
                    val ciphertext = parts[1].chunked(2).map { it.toInt(16).toByte() }.toByteArray()

                    val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
                    cipher.init(Cipher.DECRYPT_MODE, key, IvParameterSpec(iv))
                    val decrypted = cipher.doFinal(ciphertext)
                    return String(decrypted, Charsets.UTF_8)
                } catch (_: Exception) {
                    return@forEach
                }
            }

            // Check for chunked format
            if (encryptedData.startsWith("CHNK:")) {
                try {
                    val chunkStrings = encryptedData.substring(5).split("::")
                    val decompressedParts = mutableListOf<ByteArray>()

                    for (chunkString in chunkStrings) {
                        val parts = chunkString.split(":")
                        if (parts.size != 2) return@forEach

                        val iv = Base64.decode(parts[0], Base64.NO_WRAP)
                        val ciphertext = Base64.decode(parts[1], Base64.NO_WRAP)

                        val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
                        cipher.init(Cipher.DECRYPT_MODE, key, IvParameterSpec(iv))
                        val decrypted = cipher.doFinal(ciphertext)

                        decompressedParts.add(decrypted)
                    }

                    // Concatenate all decrypted chunks
                    val allBytes = decompressedParts.fold(ByteArray(0)) { acc, arr -> acc + arr }

                    // Decompress the concatenated data
                    return String(decompress(allBytes), Charsets.UTF_8)
                } catch (_: Exception) {
                    return@forEach
                }
            }

            // New format: base64 "iv:ciphertext"
            try {
                val parts = encryptedData.split(":")
                if (parts.size != 2) return@forEach

                val iv = Base64.decode(parts[0], Base64.NO_WRAP)
                val ciphertext = Base64.decode(parts[1], Base64.NO_WRAP)

                val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
                cipher.init(Cipher.DECRYPT_MODE, key, IvParameterSpec(iv))
                val decrypted = cipher.doFinal(ciphertext)

                // Decompress (совместимо с desktop + fallback для legacy)
                return String(decompress(decrypted), Charsets.UTF_8)
            } catch (_: Exception) {
                return@forEach
            }
        }

        return null
    }

    /** Check if data is in old format (base64-encoded hex with ":") */
    private fun isOldFormat(data: String): Boolean {
        // 🚀 Fast path: new format always contains ':' at plaintext level (iv:ct)
        // Old format is a single base64 blob without ':' in the encoded string
        if (data.contains(':')) return false
        if (data.startsWith("CHNK:")) return false

        return try {
            val decoded = String(Base64.decode(data, Base64.NO_WRAP), Charsets.UTF_8)
            decoded.contains(":") &&
                    decoded.split(":").all { part ->
                        part.all { it in '0'..'9' || it in 'a'..'f' || it in 'A'..'F' }
                    }
        } catch (e: Exception) {
            false
        }
    }

    /**
     * Сжатие данных для encodeWithPassword.
     *
     * Десктоп использует pako.deflate (zlib wrapper), поэтому тут должен быть обычный
     * Deflater без nowrap=true.
     */
    private fun compress(data: ByteArray): ByteArray {
        val deflater = Deflater(Deflater.DEFAULT_COMPRESSION)
        deflater.setInput(data)
        deflater.finish()

        val outputStream = ByteArrayOutputStream()
        val buffer = ByteArray(1024)
        while (!deflater.finished()) {
            val count = deflater.deflate(buffer)
            outputStream.write(buffer, 0, count)
        }
        deflater.end() // Освобождаем ресурсы
        outputStream.close()
        return outputStream.toByteArray()
    }

    /**
     * Декомпрессия с обратной совместимостью:
     * 1) сначала zlib (desktop/new android),
     * 2) затем raw deflate (legacy android данные).
     */
    private fun decompress(data: ByteArray): ByteArray {
        return try {
            inflate(data, nowrap = false)
        } catch (_: Exception) {
            inflate(data, nowrap = true)
        }
    }

    private fun inflate(data: ByteArray, nowrap: Boolean): ByteArray {
        val inflater = Inflater(nowrap)
        inflater.setInput(data)

        val outputStream = ByteArrayOutputStream()
        val buffer = ByteArray(1024)
        while (!inflater.finished()) {
            val count = inflater.inflate(buffer)
            if (count == 0) {
                if (inflater.needsInput() || inflater.needsDictionary()) {
                    throw IllegalStateException("Inflate failed: incomplete or unsupported stream")
                }
            }
            outputStream.write(buffer, 0, count)
        }
        inflater.end() // Освобождаем ресурсы
        outputStream.close()
        val result = outputStream.toByteArray()
        if (result.isEmpty()) throw IllegalStateException("Decompression produced empty output")
        return result
    }

    /**
     * Encrypt data using ECDH + AES
     *
     * Algorithm:
     * 1. Generate ephemeral key pair
     * 2. Compute shared secret using ECDH (ephemeralPrivateKey × recipientPublicKey)
     * 3. Use x-coordinate of shared point as AES key
     * 4. Encrypt data with AES-256-CBC
     * 5. Return: base64(iv:ciphertext:ephemeralPrivateKey)
     */
    fun encrypt(data: String, publicKeyHex: String): String {
        val ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1")

        // Generate ephemeral private key (random 32 bytes)
        val ephemeralPrivateKeyBytes = ByteArray(32)
        SecureRandom().nextBytes(ephemeralPrivateKeyBytes)
        val ephemeralPrivateKeyBigInt = BigInteger(1, ephemeralPrivateKeyBytes)

        // Generate ephemeral public key from private key
        val ephemeralPublicKeyPoint = ecSpec.g.multiply(ephemeralPrivateKeyBigInt)

        // Parse recipient's public key
        val recipientPublicKeyBytes =
                publicKeyHex.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
        val recipientPublicKeyPoint = ecSpec.curve.decodePoint(recipientPublicKeyBytes)

        // Compute shared secret using ECDH (ephemeralPrivateKey × recipientPublicKey)
        val sharedPoint = recipientPublicKeyPoint.multiply(ephemeralPrivateKeyBigInt).normalize()

        // Use x-coordinate of shared point as AES key (32 bytes)
        val sharedKeyBytes = sharedPoint.affineXCoord.encoded
        val sharedKey =
                if (sharedKeyBytes.size >= 32) {
                    sharedKeyBytes.copyOfRange(sharedKeyBytes.size - 32, sharedKeyBytes.size)
                } else {
                    // Pad with leading zeros if needed
                    ByteArray(32 - sharedKeyBytes.size) + sharedKeyBytes
                }
        val key = SecretKeySpec(sharedKey, "AES")

        // Generate random IV
        val iv = ByteArray(16)
        SecureRandom().nextBytes(iv)
        val ivSpec = IvParameterSpec(iv)

        // Encrypt with AES
        val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
        cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec)
        val encrypted = cipher.doFinal(data.toByteArray(Charsets.UTF_8))

        // Normalize ephemeral private key to 32 bytes
        val normalizedPrivateKey =
                if (ephemeralPrivateKeyBytes.size > 32) {
                    ephemeralPrivateKeyBytes.copyOfRange(
                            ephemeralPrivateKeyBytes.size - 32,
                            ephemeralPrivateKeyBytes.size
                    )
                } else {
                    ephemeralPrivateKeyBytes
                }

        // Return base64(iv:ciphertext:ephemeralPrivateKey)
        val ivHex = iv.joinToString("") { "%02x".format(it) }
        val ctHex = encrypted.joinToString("") { "%02x".format(it) }
        val ephemeralPrivateKeyHex = normalizedPrivateKey.joinToString("") { "%02x".format(it) }
        val combined = "$ivHex:$ctHex:$ephemeralPrivateKeyHex"

        return Base64.encodeToString(combined.toByteArray(Charsets.UTF_8), Base64.NO_WRAP)
    }

    /**
     * Decrypt data using ECDH + AES
     *
     * Algorithm:
     * 1. Parse iv, ciphertext, and ephemeralPrivateKey from base64
     * 2. Compute ephemeral public key from ephemeral private key
     * 3. Compute shared secret using ECDH (privateKey × ephemeralPublicKey)
     * 4. Use x-coordinate of shared point as AES key
     * 5. Decrypt data with AES-256-CBC
     */
    fun decrypt(encryptedData: String, privateKeyHex: String): String? {
        return try {
            // Decode base64
            val decoded = String(Base64.decode(encryptedData, Base64.NO_WRAP), Charsets.UTF_8)
            val parts = decoded.split(":")
            if (parts.size != 3) return null

            val iv = parts[0].chunked(2).map { it.toInt(16).toByte() }.toByteArray()
            val ciphertext = parts[1].chunked(2).map { it.toInt(16).toByte() }.toByteArray()
            val ephemeralPrivateKeyBytes =
                    parts[2].chunked(2).map { it.toInt(16).toByte() }.toByteArray()

            val ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1")

            // Compute ephemeral public key from ephemeral private key
            val ephemeralPrivateKeyBigInt = BigInteger(1, ephemeralPrivateKeyBytes)
            val ephemeralPublicKeyPoint = ecSpec.g.multiply(ephemeralPrivateKeyBigInt)
            val ephemeralPublicKeySpec = ECPublicKeySpec(ephemeralPublicKeyPoint, ecSpec)
            val keyFactory = KeyFactory.getInstance("ECDH", BouncyCastleProvider.PROVIDER_NAME)
            val ephemeralPublicKey = keyFactory.generatePublic(ephemeralPublicKeySpec)

            // Parse private key
            val privateKeyBytes =
                    privateKeyHex.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
            val privateKeyBigInt = BigInteger(1, privateKeyBytes)
            val privateKeySpec = ECPrivateKeySpec(privateKeyBigInt, ecSpec)
            val privateKey = keyFactory.generatePrivate(privateKeySpec)

            // Compute shared secret using ECDH
            val keyAgreement =
                    javax.crypto.KeyAgreement.getInstance(
                            "ECDH",
                            BouncyCastleProvider.PROVIDER_NAME
                    )
            keyAgreement.init(privateKey)
            keyAgreement.doPhase(ephemeralPublicKey, true)
            val sharedSecret = keyAgreement.generateSecret()

            // Use first 32 bytes (x-coordinate) as AES key
            val sharedKey = sharedSecret.copyOfRange(1, 33)
            val key = SecretKeySpec(sharedKey, "AES")

            // Decrypt with AES-256-CBC
            val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
            cipher.init(Cipher.DECRYPT_MODE, key, IvParameterSpec(iv))
            val decrypted = cipher.doFinal(ciphertext)

            String(decrypted, Charsets.UTF_8)
        } catch (e: Exception) {
            null
        }
    }

    /**
     * Encrypt data using XChaCha20-Poly1305
     *
     * Returns: { ciphertext: hex string, nonce: hex string (24 bytes), key: hex string (32 bytes) }
     */
    fun chacha20Encrypt(data: String): ChaCha20Result {
        // Generate random key (32 bytes) and nonce (24 bytes)
        val key = ByteArray(32)
        val nonce = ByteArray(24)
        SecureRandom().nextBytes(key)
        SecureRandom().nextBytes(nonce)

        // Encrypt using XChaCha20-Poly1305
        val cipher = XChaCha20Poly1305(key)
        val plaintext = data.toByteArray(Charsets.UTF_8)
        val ciphertext = cipher.encrypt(nonce, plaintext)

        return ChaCha20Result(
                ciphertext = ciphertext.joinToString("") { "%02x".format(it) },
                nonce = nonce.joinToString("") { "%02x".format(it) },
                key = key.joinToString("") { "%02x".format(it) }
        )
    }

    /**
     * Decrypt data using XChaCha20-Poly1305
     *
     * @param ciphertextHex Hex-encoded ciphertext
     * @param nonceHex Hex-encoded nonce (24 bytes)
     * @param keyHex Hex-encoded key (32 bytes)
     */
    fun chacha20Decrypt(ciphertextHex: String, nonceHex: String, keyHex: String): String? {
        return try {
            val ciphertext = ciphertextHex.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
            val nonce = nonceHex.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
            val key = keyHex.chunked(2).map { it.toInt(16).toByte() }.toByteArray()

            val cipher = XChaCha20Poly1305(key)
            val decrypted = cipher.decrypt(nonce, ciphertext)

            String(decrypted, Charsets.UTF_8)
        } catch (e: Exception) {
            null
        }
    }
}

data class KeyPairData(val privateKey: String, val publicKey: String)

data class ChaCha20Result(val ciphertext: String, val nonce: String, val key: String)
