//
// MirrorLifecycleTests.swift — T-iOS-16 测试 4/6/7：JOIN mirror 与会话生命周期
//
//   4. 双客户端 JOIN mirror：A、B 同 attach 一个会话（新 attach 加入镜像、绝不
//      踢人，src/session/manager.ts:108-174），A input，B 收到 output。
//   6. DELETE /live-sessions/:id（kill）→ 镜像客户端观察到 WS close —— 不是
//      exit 帧：manager.killById 先 ws.close() 全部客户端再 kill PTY
//      （src/session/manager.ts:202-212），exit 广播只投给 OPEN socket
//      （sendIfOpen）；且该 id 从 GET /live-sessions 消失。
//   7. 自然退出广播：A input "exit\r" → bash 退出 → pty.onExit 向仍开着的
//      socket 广播 {type:'exit'}（src/session/session.ts:146-151）→ 镜像 B 收到。

import Foundation
import Testing
import WireProtocol

@Suite("T-iOS-16 镜像与生命周期(真服务器)", .serialized, .timeLimit(.minutes(5)))
struct MirrorLifecycleTests {

    @Test("双客户端 JOIN mirror:B attach 同一会话拿到同 id,A input → B 收 output")
    func mirrorJoinSharesOutput() async throws {
        // Arrange: A 开新会话,B 以同 id 加入(镜像,不踢 A)。
        let server = try await ServerHarness.shared.server()
        let clientA = WSTestClient(server: server, origin: server.origin)
        let clientB = WSTestClient(server: server, origin: server.origin)
        defer {
            clientA.close()
            clientB.close()
        }
        let sessionId = try await attachAndAwaitAttached(client: clientA, sessionId: nil)
        let adoptedByB = try await attachAndAwaitAttached(client: clientB, sessionId: sessionId)
        #expect(adoptedByB == sessionId, "镜像 attach 应采用同一会话 id(JOIN 不踢)")

        // Act: A 打字 —— 哨兵 $((1300+37)) 只有 shell 执行才产出 1337。
        var readerB = TranscriptReader(client: clientB)
        try await clientA.send(shellCommand("echo \"MIR:$((1300+37))\""))

        // Assert: B(镜像端)收到 A 命令的输出广播。
        try await readerB.awaitContains("MIR:1337")

        // Cleanup
        clientA.close()
        clientB.close()
        await killSessionBestEffort(server: server, id: sessionId)
    }

    @Test("DELETE kill → 镜像观察到 WS close(非 exit 帧),id 从 /live-sessions 消失")
    func killClosesMirrorSocketsWithoutExitFrame() async throws {
        // Arrange
        let server = try await ServerHarness.shared.server()
        let clientA = WSTestClient(server: server, origin: server.origin)
        let clientB = WSTestClient(server: server, origin: server.origin)
        defer {
            clientA.close()
            clientB.close()
        }
        let sessionId = try await attachAndAwaitAttached(client: clientA, sessionId: nil)
        _ = try await attachAndAwaitAttached(client: clientB, sessionId: sessionId)
        let idString = sessionId.uuidString.lowercased()

        // Act: 带 Origin 的 kill(G 类端点,src/server.ts:354-359)。
        let status = try await deleteLiveSession(server: server, id: idString, origin: server.origin)

        // Assert: 204;id 立即从列表消失(killById 同步移除);镜像端观察到 close
        // 且【没有】exit 帧(close-first 语义,src/session/manager.ts:202-212)。
        #expect(status == 204, "kill 应 204,实际 \(status)")
        let idsAfter = try await liveSessionIds(server: server)
        #expect(!idsAfter.contains(idString), "kill 后 id 不应再出现在 GET /live-sessions")

        let observedB = await drainUntilClose(client: clientB)
        #expect(observedB.closed, "镜像客户端 B 应观察到 WS close")
        #expect(!observedB.sawExit, "kill 路径不广播 exit 帧(先 close 后 kill)")
    }

    @Test("自然退出广播:A input exit → 镜像 B 收到 {type:'exit'} 帧")
    func naturalExitBroadcastsExitFrameToMirror() async throws {
        // Arrange
        let server = try await ServerHarness.shared.server()
        let clientA = WSTestClient(server: server, origin: server.origin)
        let clientB = WSTestClient(server: server, origin: server.origin)
        defer {
            clientA.close()
            clientB.close()
        }
        let sessionId = try await attachAndAwaitAttached(client: clientA, sessionId: nil)
        _ = try await attachAndAwaitAttached(client: clientB, sessionId: sessionId)

        // Act: A 让 shell 自然退出。
        try await clientA.send(shellCommand("exit"))

        // Assert: 镜像 B 收到 exit 帧(pty.onExit 广播,src/session/session.ts:146-151)。
        let exit = try await awaitExitFrame(client: clientB)
        #expect(exit.code == 0, "bash `exit` 应干净退出,实际 code=\(exit.code)")

        // Cleanup(已退出的会话残留由服务器回收;kill 尽力而为)。
        clientA.close()
        clientB.close()
        await killSessionBestEffort(server: server, id: sessionId)
    }
}
