//
//  MCPTransportTests.swift
//  ConfabTests
//
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Copyright (C) 2026 Rune Olsen

import Testing
import Foundation
@testable import Confab

@Suite("MCPTransportSupport.extractResult")
struct MCPTransportSupportTests {

    @Test("Extracts the result payload from a successful JSON-RPC response")
    func extractsResult() throws {
        let json: [String: Any] = ["jsonrpc": "2.0", "id": 1, "result": ["tools": []]]
        let data = try MCPTransportSupport.extractResult(from: json)
        let decoded = try JSONSerialization.jsonObject(with: data) as? [String: Any]
        #expect(decoded?["tools"] != nil)
    }

    @Test("Throws with the server's message when the response is a JSON-RPC error")
    func throwsOnJSONRPCError() {
        let json: [String: Any] = ["jsonrpc": "2.0", "id": 1, "error": ["code": -32601, "message": "Method not found"]]
        do {
            _ = try MCPTransportSupport.extractResult(from: json)
            Issue.record("Expected extractResult to throw")
        } catch let error as MCPClientError {
            switch error {
            case .invalidResponse(let message): #expect(message == "Method not found")
            default: Issue.record("Expected .invalidResponse, got \(error)")
            }
        } catch {
            Issue.record("Expected MCPClientError, got \(error)")
        }
    }

    @Test("Throws when the response has neither result nor error")
    func throwsOnMissingResult() {
        let json: [String: Any] = ["jsonrpc": "2.0", "id": 1]
        do {
            _ = try MCPTransportSupport.extractResult(from: json)
            Issue.record("Expected extractResult to throw")
        } catch let error as MCPClientError {
            switch error {
            case .invalidResponse(let message): #expect(message == "Missing result field")
            default: Issue.record("Expected .invalidResponse, got \(error)")
            }
        } catch {
            Issue.record("Expected MCPClientError, got \(error)")
        }
    }
}

@Suite("HTTPMCPTransport.parseResponseBody")
struct HTTPMCPTransportParseTests {

    @Test("Parses a plain application/json response body directly")
    func parsesPlainJSON() throws {
        let body = Data(#"{"jsonrpc":"2.0","id":7,"result":{"ok":true}}"#.utf8)
        let json = try HTTPMCPTransport.parseResponseBody(body, contentType: "application/json", expectedId: 7)
        #expect(json["id"] as? Int == 7)
    }

    @Test("Parses a text/event-stream body, finding the data: line matching the expected id")
    func parsesSSEMatchingId() throws {
        let sse = """
        event: message
        data: {"jsonrpc":"2.0","id":7,"result":{"ok":true}}

        """
        let json = try HTTPMCPTransport.parseResponseBody(
            Data(sse.utf8), contentType: "text/event-stream", expectedId: 7
        )
        #expect(json["id"] as? Int == 7)
    }

    @Test("Skips unrelated server-sent messages before the matching response, per the Streamable HTTP spec")
    func skipsUnrelatedMessagesInSSE() throws {
        // Spec: "The server MAY send JSON-RPC requests and notifications before sending the
        // JSON-RPC response." Simulated here as an unrelated id=99 message before the real id=7 one.
        let sse = """
        data: {"jsonrpc":"2.0","id":99,"method":"unrelated/notification"}

        data: {"jsonrpc":"2.0","id":7,"result":{"ok":true}}

        """
        let json = try HTTPMCPTransport.parseResponseBody(
            Data(sse.utf8), contentType: "text/event-stream", expectedId: 7
        )
        #expect(json["id"] as? Int == 7)
    }

    @Test("Throws when no SSE data: line matches the expected id")
    func throwsWhenNoMatchInSSE() {
        let sse = """
        data: {"jsonrpc":"2.0","id":99,"result":{}}

        """
        do {
            _ = try HTTPMCPTransport.parseResponseBody(
                Data(sse.utf8), contentType: "text/event-stream", expectedId: 7
            )
            Issue.record("Expected parseResponseBody to throw")
        } catch let error as MCPClientError {
            switch error {
            case .invalidResponse: break
            default: Issue.record("Expected .invalidResponse, got \(error)")
            }
        } catch {
            Issue.record("Expected MCPClientError, got \(error)")
        }
    }

    @Test("Throws on malformed JSON in a plain application/json body")
    func throwsOnMalformedJSON() {
        let body = Data("not json".utf8)
        do {
            _ = try HTTPMCPTransport.parseResponseBody(body, contentType: "application/json", expectedId: 1)
            Issue.record("Expected parseResponseBody to throw")
        } catch let error as MCPClientError {
            switch error {
            case .invalidResponse: break
            default: Issue.record("Expected .invalidResponse, got \(error)")
            }
        } catch {
            Issue.record("Expected MCPClientError, got \(error)")
        }
    }
}
