import Foundation
import Testing
import WireProtocol

// T-iOS-3 · MessageCodec.encode 与服务器 parseClientMessage 的逐键契约 + roundtrip
// property + fuzz。向量出处：src/protocol.ts、src/server.ts:91-102、test/protocol.test.ts。

// MARK: - encode 形状（服务器视角逐键一致）

@Test("encode(attach) 无 sessionId 时也显式携带 sessionId:null（src/protocol.ts:132-134）")
func encodeAttachCarriesExplicitNullSessionId() {
    // Arrange / Act
    let frame = MessageCodec.encode(.attach(sessionId: nil, cwd: nil))

    // Assert — 逐字节 + 服务器视角双重断言
    #expect(frame == "{\"type\":\"attach\",\"sessionId\":null}")
    #expect(ServerViewParser.parse(frame) == .attach(sessionId: nil, cwd: nil))
}

@Test("encode(attach) 带 UUID → 服务器收到小写 UUID 字符串")
func encodeAttachSerializesLowercasedUUID() {
    // Arrange
    let uuid = UUID(uuidString: "F47AC10B-58CC-4372-A567-0E02B2C3D479")!

    // Act
    let frame = MessageCodec.encode(.attach(sessionId: uuid, cwd: nil))

    // Assert
    #expect(frame == "{\"type\":\"attach\",\"sessionId\":\"f47ac10b-58cc-4372-a567-0e02b2c3d479\"}")
    #expect(ServerViewParser.parse(frame)
        == .attach(sessionId: "f47ac10b-58cc-4372-a567-0e02b2c3d479", cwd: nil))
}

@Test("encode(attach) 带 cwd → cwd 键出现且为绝对路径原文；无 cwd 时键不出现")
func encodeAttachCwdKeyAppearsOnlyWhenPresent() throws {
    // Arrange / Act
    let withCwd = MessageCodec.encode(.attach(sessionId: nil, cwd: "/Users/dev/proj"))
    let withoutCwd = MessageCodec.encode(.attach(sessionId: nil, cwd: nil))

    // Assert
    #expect(ServerViewParser.parse(withCwd) == .attach(sessionId: nil, cwd: "/Users/dev/proj"))
    let withoutObj = try #require(jsonObject(withoutCwd))
    #expect(withoutObj.index(forKey: "cwd") == nil)
}

@Test("encode(input) 原始键盘字节逐字节透传（Esc/^C/CR/Tab/Shift+Tab）")
func encodeInputPassesRawKeyboardBytesVerbatim() {
    // Arrange — test/protocol.test.ts:79 的 verbatim 向量
    let raw = "\u{1B}[A\u{03}\r\t\u{1B}[Z"

    // Act
    let frame = MessageCodec.encode(.input(data: raw))

    // Assert
    #expect(ServerViewParser.parse(frame) == .input(data: raw))
}

@Test("encode(input) 控制字节按 JSON.stringify 规则转义（\\r 短转义、ESC → \\u001b）")
func encodeInputEscapesControlBytesLikeJSONStringify() {
    // Arrange / Act
    let frame = MessageCodec.encode(.input(data: "hi\r\u{1B}[A\"\\"))

    // Assert — 与 JS 客户端 JSON.stringify 输出逐字节一致
    #expect(frame == "{\"type\":\"input\",\"data\":\"hi\\r\\u001b[A\\\"\\\\\"}")
}

@Test("encode(resize) cols/rows 为 JSON 整数，服务器按 [1,1000] 接受")
func encodeResizeEmitsIntegers() {
    // Arrange / Act
    let frame = MessageCodec.encode(.resize(cols: 120, rows: 40))

    // Assert
    #expect(frame == "{\"type\":\"resize\",\"cols\":120,\"rows\":40}")
    #expect(ServerViewParser.parse(frame) == .resize(cols: 120, rows: 40))
    #expect(ServerViewParser.parse(MessageCodec.encode(.resize(cols: 1, rows: 1)))
        == .resize(cols: 1, rows: 1))
    #expect(ServerViewParser.parse(MessageCodec.encode(.resize(cols: 1000, rows: 1000)))
        == .resize(cols: 1000, rows: 1000))
}

@Test("encode(approve) 无 mode → 裸 {type:approve}；encode(reject) → {type:reject}")
func encodeApproveRejectBareFrames() {
    // Arrange / Act / Assert
    #expect(MessageCodec.encode(.approve(mode: nil)) == "{\"type\":\"approve\"}")
    #expect(MessageCodec.encode(.reject) == "{\"type\":\"reject\"}")
    #expect(ServerViewParser.parse("{\"type\":\"approve\"}") == .approve)
    #expect(ServerViewParser.parse("{\"type\":\"reject\"}") == .reject)
}

@Test("encode(approve.mode) 把 mode 放在顶层键（src/server.ts:94-102 读原始帧 obj['mode']）",
      arguments: ApproveMode.allCases)
func encodeApproveModeIsTopLevelKey(mode: ApproveMode) throws {
    // Arrange / Act
    let frame = MessageCodec.encode(.approve(mode: mode))

    // Assert — 服务器 parseClientMessage 接受该帧形状
    #expect(ServerViewParser.parse(frame) == .approve)
    // …且 WS 接线层的 parseApproveMode 能从顶层恢复 mode
    #expect(ServerViewParser.topLevelMode(frame) == mode.rawValue)
    let obj = try #require(jsonObject(frame))
    #expect(obj["mode"] as? String == mode.rawValue)
}

@Test("ApproveMode rawValue 与服务器 PERMISSION_MODES 白名单一致（src/server.ts:76）")
func approveModeRawValuesMatchServerWhitelist() {
    // Arrange / Act
    let rawValues = Set(ApproveMode.allCases.map(\.rawValue))

    // Assert
    #expect(rawValues == ServerViewParser.permissionModes)
}

// MARK: - roundtrip property（approve.mode 豁免：服务器刻意丢 mode，src/protocol.ts:77-79）

@Test("roundtrip property：任意合法 ClientMessage encode→(服务器视角)decode 不变形")
func roundtripPropertyHoldsForRandomClientMessages() {
    // Arrange — 固定种子保证可复现
    var rng = SplitMix64(seed: 0xC0FF_EE00_0003)
    let iterations = 300

    for _ in 0..<iterations {
        let original = MessageGen.randomClientMessage(using: &rng)

        // Act
        let frame = MessageCodec.encode(original)
        let parsed = ServerViewParser.parse(frame)

        // Assert
        assertServerViewMatches(original: original, frame: frame, parsed: parsed)
    }
}

private func assertServerViewMatches(
    original: ClientMessage, frame: String, parsed: ServerViewMessage?
) {
    switch (original, parsed) {
    case let (.attach(sessionId, cwd), .attach(parsedSessionId, parsedCwd)):
        #expect(parsedSessionId == sessionId.map { $0.uuidString.lowercased() })
        #expect(parsedCwd == cwd)
    case let (.input(data), .input(parsedData)):
        #expect(parsedData == data)
    case let (.resize(cols, rows), .resize(parsedCols, parsedRows)):
        #expect(parsedCols == cols)
        #expect(parsedRows == rows)
    case let (.approve(mode), .approve):
        // approve 豁免：只断言形状被接受 + 顶层 mode 可恢复
        #expect(ServerViewParser.topLevelMode(frame) == mode?.rawValue)
    case (.reject, .reject):
        break
    default:
        Issue.record("服务器视角解析结果与原消息 case 不符: \(original) → \(String(describing: parsed)), frame=\(frame)")
    }
}

// MARK: - fuzz：decodeServer 对随机字节永不 crash（安全注：服务器是不可信输入源）

@Test("fuzz：300 轮随机字节喂 decodeServer 不 crash（非法 → nil）")
func decodeServerSurvivesRandomByteFuzz() {
    // Arrange
    var rng = SplitMix64(seed: 0xDEAD_BEEF_0003)

    for _ in 0..<300 {
        let length = Int.random(in: 0...80, using: &rng)
        let bytes = (0..<length).map { _ in UInt8.random(in: 0...255, using: &rng) }
        let text = String(decoding: bytes, as: UTF8.self)

        // Act — 不得 throw / crash；返回值任意
        _ = MessageCodec.decodeServer(text)
    }
}

@Test("fuzz：合法帧随机单字节突变喂 decodeServer 不 crash")
func decodeServerSurvivesMutatedValidFrames() {
    // Arrange
    var rng = SplitMix64(seed: 0xFEED_FACE_0003)
    let seeds = [
        "{\"type\":\"attached\",\"sessionId\":\"f47ac10b-58cc-4372-a567-0e02b2c3d479\"}",
        "{\"type\":\"output\",\"data\":\"\\u001b[1;32mHello\\u001b[0m\"}",
        "{\"type\":\"exit\",\"code\":-1,\"reason\":\"spawn failed\"}",
        "{\"type\":\"status\",\"status\":\"waiting\",\"pending\":true,\"gate\":\"plan\"}",
        "{\"type\":\"telemetry\",\"telemetry\":{\"at\":1751600000000,\"costUsd\":1.5}}",
    ]

    for seed in seeds {
        for _ in 0..<40 {
            var bytes = Array(seed.utf8)
            let index = Int.random(in: 0..<bytes.count, using: &rng)
            bytes[index] = UInt8.random(in: 0...255, using: &rng)

            // Act — 不得 crash
            _ = MessageCodec.decodeServer(String(decoding: bytes, as: UTF8.self))
        }
    }
}

@Test("客户端帧类型（attach/input/resize/approve/reject）不是服务器帧 → decodeServer nil")
func decodeServerRejectsClientFrameTypes() {
    // Arrange
    let clientFrames: [ClientMessage] = [
        .attach(sessionId: nil, cwd: nil), .input(data: "x"),
        .resize(cols: 80, rows: 24), .approve(mode: .plan), .reject,
    ]

    for message in clientFrames {
        // Act / Assert
        #expect(MessageCodec.decodeServer(MessageCodec.encode(message)) == nil)
    }
}

// MARK: - helpers

private func jsonObject(_ text: String) -> [String: Any]? {
    guard let data = text.data(using: .utf8) else { return nil }
    return (try? JSONSerialization.jsonObject(with: data)) as? [String: Any]
}
