//! JSON-RPC 2.0 message types for the MCP protocol.

use serde::{Deserialize, Serialize};
use serde_json::Value;

/// A JSON-RPC 2.0 request (or notification, when `id` is `None`).
#[derive(Debug, Deserialize)]
pub struct JsonRpcRequest {
    /// Must be `"2.0"`.
    pub jsonrpc: String,
    /// Request identifier. `None` for notifications (no response expected).
    pub id: Option<Value>,
    /// The method to invoke (e.g. `"initialize"`, `"tools/list"`, `"tools/call"`).
    pub method: String,
    /// Method parameters. Defaults to `null` when absent.
    #[serde(default)]
    pub params: Value,
}

/// A JSON-RPC 2.0 response.
#[derive(Debug, Serialize)]
pub struct JsonRpcResponse {
    /// Always `"2.0"`.
    pub jsonrpc: String,
    /// Mirrors the `id` from the request.
    pub id: Value,
    /// The result on success.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub result: Option<Value>,
    /// The error on failure.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<JsonRpcError>,
}

/// A JSON-RPC 2.0 error object.
#[derive(Debug, Serialize)]
pub struct JsonRpcError {
    /// Numeric error code (see JSON-RPC 2.0 spec).
    pub code: i64,
    /// Human-readable error message.
    pub message: String,
    /// Optional structured error data.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<Value>,
}

// ── Standard JSON-RPC 2.0 error codes ───────────────────────────────

/// The JSON sent is not valid JSON (parse error).
pub const PARSE_ERROR: i64 = -32700;

/// The JSON sent is not a valid JSON-RPC request.
pub const INVALID_REQUEST: i64 = -32600;

/// The method does not exist or is not available.
pub const METHOD_NOT_FOUND: i64 = -32601;

/// Invalid method parameters.
pub const INVALID_PARAMS: i64 = -32602;

/// Internal server error.
pub const INTERNAL_ERROR: i64 = -32603;

// ── Helpers ─────────────────────────────────────────────────────────

/// Build a successful response.
pub fn success_response(id: Value, result: Value) -> JsonRpcResponse {
    JsonRpcResponse {
        jsonrpc: "2.0".into(),
        id,
        result: Some(result),
        error: None,
    }
}

/// Build an error response.
pub fn error_response(id: Value, code: i64, message: &str) -> JsonRpcResponse {
    JsonRpcResponse {
        jsonrpc: "2.0".into(),
        id,
        result: None,
        error: Some(JsonRpcError {
            code,
            message: message.to_owned(),
            data: None,
        }),
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn deserialize_request_with_id() {
        let raw = r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}"#;
        let req: JsonRpcRequest = serde_json::from_str(raw).unwrap();
        assert_eq!(req.jsonrpc, "2.0");
        assert_eq!(req.method, "initialize");
        assert_eq!(req.id, Some(json!(1)));
    }

    #[test]
    fn deserialize_notification_no_id() {
        let raw = r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#;
        let req: JsonRpcRequest = serde_json::from_str(raw).unwrap();
        assert!(req.id.is_none());
        assert_eq!(req.params, Value::Null);
    }

    #[test]
    fn serialize_success_response() {
        let resp = success_response(json!(1), json!({"ok": true}));
        let s = serde_json::to_string(&resp).unwrap();
        assert!(s.contains(r#""result":"#));
        assert!(!s.contains(r#""error""#));
    }

    #[test]
    fn serialize_error_response() {
        let resp = error_response(json!(2), METHOD_NOT_FOUND, "Method not found");
        let s = serde_json::to_string(&resp).unwrap();
        assert!(s.contains(r#""error""#));
        assert!(!s.contains(r#""result""#));
    }

    #[test]
    fn deserialize_request_string_id() {
        let raw = r#"{"jsonrpc":"2.0","id":"abc","method":"tools/list"}"#;
        let req: JsonRpcRequest = serde_json::from_str(raw).unwrap();
        assert_eq!(req.id, Some(json!("abc")));
    }
}
