import Foundation
import Testing
import WireProtocol

// T-iOS-3 · 从 test/protocol.test.ts 移植的跨实现向量 + decodeServer 白名单解码。
// 双实现（TS/Swift）防漂移：这里锁当前语义，真服务器回归由 T-iOS-16 CI 常驻看护。

private let validUUID = "f47ac10b-58cc-4372-a567-0e02b2c3d479"

// MARK: - Validation.isValidSessionId（SESSION_ID_RE 向量, test/protocol.test.ts:19-49）

@Test("isValidSessionId：合法 UUID v4 通过")
func sessionIdAcceptsValidUUIDv4() {
    #expect(Validation.isValidSessionId(validUUID))
}

@Test("isValidSessionId：大写十六进制也通过（服务器正则 /i）")
func sessionIdAcceptsUppercaseHex() {
    #expect(Validation.isValidSessionId("F47AC10B-58CC-4372-A567-0E02B2C3D479"))
}

@Test("isValidSessionId：variant=8 的 UUID v4 通过")
func sessionIdAcceptsVariant8() {
    #expect(Validation.isValidSessionId("550e8400-e29b-41d4-8716-446655440000"))
}

@Test("isValidSessionId：非 UUID / 空串 / v1 / 错误 variant 全部拒绝（M7）",
      arguments: [
          "abc123",
          "",
          "550e8400-e29b-11d4-a716-446655440000", // v1：版本位=1
          "f47ac10b-58cc-4372-c567-0e02b2c3d479", // variant 'c'（非 8/9/a/b）
          "f47ac10b58cc4372a5670e02b2c3d479", // 缺分隔符
          "f47ac10b-58cc-4372-a567-0e02b2c3d47", // 少一位
          "g47ac10b-58cc-4372-a567-0e02b2c3d479", // 非法字符
      ])
func sessionIdRejectsInvalid(candidate: String) {
    #expect(!Validation.isValidSessionId(candidate))
}

@Test("isValidSessionId 与测试侧服务器正则镜像逐向量同判（防移植走样）")
func sessionIdAgreesWithServerRegexMirror() {
    // Arrange — 正反两面向量各若干
    let candidates = [
        validUUID, "F47AC10B-58CC-4372-A567-0E02B2C3D479",
        "550e8400-e29b-41d4-8716-446655440000", "abc123", "",
        "550e8400-e29b-11d4-a716-446655440000",
        "f47ac10b-58cc-4372-c567-0e02b2c3d479",
    ]

    for candidate in candidates {
        // Act / Assert
        #expect(Validation.isValidSessionId(candidate)
            == ServerViewParser.isServerValidSessionId(candidate),
            "分歧向量: \(candidate)")
    }
}

// MARK: - isValidResize / isAbsoluteCwd（src/protocol.ts:113-115,142-149）

@Test("isValidResize：边界 1/1000 通过，0/1001 拒绝")
func resizeValidationBoundaries() {
    #expect(Validation.isValidResize(cols: 1, rows: 1))
    #expect(Validation.isValidResize(cols: 1000, rows: 1000))
    #expect(Validation.isValidResize(cols: 120, rows: 40))
    #expect(!Validation.isValidResize(cols: 0, rows: 40))
    #expect(!Validation.isValidResize(cols: 1001, rows: 40))
    #expect(!Validation.isValidResize(cols: 80, rows: 0))
    #expect(!Validation.isValidResize(cols: 80, rows: 1001))
}

@Test("isAbsoluteCwd：'/a' 通过；'a' 与空串拒绝")
func absoluteCwdValidation() {
    #expect(Validation.isAbsoluteCwd("/a"))
    #expect(!Validation.isAbsoluteCwd("a"))
    #expect(!Validation.isAbsoluteCwd(""))
}

// MARK: - 服务器 parseClientMessage 向量 ↔ 测试侧镜像（transcribed from test/protocol.test.ts）

@Test("服务器视角镜像：合法客户端帧向量全部接受（test/protocol.test.ts:53-124）")
func serverMirrorAcceptsValidClientVectors() {
    #expect(ServerViewParser.parse("{\"type\":\"attach\",\"sessionId\":null}")
        == .attach(sessionId: nil, cwd: nil))
    #expect(ServerViewParser.parse("{\"type\":\"attach\",\"sessionId\":\"\(validUUID)\"}")
        == .attach(sessionId: validUUID, cwd: nil))
    #expect(ServerViewParser.parse("{\"type\":\"input\",\"data\":\"ls -la\\r\"}")
        == .input(data: "ls -la\r"))
    #expect(ServerViewParser.parse("{\"type\":\"resize\",\"cols\":1,\"rows\":1}")
        == .resize(cols: 1, rows: 1))
    #expect(ServerViewParser.parse("{\"type\":\"resize\",\"cols\":1000,\"rows\":1000}")
        == .resize(cols: 1000, rows: 1000))
    #expect(ServerViewParser.parse("{\"type\":\"approve\"}") == .approve)
    #expect(ServerViewParser.parse("{\"type\":\"reject\"}") == .reject)
    // 已移除的 blur 类型必须被拒（test/protocol.test.ts:120-123）
    #expect(ServerViewParser.parse("{\"type\":\"blur\"}") == nil)
    // 多余字段忽略（test/protocol.test.ts:284）
    #expect(ServerViewParser.parse("{\"type\":\"resize\",\"cols\":80,\"rows\":40,\"extra\":\"ignored\"}")
        == .resize(cols: 80, rows: 40))
}

@Test("服务器视角镜像：非法客户端帧向量全部拒绝（test/protocol.test.ts:128-266）",
      arguments: [
          "not json at all", "", "null", "[]", "42",
          "{\"type\":\"ping\"}", "{\"data\":\"hello\"}", "{\"type\":42}",
          "{\"type\":\"resize\",\"cols\":0,\"rows\":40}",
          "{\"type\":\"resize\",\"cols\":1001,\"rows\":40}",
          "{\"type\":\"resize\",\"cols\":80,\"rows\":0}",
          "{\"type\":\"resize\",\"cols\":80,\"rows\":1001}",
          "{\"type\":\"resize\",\"cols\":80.5,\"rows\":40}",
          "{\"type\":\"resize\",\"cols\":80,\"rows\":24.9}",
          "{\"type\":\"resize\",\"cols\":\"80\",\"rows\":40}",
          "{\"type\":\"resize\",\"rows\":40}",
          "{\"type\":\"resize\",\"cols\":80}",
          "{\"type\":\"input\",\"data\":42}",
          "{\"type\":\"input\",\"data\":null}",
          "{\"type\":\"input\"}",
          "{\"type\":\"input\",\"data\":{}}",
          "{\"type\":\"attach\",\"sessionId\":\"abc123\"}",
          "{\"type\":\"attach\",\"sessionId\":\"\"}",
          "{\"type\":\"attach\",\"sessionId\":42}",
          "{\"type\":\"attach\"}",
          "{\"type\":\"attach\",\"sessionId\":null,\"cwd\":\"relative\"}",
          "{\"type\":\"attach\",\"sessionId\":null,\"cwd\":42}",
      ])
func serverMirrorRejectsInvalidClientVectors(frame: String) {
    #expect(ServerViewParser.parse(frame) == nil)
}

// MARK: - decodeServer 合法帧（5 种 case）

@Test("decodeServer(attached)：合法 UUID → .attached；大写 UUID 同样接受")
func decodeAttachedFrames() {
    // Arrange
    let expected = UUID(uuidString: validUUID)!

    // Act / Assert
    #expect(MessageCodec.decodeServer("{\"type\":\"attached\",\"sessionId\":\"\(validUUID)\"}")
        == .attached(sessionId: expected))
    #expect(MessageCodec.decodeServer(
        "{\"type\":\"attached\",\"sessionId\":\"F47AC10B-58CC-4372-A567-0E02B2C3D479\"}")
        == .attached(sessionId: expected))
}

@Test("decodeServer(output)：ANSI 字节原样进 data（serialize 向量, test/protocol.test.ts:303-307）")
func decodeOutputFrame() {
    #expect(MessageCodec.decodeServer("{\"type\":\"output\",\"data\":\"\\u001b[1;32mHello\\u001b[0m\"}")
        == .output(data: "\u{1B}[1;32mHello\u{1B}[0m"))
}

@Test("decodeServer(exit)：仅 code / code+reason 两形状（-1 = spawn 失败）")
func decodeExitFrames() {
    #expect(MessageCodec.decodeServer("{\"type\":\"exit\",\"code\":0}")
        == .exit(code: 0, reason: nil))
    #expect(MessageCodec.decodeServer(
        "{\"type\":\"exit\",\"code\":-1,\"reason\":\"spawn failed: /bin/badshell\"}")
        == .exit(code: WireConstants.spawnFailedExitCode, reason: "spawn failed: /bin/badshell"))
}

@Test("decodeServer(status)：5 个 ClaudeStatus 值全解；缺省 detail/pending/gate 取安全默认",
      arguments: ["working", "waiting", "idle", "unknown", "stuck"])
func decodeStatusAllClaudeStatuses(raw: String) throws {
    // Arrange / Act
    let message = MessageCodec.decodeServer("{\"type\":\"status\",\"status\":\"\(raw)\"}")

    // Assert
    let expectedStatus = try #require(ClaudeStatus(rawValue: raw))
    #expect(message == .status(expectedStatus, detail: nil, pending: false, gate: nil))
}

@Test("decodeServer(status)：pending/gate/detail 全携带（tool 与 plan 两种 gate）")
func decodeStatusWithGate() {
    #expect(MessageCodec.decodeServer(
        "{\"type\":\"status\",\"status\":\"waiting\",\"detail\":\"Bash\",\"pending\":true,\"gate\":\"tool\"}")
        == .status(.waiting, detail: "Bash", pending: true, gate: .tool))
    #expect(MessageCodec.decodeServer(
        "{\"type\":\"status\",\"status\":\"waiting\",\"pending\":true,\"gate\":\"plan\"}")
        == .status(.waiting, detail: nil, pending: true, gate: .plan))
}

@Test("decodeServer(status)：未知 gate 值按缺席容忍（保留 pending 信号，不丢帧）")
func decodeStatusToleratesUnknownGate() {
    #expect(MessageCodec.decodeServer(
        "{\"type\":\"status\",\"status\":\"waiting\",\"pending\":true,\"gate\":\"alien\"}")
        == .status(.waiting, detail: nil, pending: true, gate: nil))
    // pending 非 bool → 安全默认 false
    #expect(MessageCodec.decodeServer(
        "{\"type\":\"status\",\"status\":\"working\",\"pending\":\"yes\"}")
        == .status(.working, detail: nil, pending: false, gate: nil))
}

@Test("decodeServer(telemetry)：全字段样本逐字段解出（src/types.ts:406-416 镜像）")
func decodeTelemetryFullSample() throws {
    // Arrange
    let frame = """
    {"type":"telemetry","telemetry":{"contextUsedPct":42.5,"costUsd":1.23,\
    "linesAdded":10,"linesRemoved":2,"model":"Fable 5","effort":"high",\
    "pr":{"number":7,"url":"https://github.com/x/y/pull/7","reviewState":"APPROVED"},\
    "rate":{"fiveHourPct":12.5,"sevenDayPct":33},"at":1751600000000}}
    """

    // Act
    let message = try #require(MessageCodec.decodeServer(frame))

    // Assert
    let expected = StatusTelemetry(
        contextUsedPct: 42.5, costUsd: 1.23, linesAdded: 10, linesRemoved: 2,
        model: "Fable 5", effort: "high",
        pr: PrInfo(number: 7, url: "https://github.com/x/y/pull/7", reviewState: "APPROVED"),
        rate: RateInfo(fiveHourPct: 12.5, sevenDayPct: 33),
        at: 1_751_600_000_000
    )
    #expect(message == .telemetry(expected))
}

@Test("decodeServer(telemetry)：全可选字段缺省仍可解（只有 at）")
func decodeTelemetryMinimalSample() {
    #expect(MessageCodec.decodeServer("{\"type\":\"telemetry\",\"telemetry\":{\"at\":123}}")
        == .telemetry(StatusTelemetry(at: 123)))
}

@Test("decodeServer(telemetry)：可选字段类型错误按缺席容忍（帧保留）")
func decodeTelemetryToleratesWrongTypedOptionalField() {
    #expect(MessageCodec.decodeServer(
        "{\"type\":\"telemetry\",\"telemetry\":{\"at\":5,\"costUsd\":\"lots\",\"pr\":42}}")
        == .telemetry(StatusTelemetry(at: 5)))
}

// MARK: - decodeServer 非法帧全表 → nil（永不 throw）

@Test("decodeServer：非法帧全表 → nil（坏 JSON/未知 type/字段缺失或错型/at 缺失）",
      arguments: [
          "not json at all", "", "null", "[]", "42", "true",
          "{\"type\":\"ping\"}", "{\"data\":\"hello\"}", "{\"type\":42}",
          // attached：非 UUID / v1 / 缺失 / 错型（M7）
          "{\"type\":\"attached\",\"sessionId\":\"abc123\"}",
          "{\"type\":\"attached\",\"sessionId\":\"550e8400-e29b-11d4-a716-446655440000\"}",
          "{\"type\":\"attached\"}",
          "{\"type\":\"attached\",\"sessionId\":42}",
          // output：data 缺失 / 错型
          "{\"type\":\"output\"}",
          "{\"type\":\"output\",\"data\":42}",
          // exit：code 缺失 / 字符串 / 非整数
          "{\"type\":\"exit\"}",
          "{\"type\":\"exit\",\"code\":\"0\"}",
          "{\"type\":\"exit\",\"code\":1.5}",
          // status：status 缺失 / 未知值 / 错型（白名单）
          "{\"type\":\"status\"}",
          "{\"type\":\"status\",\"status\":\"sleeping\"}",
          "{\"type\":\"status\",\"status\":42}",
          // telemetry：telemetry 键缺失 / 非对象 / at 缺失 / at 错型
          "{\"type\":\"telemetry\"}",
          "{\"type\":\"telemetry\",\"telemetry\":\"x\"}",
          "{\"type\":\"telemetry\",\"telemetry\":{\"costUsd\":1}}",
          "{\"type\":\"telemetry\",\"telemetry\":{\"at\":\"now\"}}",
      ])
func decodeServerReturnsNilForMalformedFrames(frame: String) {
    #expect(MessageCodec.decodeServer(frame) == nil)
}
