import Foundation
import HostRegistry
import SessionCore
import Testing
import WireProtocol
@testable import WebTerm

/// T-iOS-15 · Session-activity bridge tests: the third fan-out branch that
/// (a) persists the server-adopted sessionId per host (`LastSessionStore`,
///     the "继续上次" cold-start source),
/// (b) forwards gate pending↔lifted into
///     `SessionListViewModel.setPendingApproval` (the ⚠ overlay hook the list
///     VM header documents), and
/// (c) on exit clears both — a dead session must not stay ⚠ nor be offered
///     as "继续上次".
@MainActor
@Suite("SessionActivityBridge")
struct SessionActivityBridgeTests {
    // MARK: - Test doubles

    /// Thread-safe in-memory `LastSessionStore` that records every write.
    final class RecordingLastSessionStore: LastSessionStore, @unchecked Sendable {
        private let lock = NSLock()
        private var storage: [UUID: UUID] = [:]
        private var writes: [(id: UUID?, host: UUID)] = []

        func lastSessionId(host: UUID) -> UUID? {
            lock.lock()
            defer { lock.unlock() }
            return storage[host]
        }

        func setLastSessionId(_ id: UUID?, host: UUID) {
            lock.lock()
            defer { lock.unlock() }
            storage[host] = id
            writes = writes + [(id, host)]
        }

        var writeCount: Int {
            lock.lock()
            defer { lock.unlock() }
            return writes.count
        }
    }

    // MARK: - Harness

    @MainActor
    final class Harness {
        let hostId = UUID()
        let store = RecordingLastSessionStore()
        let continuation: AsyncStream<SessionEvent>.Continuation
        let bridge: SessionActivityBridge
        private(set) var pendingChanges: [(sessionId: UUID, pending: Bool)] = []

        init() {
            let (stream, continuation) = AsyncStream<SessionEvent>.makeStream()
            self.continuation = continuation
            var recorder: (@MainActor (UUID, Bool) -> Void)!
            let bridge = SessionActivityBridge(
                events: stream, hostId: hostId, lastSessionStore: store,
                onPendingChanged: { sessionId, pending in
                    recorder(sessionId, pending)
                }
            )
            self.bridge = bridge
            recorder = { [weak self] sessionId, pending in
                self?.pendingChanges.append((sessionId, pending))
            }
            bridge.start()
        }
    }

    private static func toolGate(epoch: Int) -> GateState {
        GateState(kind: .tool, detail: "Bash", epoch: epoch)
    }

    // MARK: - (a) adopted → persisted last session

    @Test("adopted → 记录 adoptedSessionId 并按 host 持久化 lastSessionId")
    func adoptedPersistsLastSession() async {
        let harness = Harness()
        let sessionId = UUID()

        harness.continuation.yield(.adopted(sessionId: sessionId))
        await harness.bridge.waitUntilProcessed(eventCount: 1)

        #expect(harness.bridge.adoptedSessionId == sessionId)
        #expect(harness.store.lastSessionId(host: harness.hostId) == sessionId)
    }

    @Test("重连拿到新 id → 采用并覆盖持久化（永远采用服务器回发的 id）")
    func readoptionOverwritesPersistedId() async {
        let harness = Harness()
        let first = UUID()
        let second = UUID()

        harness.continuation.yield(.adopted(sessionId: first))
        harness.continuation.yield(.adopted(sessionId: second))
        await harness.bridge.waitUntilProcessed(eventCount: 2)

        #expect(harness.bridge.adoptedSessionId == second)
        #expect(harness.store.lastSessionId(host: harness.hostId) == second)
    }

    // MARK: - (b) gate → pending overlay forwarding

    @Test("gate 上升/解除 → setPendingApproval(true)/(false) 带 adopted 的 sessionId")
    func gateForwardsPendingWithAdoptedId() async {
        let harness = Harness()
        let sessionId = UUID()

        harness.continuation.yield(.adopted(sessionId: sessionId))
        harness.continuation.yield(.gate(Self.toolGate(epoch: 1)))
        harness.continuation.yield(.gate(nil))
        await harness.bridge.waitUntilProcessed(eventCount: 3)

        #expect(harness.pendingChanges.count == 2)
        #expect(harness.pendingChanges[0].sessionId == sessionId)
        #expect(harness.pendingChanges[0].pending == true)
        #expect(harness.pendingChanges[1].sessionId == sessionId)
        #expect(harness.pendingChanges[1].pending == false)
    }

    @Test("adopted 之前到达的 gate → 不转发（没有可关联的 sessionId）")
    func gateBeforeAdoptionIsDropped() async {
        let harness = Harness()

        harness.continuation.yield(.gate(Self.toolGate(epoch: 1)))
        await harness.bridge.waitUntilProcessed(eventCount: 1)

        #expect(harness.pendingChanges.isEmpty)
    }

    // MARK: - (c) exited → clear overlay + last session

    @Test("exited → 清 ⚠ overlay 且清掉 lastSessionId（死会话不再被『继续上次』指向）")
    func exitClearsOverlayAndLastSession() async {
        let harness = Harness()
        let sessionId = UUID()

        harness.continuation.yield(.adopted(sessionId: sessionId))
        harness.continuation.yield(.gate(Self.toolGate(epoch: 1)))
        harness.continuation.yield(.exited(code: 0, reason: nil))
        await harness.bridge.waitUntilProcessed(eventCount: 3)

        #expect(harness.pendingChanges.last?.pending == false)
        #expect(harness.pendingChanges.last?.sessionId == sessionId)
        #expect(harness.store.lastSessionId(host: harness.hostId) == nil)
    }

    @Test("output/telemetry/connection 与本桥无关 → 不写 store、不发 overlay")
    func unrelatedEventsAreIgnored() async {
        let harness = Harness()

        harness.continuation.yield(.output("hello"))
        harness.continuation.yield(.connection(.connected))
        await harness.bridge.waitUntilProcessed(eventCount: 2)

        #expect(harness.pendingChanges.isEmpty)
        #expect(harness.store.writeCount == 0)
    }
}
