package wang.yaojia.webterm.transport

import okhttp3.OkHttpClient
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.X509TrustManager

/**
 * The optional mTLS material a [ClientIdentityProvider] hands to the shared client (plan §2 mTLS
 * row, §8). Both fields are required together — OkHttp binds an `(SSLSocketFactory, X509TrustManager)`
 * pair, never one without the other.
 *
 * The [sslSocketFactory] SHOULD be backed by a **re-reading `X509KeyManager`** (built in
 * `:client-tls-android`, wired in `:app`) so a mid-run cert rotation is presented on the next
 * handshake without rebuilding the client (plan §8 / R4). This module treats the pair as opaque —
 * it never parses PKCS#12, never touches `AndroidKeyStore`, and keeps depending ONLY on
 * `:wire-protocol`.
 */
public class ClientIdentity(
    public val sslSocketFactory: SSLSocketFactory,
    public val trustManager: X509TrustManager,
)

/**
 * The mTLS injection seam, defined LOCALLY in `:transport-okhttp` on purpose (plan §5 A7): it lets
 * `:app` supply a `:client-tls-android`-backed client identity WITHOUT this module gaining a
 * `:client-tls` dependency edge — the module stays pure `:wire-protocol` + OkHttp.
 *
 * A SAM ([fun interface]) so the common "no device cert installed" case is just
 * `ClientIdentityProvider.NONE`, and a test/`:app` can supply `ClientIdentityProvider { currentId }`.
 *
 * [currentIdentity] is read ONCE, when the shared [OkHttpClient] is built ([OkHttpClientFactory]).
 * Live cert rotation is the injected `SSLSocketFactory`'s concern (its `X509KeyManager` re-reads the
 * `AndroidKeyStore` per handshake), not this seam's — so returning a fresh value later has no effect,
 * by design.
 */
public fun interface ClientIdentityProvider {
    /** The current client identity, or null for no mTLS (default system trust only). */
    public fun currentIdentity(): ClientIdentity?

    public companion object {
        /** No client certificate — the default for unpaired / bare-LAN hosts. */
        public val NONE: ClientIdentityProvider = ClientIdentityProvider { null }
    }
}

/**
 * Builds the SINGLE shared [OkHttpClient] both transports use (plan §2 "one shared OkHttpClient"):
 * one client → one `SSLSocketFactory` → mTLS applies uniformly to WS and REST, and the connection
 * pool is shared.
 *
 * Two invariants baked in here:
 *  - **`.cache(null)`** (plan §8): no ephemeral/disk HTTP cache — preview/diff bodies can hold
 *    terminal secrets, so nothing is persisted.
 *  - **Server-cert trust = default system trust.** Tunnel (`*.terminal.yaojia.wang`) and Tailscale
 *    MagicDNS present real LE certs, so NO custom `X509TrustManager` is installed for the common
 *    case (plan §6.9). Bare-LAN `ws://` cleartext is an `:app` `network_security_config` allowlist
 *    concern, NOT this module's — a plain `ws://` upgrade needs no TLS here at all.
 */
public object OkHttpClientFactory {
    /**
     * @param identityProvider optional mTLS material; [ClientIdentityProvider.NONE] → default trust,
     *   no client cert.
     */
    public fun create(
        identityProvider: ClientIdentityProvider = ClientIdentityProvider.NONE,
    ): OkHttpClient {
        val builder = OkHttpClient.Builder().cache(null)
        identityProvider.currentIdentity()?.let { identity ->
            builder.sslSocketFactory(identity.sslSocketFactory, identity.trustManager)
        }
        return builder.build()
    }
}

/**
 * Convenience composition root proving the "one client for BOTH transports" contract: builds the
 * shared [OkHttpClient] once via [OkHttpClientFactory] and hands the SAME instance to the WS and
 * REST transports (the WS one derives a streaming-tuned variant via `newBuilder()`, which shares the
 * connection pool + dispatcher + mTLS). `:app`'s Hilt module will typically call this.
 */
public class OkHttpTransports private constructor(
    public val term: OkHttpTermTransport,
    public val http: OkHttpHttpTransport,
    public val client: OkHttpClient,
) {
    public companion object {
        public fun create(
            identityProvider: ClientIdentityProvider = ClientIdentityProvider.NONE,
        ): OkHttpTransports {
            val client = OkHttpClientFactory.create(identityProvider)
            return OkHttpTransports(
                term = OkHttpTermTransport(client),
                http = OkHttpHttpTransport(client),
                client = client,
            )
        }
    }
}
