package ru.forbion.f7cloud.core.auth

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

/**
 * Шифрование секретов в покое ключом из AndroidKeystore (AES-256-GCM).
 *
 * Ключ НЕэкспортируемый (на большинстве устройств — аппаратный TEE/StrongBox), поэтому даже
 * утёкший ciphertext (бэкап, дамп prefs, root) бесполезен вне этого устройства.
 *
 * Пер-запросной авторизации пользователя НЕТ намеренно (`setUserAuthenticationRequired` не
 * ставится): пароль читается в фоне (push, входящие звонки, холодный старт), где биометрию
 * спросить не у кого. Биометрическая привязка — отдельная задача поверх (кэш в памяти +
 * деградация фоновых сценариев).
 *
 * Формат выхода: `v1:` + Base64(IV[12] || ciphertext+tag). Префикс отличает шифртекст от
 * legacy-plaintext при миграции.
 */
object KeystoreCrypto {
    private const val ANDROID_KEYSTORE = "AndroidKeyStore"
    private const val KEY_ALIAS = "f7_auth_master"
    private const val TRANSFORMATION = "AES/GCM/NoPadding"
    private const val IV_SIZE = 12
    private const val TAG_BITS = 128
    const val PREFIX = "v1:"

    fun isEncrypted(value: String?): Boolean = value != null && value.startsWith(PREFIX)

    /** Шифрует строку; результат — с префиксом [PREFIX]. */
    fun encrypt(plaintext: String): String {
        val cipher = Cipher.getInstance(TRANSFORMATION)
        cipher.init(Cipher.ENCRYPT_MODE, getOrCreateKey())
        val iv = cipher.iv
        val ct = cipher.doFinal(plaintext.toByteArray(Charsets.UTF_8))
        val combined = ByteArray(iv.size + ct.size)
        System.arraycopy(iv, 0, combined, 0, iv.size)
        System.arraycopy(ct, 0, combined, iv.size, ct.size)
        return PREFIX + Base64.encodeToString(combined, Base64.NO_WRAP)
    }

    /**
     * Расшифровывает строку с префиксом [PREFIX]. Бросает, если ключ недоступен/инвалидирован
     * (например, сброшены учётные данные устройства) или данные повреждены — вызывающий
     * трактует это как «сессии нет» (повторный вход).
     */
    fun decrypt(stored: String): String {
        require(isEncrypted(stored)) { "not an encrypted value" }
        val combined = Base64.decode(stored.removePrefix(PREFIX), Base64.NO_WRAP)
        val iv = combined.copyOfRange(0, IV_SIZE)
        val ct = combined.copyOfRange(IV_SIZE, combined.size)
        val cipher = Cipher.getInstance(TRANSFORMATION)
        cipher.init(Cipher.DECRYPT_MODE, getOrCreateKey(), GCMParameterSpec(TAG_BITS, iv))
        return String(cipher.doFinal(ct), Charsets.UTF_8)
    }

    private fun getOrCreateKey(): SecretKey {
        val ks = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
        (ks.getEntry(KEY_ALIAS, null) as? KeyStore.SecretKeyEntry)?.let { return it.secretKey }
        val generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE)
        generator.init(
            KeyGenParameterSpec.Builder(
                KEY_ALIAS,
                KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT,
            )
                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                .setKeySize(256)
                .build(),
        )
        return generator.generateKey()
    }
}
