import Foundation
import Testing
import WireProtocol

// T-iOS-3 · 冻结契约常量绊线：Tunables（§3.2.1 唯一取值表）与 WireConstants。
// 这些断言故意逐字重复取值表——改常量必须回 T-iOS-3 改契约并同步这里。

@Test("Tunables 取值与 §3.2.1 表逐项一致")
func tunablesMatchFrozenValueTable() {
    #expect(Tunables.pingInterval == .seconds(25))
    #expect(Tunables.pongMissLimit == 2)
    #expect(Tunables.listPollInterval == .seconds(5)) // public/launcher.ts:30 REFRESH_MS
    #expect(Tunables.telemetryStaleTtlMs == 30_000) // public/tabs.ts:45 STATUSLINE_TTL_MS
    #expect(Tunables.digestFadeDelay == .seconds(8))
    #expect(Tunables.titleMaxLength == 256)
    #expect(Tunables.pairingProbeTimeout == .seconds(10)) // §3.4 契约裁定 2026-07-04
    #expect(Tunables.maxWSMessageBytes == 16 * 1024 * 1024)
}

@Test("maxWSMessageBytes ≥ 6 × 默认 SCROLLBACK_BYTES(2MiB)（JSON 转义最坏膨胀系数）")
func maxWSMessageBytesCoversWorstCaseReplayExpansion() {
    // Arrange — src/config.ts:39 DEFAULT_SCROLLBACK_BYTES = 2MiB；\uXXXX 转义最坏 6×
    let defaultScrollbackBytes = 2 * 1024 * 1024
    let worstCaseEscapeFactor = 6

    // Assert
    #expect(Tunables.maxWSMessageBytes >= worstCaseEscapeFactor * defaultScrollbackBytes)
}

@Test("WireConstants：wsPath/soft-reset 前缀/spawn 失败码/resize 区间")
func wireConstantsMatchServer() {
    #expect(WireConstants.wsPath == "/term") // src/config.ts:41 DEFAULT_WS_PATH
    #expect(WireConstants.replaySoftResetPrefix == "\u{1B}[0m") // src/types.ts:167-170
    #expect(WireConstants.spawnFailedExitCode == -1) // M4
    #expect(WireConstants.resizeRange == 1...1000) // src/protocol.ts:113-115
}

@Test("TransportConnection：能力句柄原样保存；frames finish = 干净断线")
func transportConnectionHoldsCapabilityHandles() async throws {
    // Arrange
    actor Recorder {
        var sentFrames: [String] = []
        var isClosed = false
        func recordSend(_ frame: String) { sentFrames.append(frame) }
        func recordClose() { isClosed = true }
    }
    let recorder = Recorder()
    let connection = TransportConnection(
        frames: AsyncThrowingStream { continuation in
            continuation.yield("{\"type\":\"output\",\"data\":\"x\"}")
            continuation.finish()
        },
        send: { await recorder.recordSend($0) },
        close: { await recorder.recordClose() }
    )

    // Act
    var received: [String] = []
    for try await frame in connection.frames {
        received.append(frame)
    }
    try await connection.send("{\"type\":\"reject\"}")
    await connection.close()

    // Assert
    #expect(received == ["{\"type\":\"output\",\"data\":\"x\"}"])
    #expect(await recorder.sentFrames == ["{\"type\":\"reject\"}"])
    #expect(await recorder.isClosed)
}

@Test("ClaudeStatus/GateKind rawValue 与服务器字面量一致")
func statusAndGateRawValuesMatchServer() {
    #expect(ClaudeStatus.working.rawValue == "working")
    #expect(ClaudeStatus.waiting.rawValue == "waiting")
    #expect(ClaudeStatus.idle.rawValue == "idle")
    #expect(ClaudeStatus.unknown.rawValue == "unknown")
    #expect(ClaudeStatus.stuck.rawValue == "stuck")
    #expect(GateKind.tool.rawValue == "tool")
    #expect(GateKind.plan.rawValue == "plan")
}
