package wang.yaojia.webterm.testsupport

import wang.yaojia.webterm.wire.ConnectionPinger
import wang.yaojia.webterm.wire.HostEndpoint
import wang.yaojia.webterm.wire.PingableConnection
import wang.yaojia.webterm.wire.PingableTermTransport

/**
 * A controllable [ConnectionPinger] double. `PingScheduler` (A5) calls [ping] once per interval and
 * counts misses against `Tunables.PONG_MISS_LIMIT`; this fake lets a test decide, deterministically,
 * which pings answer and which are dropped.
 *
 * Scripted results are consumed FIFO; once exhausted [ping] returns [defaultPong]. `true` = pong
 * arrived in time, `false` = a miss.
 */
public class FakeConnectionPinger(
    private val defaultPong: Boolean = true,
) : ConnectionPinger {
    private val lock = Any()
    private val scriptedPongs = ArrayDeque<Boolean>()
    private var callCount = 0

    /** Total [ping] calls so far — for `PingScheduler` assertions. */
    public val pingCallCount: Int
        get() = synchronized(lock) { callCount }

    /** Queue pong outcomes consumed FIFO by [ping]; when the queue drains, [ping] uses [defaultPong]. */
    public fun scriptPongs(vararg results: Boolean) {
        synchronized(lock) { scriptedPongs.addAll(results.toList()) }
    }

    override suspend fun ping(): Boolean =
        synchronized(lock) {
            callCount++
            scriptedPongs.removeFirstOrNull() ?: defaultPong
        }
}

/**
 * A [FakeTermTransport] that ALSO implements [PingableTermTransport] (A3, mirrors iOS's
 * `transport as? any PingableTermTransport` path). Use this when a test wants `SessionEngine`/
 * `PingScheduler` to drive keep-alive through [connectPingable]; use the plain [FakeTermTransport]
 * when the closure-driven ping path is under test instead.
 *
 * The [pinger] is shared across every connection this transport opens, so a test can script pong
 * outcomes up front and inspect [FakeConnectionPinger.pingCallCount] after — reconnects reuse it,
 * which matches "one keep-alive policy per transport" for assertion simplicity.
 */
public class FakePingableTermTransport(
    public val pinger: FakeConnectionPinger = FakeConnectionPinger(),
) : FakeTermTransport(), PingableTermTransport {

    /** How many times [connectPingable] (vs the plain [connect]) was taken — for path assertions. */
    public var connectPingableCount: Int = 0
        private set

    override suspend fun connectPingable(endpoint: HostEndpoint): PingableConnection {
        val connection = connect(endpoint)
        synchronized(this) { connectPingableCount++ }
        return PingableConnection(connection = connection, pinger = pinger)
    }
}
