package wang.yaojia.webterm.wiring

import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test
import wang.yaojia.webterm.hostregistry.InMemoryLastSessionStore
import wang.yaojia.webterm.session.Adopted
import wang.yaojia.webterm.session.Connection
import wang.yaojia.webterm.session.ConnectionState
import wang.yaojia.webterm.session.Exited
import wang.yaojia.webterm.session.Output

/**
 * The [SessionActivityBridge] lifecycle contract (plan §5 A29 Verify): it drives the
 * [InMemoryLastSessionStore][wang.yaojia.webterm.hostregistry.InMemoryLastSessionStore] off the engine
 * event stream —
 *  - `Adopted` → SET the server-issued id (so cold-start can offer "continue last");
 *  - `Exited`  → CLEAR it (a dead shell must never be offered);
 *  - a mere detach (`Connection(Closed)`) and plain `Output` leave it untouched (still continuable);
 *  - distinct hosts are keyed independently.
 * Pure JVM, driven under `runTest` with a `flowOf(...)` — no device.
 */
class SessionActivityBridgeTest {

    private val hostA = "host-A"
    private val hostB = "host-B"

    @Test
    fun `adopted sets the last session id for the bound host`() = runTest {
        val store = InMemoryLastSessionStore()
        val bridge = SessionActivityBridge(store, hostId = hostA)

        bridge.observe(flowOf(Adopted("sess-42")))

        assertEquals("sess-42", store.lastSessionId(hostA))
    }

    @Test
    fun `exited clears the last session id`() = runTest {
        val store = InMemoryLastSessionStore()
        store.setLastSessionId("sess-42", hostA) // a previously-adopted session
        val bridge = SessionActivityBridge(store, hostId = hostA)

        bridge.observe(flowOf(Exited(code = 0)))

        assertNull(store.lastSessionId(hostA))
    }

    @Test
    fun `adopt-then-exit leaves no continuable session`() = runTest {
        val store = InMemoryLastSessionStore()
        val bridge = SessionActivityBridge(store, hostId = hostA)

        bridge.observe(flowOf(Adopted("sess-99"), Output("some bytes"), Exited(code = 137, reason = "killed")))

        assertNull(store.lastSessionId(hostA))
    }

    @Test
    fun `a detach (connection closed) does NOT clear the last session`() = runTest {
        val store = InMemoryLastSessionStore()
        val bridge = SessionActivityBridge(store, hostId = hostA)

        // Adopt, then a mere WS close (detach) — the server-side PTY keeps running, so it stays offerable.
        bridge.observe(flowOf(Adopted("sess-7"), Connection(ConnectionState.Closed), Output("bytes")))

        assertEquals("sess-7", store.lastSessionId(hostA))
    }

    @Test
    fun `only the bound host is keyed`() = runTest {
        val store = InMemoryLastSessionStore()
        val bridge = SessionActivityBridge(store, hostId = hostA)

        bridge.observe(flowOf(Adopted("sess-A")))

        assertEquals("sess-A", store.lastSessionId(hostA))
        assertNull(store.lastSessionId(hostB)) // a sibling host is never clobbered
    }

    @Test
    fun `a later adopt overwrites the earlier server-issued id`() = runTest {
        val store = InMemoryLastSessionStore()
        val bridge = SessionActivityBridge(store, hostId = hostA)

        // A reconnect can hand back a fresh id — the LATEST adopted id is the one to offer.
        bridge.observe(flowOf(Adopted("sess-old"), Adopted("sess-new")))

        assertEquals("sess-new", store.lastSessionId(hostA))
    }
}
