package wang.yaojia.webterm.tlsandroid

import java.net.Socket
import java.security.Principal
import java.security.PrivateKey
import java.security.cert.X509Certificate
import javax.net.ssl.SSLEngine
import javax.net.ssl.X509ExtendedKeyManager
import wang.yaojia.webterm.clienttls.ClientKeyManagerLogic
import wang.yaojia.webterm.clienttls.KeyManagerIdentity

/**
 * The live, non-exportable device identity as the KeyManager sees it: the AndroidKeyStore-backed
 * [privateKey] handle (opaque — signing happens inside the keystore) plus the cert chain to present.
 */
public class InstalledIdentity(
    public val alias: String,
    public val keyAlgorithm: String,
    public val privateKey: PrivateKey,
    public val leafCertificate: X509Certificate,
    public val issuerCertificates: List<X509Certificate>,
) {
    /** Full chain, leaf at index 0, as presented to the server. */
    public val chain: List<X509Certificate> get() = listOf(leafCertificate) + issuerCertificates

    internal val keyManagerIdentity: KeyManagerIdentity
        get() = KeyManagerIdentity(alias = alias, keyAlgorithm = keyAlgorithm)
}

/**
 * A **re-reading** `X509KeyManager` (R4): every alias/key/chain lookup consults [identitySource]
 * afresh, so a cert imported or rotated mid-run is presented on the NEXT handshake with no app
 * relaunch and no rebuild of the shared `OkHttpClient`. The alias truth table is delegated to the
 * pure [ClientKeyManagerLogic] (present iff an identity is installed AND its key type is acceptable),
 * keeping the security-load-bearing decision unit-tested at JVM speed in `:client-tls`.
 *
 * Client-only: the server-side methods return null. Key material is gated by `ownsAlias` so a foreign
 * alias never leaks the device identity.
 */
internal class ReReadingX509KeyManager(
    private val identitySource: () -> InstalledIdentity?,
) : X509ExtendedKeyManager() {

    override fun chooseClientAlias(
        keyType: Array<String>?,
        issuers: Array<out Principal>?,
        socket: Socket?,
    ): String? = logicOrNull()?.chooseClientAlias(keyType)

    override fun chooseEngineClientAlias(
        keyType: Array<String>?,
        issuers: Array<out Principal>?,
        engine: SSLEngine?,
    ): String? = logicOrNull()?.chooseClientAlias(keyType)

    override fun getClientAliases(
        keyType: String?,
        issuers: Array<out Principal>?,
    ): Array<String>? = logicOrNull()?.clientAliases(keyType)

    override fun getPrivateKey(alias: String?): PrivateKey? {
        val identity = identitySource() ?: return null
        return if (ClientKeyManagerLogic(identity.keyManagerIdentity).ownsAlias(alias)) {
            identity.privateKey
        } else {
            null
        }
    }

    override fun getCertificateChain(alias: String?): Array<X509Certificate>? {
        val identity = identitySource() ?: return null
        return if (ClientKeyManagerLogic(identity.keyManagerIdentity).ownsAlias(alias)) {
            identity.chain.toTypedArray()
        } else {
            null
        }
    }

    // Client-only: never act as a TLS server.
    override fun chooseServerAlias(
        keyType: String?,
        issuers: Array<out Principal>?,
        socket: Socket?,
    ): String? = null

    override fun getServerAliases(
        keyType: String?,
        issuers: Array<out Principal>?,
    ): Array<String>? = null

    private fun logicOrNull(): ClientKeyManagerLogic? =
        identitySource()?.let { ClientKeyManagerLogic(it.keyManagerIdentity) }
}
