//! MCP Client Implementation - Low-level JSON-RPC Bridge
//!
//! @docs ARCHITECTURE:Registry:Mcp
//!
//! ### AI Assist Note
//! **Module: client.rs**
//! Provides the raw stdio-based JSON-RPC bridge for communicating with
//! Model Context Protocol (MCP) servers.
//!
//! ### 🔍 Debugging & Observability
//! - **Failure Path**: Zombie processes, broken pipes, or JSON-RPC protocol mismatches.
//! - **Telemetry Link**: Search `[client]` in tracing logs.
//! - **Trace Scope**: `server-rs::agent::mcp::client`
//!

use crate::error::{AppError, InfrastructureErrorKind, ProviderId};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::process::Stdio;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::process::{Child, ChildStdin, ChildStdout, Command};
use tracing::{debug, info};

#[derive(Debug, Serialize)]
pub struct JsonRpcRequest {
    pub jsonrpc: String,
    pub id: Value,
    pub method: String,
    pub params: Value,
}

#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct JsonRpcResponse {
    pub jsonrpc: String,
    pub id: Option<Value>,
    pub result: Option<Value>,
    pub error: Option<Value>,
}

#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct JsonRpcNotification {
    pub jsonrpc: String,
    pub method: String,
    pub params: Option<Value>,
}

pub struct McpClient {
    #[allow(dead_code)]
    child: Child,
    stdin: ChildStdin,
    stdout: BufReader<ChildStdout>,
    next_id: u64,
}

impl McpClient {
    pub async fn spawn(command_line: &str) -> Result<Self, AppError> {
        info!("🚀 [client] [MCP] Spawning server: {}", command_line);

        let mut parts = command_line.split_whitespace();
        let program = parts
            .next()
            .ok_or_else(|| AppError::BadRequest("Empty command".to_string()))?;
        let args: Vec<&str> = parts.collect();

        let mut child = Command::new(program)
            .args(args)
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::inherit()) // Log stderr to the console
            .spawn()
            .map_err(AppError::Io)?;

        let stdin = child
            .stdin
            .take()
            .ok_or_else(|| AppError::InternalServerError("Failed to open stdin".to_string()))?;
        let stdout = child
            .stdout
            .take()
            .ok_or_else(|| AppError::InternalServerError("Failed to open stdout".to_string()))?;

        Ok(Self {
            child,
            stdin,
            stdout: BufReader::new(stdout),
            next_id: 1,
        })
    }

    pub async fn call(&mut self, method: &str, params: Value) -> Result<Value, AppError> {
        let id = self.next_id;
        self.next_id += 1;

        let request = JsonRpcRequest {
            jsonrpc: "2.0".to_string(),
            id: json!(id),
            method: method.to_string(),
            params,
        };

        let request_str = serde_json::to_string(&request)
            .map_err(|e| AppError::InternalServerError(e.to_string()))?
            + "\n";
        debug!(">> [MCP] Sending: {}", request_str.trim());
        self.stdin
            .write_all(request_str.as_bytes())
            .await
            .map_err(AppError::Io)?;
        self.stdin.flush().await.map_err(AppError::Io)?;

        let target_id = json!(id);
        loop {
            let mut response_line = String::new();
            self.stdout
                .read_line(&mut response_line)
                .await
                .map_err(AppError::Io)?;
            debug!("<< [MCP] Received: {}", response_line.trim());

            if response_line.is_empty() {
                return Err(AppError::InfrastructureError {
                    provider_id: ProviderId::Mcp,
                    kind: InfrastructureErrorKind::NetworkError,
                    detail: "MCP server closed connection".to_string(),
                    help_link: None,
                });
            }

            if let Ok(raw_val) = serde_json::from_str::<Value>(&response_line) {
                // If it is a notification (contains method and does not contain id), we skip it.
                if raw_val.get("method").is_some() && raw_val.get("id").is_none() {
                    debug!("<< [MCP] Skipping notification: {}", response_line.trim());
                    continue;
                }

                if let Some(resp_id) = raw_val.get("id") {
                    if resp_id == &target_id {
                        let response: JsonRpcResponse = serde_json::from_value(raw_val)
                            .map_err(|e| AppError::InternalServerError(e.to_string()))?;

                        if let Some(error) = response.error {
                            return Err(AppError::InfrastructureError {
                                provider_id: ProviderId::Mcp,
                                kind: InfrastructureErrorKind::ApiError,
                                detail: format!("MCP Error: {}", error),
                                help_link: None,
                            });
                        }

                        return response
                            .result
                            .ok_or_else(|| AppError::InfrastructureError {
                                provider_id: ProviderId::Mcp,
                                kind: InfrastructureErrorKind::ApiError,
                                detail: "Missing result in MCP response".to_string(),
                                help_link: None,
                            });
                    } else {
                        debug!("<< [MCP] Mismatched JSON-RPC response ID (expected {:?}, got {:?}): {}", target_id, resp_id, response_line.trim());
                    }
                }
            } else {
                debug!(
                    "<< [MCP] Malformed JSON-RPC message: {}",
                    response_line.trim()
                );
            }
        }
    }

    pub async fn initialize(&mut self) -> Result<(), AppError> {
        let params = json!({
            "protocolVersion": "2024-11-05",
            "capabilities": {},
            "clientInfo": {
                "name": "tadpole-os-engine",
                "version": "1.1.0"
            }
        });

        self.call("initialize", params).await?;

        // Notify initialized
        let notification = json!({
            "jsonrpc": "2.0",
            "method": "notifications/initialized"
        });
        let notif_str = serde_json::to_string(&notification)
            .map_err(|e| AppError::InternalServerError(e.to_string()))?
            + "\n";
        self.stdin
            .write_all(notif_str.as_bytes())
            .await
            .map_err(AppError::Io)?;
        self.stdin.flush().await.map_err(AppError::Io)?;

        Ok(())
    }

    #[allow(dead_code)]
    pub async fn list_tools(&mut self) -> Result<Vec<Value>, AppError> {
        let result = self.call("tools/list", json!({})).await?;
        let tools = result
            .get("tools")
            .and_then(|t| t.as_array())
            .cloned()
            .unwrap_or_default();
        Ok(tools)
    }

    pub async fn call_tool(&mut self, name: &str, arguments: Value) -> Result<Value, AppError> {
        let params = json!({
            "name": name,
            "arguments": arguments
        });
        self.call("tools/call", params).await
    }
}
