package wang.yaojia.webterm.tlsandroid

import android.security.keystore.KeyProperties
import android.security.keystore.KeyProtection
import java.security.KeyStore
import java.security.PrivateKey
import java.security.cert.X509Certificate
import wang.yaojia.webterm.clienttls.ParsedClientIdentity
import wang.yaojia.webterm.clienttls.Pkcs12Parse

/**
 * Imports a device `.p12` into AndroidKeyStore — the ONE runtime key home (plan §2/§8, R4).
 *
 * Two-step, validate-before-persist:
 *  1. PARSE + VALIDATE with the pure [Pkcs12Parse] (a transient `KeyStore("PKCS12")` used ONLY to read
 *     the import file). This throws on a wrong passphrase / corrupt blob / certs-only file BEFORE any
 *     AndroidKeyStore mutation — so a bad import can NEVER clobber a previously installed identity.
 *  2. IMPORT the private key NON-EXPORTABLE into AndroidKeyStore via `setEntry(..., KeyProtection)`.
 *     AndroidKeyStore keys are non-exportable by construction (there is no key-material getter); the
 *     private key never leaves secure hardware/OS custody once imported.
 *
 * No `.p12` bytes and no passphrase are ever persisted here (the cert chain + metadata is persisted
 * separately, encrypted, by [TinkCertStore]).
 */
public class AndroidKeyStoreImporter(
    public val alias: String = DEFAULT_ALIAS,
) {
    /**
     * The two physical AndroidKeyStore slots this importer ping-pongs between so a rotation can import
     * the NEW key WITHOUT overwriting the still-live OLD key (single-commit rotation, see
     * [AndroidIdentityRepository]). Neither is inherently "live" — the Tink-encrypted live pointer
     * ([StoredIdentityMetadata.keyStoreAlias]) names whichever slot currently holds the live key. Each
     * `setEntry` is per-alias atomic, so importing into one slot never disturbs the other.
     */
    public val primarySlot: String get() = alias
    public val secondarySlot: String get() = alias + STAGING_SUFFIX

    /**
     * Parse+validate then import into [slot]. Returns the [ParsedClientIdentity] so the caller can
     * persist the chain/metadata and derive a display summary. Throws (leaving [slot] — and every other
     * slot — untouched, validate-before-persist) on:
     *  - wrong passphrase → `UnrecoverableKeyException`,
     *  - corrupt / not-a-p12 → `Pkcs12DecodeException`,
     *  - certs-only (no key) → `NoClientIdentityException`.
     */
    public fun import(p12Bytes: ByteArray, passphrase: String, slot: String = alias): ParsedClientIdentity {
        // Step 1 — validate. Throws before we touch AndroidKeyStore (validate-before-persist).
        val parsed = Pkcs12Parse.parse(p12Bytes, passphrase)
        // Step 2 — persist the key non-exportably into `slot` (per-alias atomic; other slots untouched).
        persist(parsed, slot)
        return parsed
    }

    /** The AndroidKeyStore-backed (opaque, non-exportable) private key handle in [slot], or null. */
    public fun loadPrivateKey(slot: String = alias): PrivateKey? =
        androidKeyStore().getKey(slot, null) as? PrivateKey

    /** The cert chain AndroidKeyStore stored alongside the key entry in [slot]; null if none. */
    public fun loadCertificateChain(slot: String = alias): List<X509Certificate>? =
        androidKeyStore().getCertificateChain(slot)
            ?.filterIsInstance<X509Certificate>()
            ?.takeIf { it.isNotEmpty() }

    /** Cheap existence check for [slot] (does NOT read key material). */
    public fun hasInstalledKey(slot: String = alias): Boolean = androidKeyStore().isKeyEntry(slot)

    /** Delete the imported key entry in [slot]. Idempotent (a missing alias is a no-op). */
    public fun remove(slot: String = alias) {
        val keyStore = androidKeyStore()
        if (keyStore.containsAlias(slot)) {
            keyStore.deleteEntry(slot)
        }
    }

    private fun persist(parsed: ParsedClientIdentity, slot: String) {
        val keyStore = androidKeyStore()
        keyStore.setEntry(
            slot,
            KeyStore.PrivateKeyEntry(parsed.privateKey, parsed.fullChain.toTypedArray()),
            keyProtection(parsed.keyAlgorithm),
        )
    }

    private fun androidKeyStore(): KeyStore =
        KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }

    /**
     * Protection for the imported client-auth key. TLS client authentication signs the
     * `CertificateVerify`, so SIGN is always required; RSA additionally needs DECRYPT (RSA key
     * transport in TLS ≤1.2) and its padding set. EC keys reject paddings, so only SIGN is granted.
     * A broad digest set keeps TLS 1.2/1.3 signature negotiation working. The key stays
     * non-exportable (AndroidKeyStore has no export path).
     */
    private fun keyProtection(keyAlgorithm: String): KeyProtection {
        val isRsa = keyAlgorithm.equals(KeyProperties.KEY_ALGORITHM_RSA, ignoreCase = true)
        val purposes = if (isRsa) {
            KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_DECRYPT
        } else {
            KeyProperties.PURPOSE_SIGN
        }
        val builder = KeyProtection.Builder(purposes)
            .setDigests(
                KeyProperties.DIGEST_NONE,
                KeyProperties.DIGEST_SHA256,
                KeyProperties.DIGEST_SHA384,
                KeyProperties.DIGEST_SHA512,
            )
        if (isRsa) {
            builder
                .setSignaturePaddings(
                    KeyProperties.SIGNATURE_PADDING_RSA_PKCS1,
                    KeyProperties.SIGNATURE_PADDING_RSA_PSS,
                )
                .setEncryptionPaddings(
                    KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1,
                    KeyProperties.ENCRYPTION_PADDING_RSA_OAEP,
                )
        }
        return builder.build()
    }

    public companion object {
        /** The single device-identity alias this app presents (one pinned identity, plan §8). */
        public const val DEFAULT_ALIAS: String = "webterm-device-identity"

        /** Suffix of the second ping-pong slot ([secondarySlot]) used for single-commit rotation. */
        public const val STAGING_SUFFIX: String = ".staging"
        private const val ANDROID_KEYSTORE = "AndroidKeyStore"
    }
}
