package wang.yaojia.webterm.transport

import okhttp3.OkHttpClient
import okhttp3.Request
import wang.yaojia.webterm.wire.HostEndpoint
import wang.yaojia.webterm.wire.PingableConnection
import wang.yaojia.webterm.wire.PingableTermTransport
import wang.yaojia.webterm.wire.TransportConnection
import wang.yaojia.webterm.wire.Tunables
import java.util.concurrent.TimeUnit

/** The `Origin` header name — THE CSWSH defence; stamped byte-equal from [HostEndpoint.originHeader]. */
internal const val HEADER_ORIGIN: String = "Origin"

/**
 * The only concrete WS transport (A7): implements [PingableTermTransport] (and thus `TermTransport`)
 * over OkHttp. `SessionEngine` (A14) cannot tell it apart from the `FakeTermTransport` double.
 *
 * The shared [OkHttpClient] (built by [OkHttpClientFactory], mTLS + `cache(null)` applied) is reused,
 * but WS needs streaming tuning, so a variant is derived via `newBuilder()` — which SHARES the
 * connection pool, dispatcher and `SSLSocketFactory`:
 *  - **`readTimeout(0)`** — a long-lived stream may sit idle between frames; a read timeout would
 *    wrongly kill it. Keep-alive is the ping's job, not the read timeout's.
 *  - **`pingInterval(PING_INTERVAL)`** — OkHttp sends real WS control pings and fails a
 *    pong-less (half-dead) connection, surfacing it as an `onFailure` → flow error → reconnect.
 *
 * `connect` stamps `Origin: endpoint.originHeader` on the upgrade [Request] and suspends until the
 * handshake opens (or rethrows the connect-time failure verbatim, per the frozen contract). The dial
 * is cancellation-safe: if the caller's coroutine is cancelled (or the handshake fails) while it is
 * in flight, the just-created WebSocket is torn down before rethrowing, so no socket is leaked.
 *
 * Inbound frames flow up UNMODIFIED — there is deliberately NO transport-level frame-size cap here.
 * `SessionEngine` (A14) self-measures each inbound frame's UTF-8 size and is the single authoritative
 * classifier of an oversized ring-buffer replay (the non-retryable `REPLAY_TOO_LARGE` failure).
 */
public class OkHttpTermTransport(
    sharedClient: OkHttpClient,
) : PingableTermTransport {

    private val wsClient: OkHttpClient = sharedClient.newBuilder()
        .readTimeout(0, TimeUnit.MILLISECONDS)
        .pingInterval(Tunables.PING_INTERVAL.inWholeMilliseconds, TimeUnit.MILLISECONDS)
        .build()

    override suspend fun connect(endpoint: HostEndpoint): TransportConnection =
        openConnection(endpoint)

    override suspend fun connectPingable(endpoint: HostEndpoint): PingableConnection {
        val connection = openConnection(endpoint)
        return PingableConnection(connection = connection, pinger = connection.pinger)
    }

    private suspend fun openConnection(endpoint: HostEndpoint): OkHttpWebSocketConnection {
        val request = Request.Builder()
            .url(endpoint.wsUrl) // OkHttp maps ws(s):// → http(s):// internally
            .header(HEADER_ORIGIN, endpoint.originHeader)
            .build()
        val connection = OkHttpWebSocketConnection(wsClient, request)
        try {
            connection.awaitOpen()
        } catch (e: Throwable) {
            // Cancelled or failed mid-handshake → abort the socket so it is never leaked, then
            // rethrow verbatim (a connect-time onFailure stays byte-identical for the A9 probe).
            connection.cancel()
            throw e
        }
        return connection
    }
}
