use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;

use motosan_agent_tool::{Tool, ToolContext, ToolDef, ToolResult};

use super::McpServer;
use crate::Result;

/// Wraps a single MCP tool as an `Arc<dyn Tool>`.
///
/// Holds a reference to the [`McpServer`] to route `call()` back through MCP.
/// Tool names are namespaced as `{server_name}__{tool_name}` to avoid
/// collisions when multiple MCP servers are registered.
pub struct McpToolAdapter {
    server: Arc<dyn McpServer>,
    /// The original (un-namespaced) tool name used for `call_tool`.
    original_name: String,
    /// The tool definition with namespaced name.
    def: ToolDef,
}

impl McpToolAdapter {
    /// Convert all tools from a connected [`McpServer`] into `Arc<dyn Tool>`.
    pub async fn from_server(server: Arc<dyn McpServer>) -> Result<Vec<Arc<dyn Tool>>> {
        let tool_defs = server.list_tools().await?;
        let server_name = server.name();
        Ok(tool_defs
            .into_iter()
            .map(|def| {
                let namespaced = ToolDef {
                    name: format!("{server_name}__{}", def.name),
                    description: def.description.clone(),
                    input_schema: def.input_schema.clone(),
                };
                Arc::new(McpToolAdapter {
                    server: Arc::clone(&server),
                    original_name: def.name,
                    def: namespaced,
                }) as Arc<dyn Tool>
            })
            .collect())
    }
}

impl Tool for McpToolAdapter {
    fn def(&self) -> ToolDef {
        self.def.clone()
    }

    fn call(
        &self,
        args: serde_json::Value,
        _ctx: &ToolContext,
    ) -> Pin<Box<dyn Future<Output = ToolResult> + Send + '_>> {
        Box::pin(async move {
            match self.server.call_tool(&self.original_name, args).await {
                Ok(output) => ToolResult::text(output),
                Err(e) => ToolResult::error(e.to_string()),
            }
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use async_trait::async_trait;
    use serde_json::json;

    /// Fake MCP server for testing.
    struct FakeServer {
        name: String,
        tools: Vec<ToolDef>,
    }

    impl FakeServer {
        fn new(name: &str, tools: Vec<ToolDef>) -> Arc<Self> {
            Arc::new(Self {
                name: name.to_string(),
                tools,
            })
        }
    }

    #[async_trait]
    impl McpServer for FakeServer {
        fn name(&self) -> &str {
            &self.name
        }

        async fn connect(&self) -> Result<()> {
            Ok(())
        }

        async fn list_tools(&self) -> Result<Vec<ToolDef>> {
            Ok(self.tools.clone())
        }

        async fn call_tool(&self, tool_name: &str, args: serde_json::Value) -> Result<String> {
            Ok(format!("called {tool_name} with {args}"))
        }

        async fn disconnect(&self) -> Result<()> {
            Ok(())
        }
    }

    fn echo_tool_def() -> ToolDef {
        ToolDef {
            name: "echo".to_string(),
            description: "Echo the input".to_string(),
            input_schema: json!({
                "type": "object",
                "properties": {
                    "message": { "type": "string" }
                },
                "required": ["message"]
            }),
        }
    }

    #[tokio::test]
    async fn from_server_converts_all_tools() {
        let server = FakeServer::new("my_server", vec![echo_tool_def()]);
        let tools = McpToolAdapter::from_server(server).await.unwrap();
        assert_eq!(tools.len(), 1);
        assert_eq!(tools[0].def().name, "my_server__echo");
    }

    #[tokio::test]
    async fn tool_name_is_namespaced() {
        let server = FakeServer::new("github", vec![echo_tool_def()]);
        let tools = McpToolAdapter::from_server(server).await.unwrap();
        let def = tools[0].def();
        assert_eq!(def.name, "github__echo");
        assert_eq!(def.description, "Echo the input");
    }

    #[tokio::test]
    async fn call_routes_to_mcp_server() {
        let server = FakeServer::new("s", vec![echo_tool_def()]);
        let tools = McpToolAdapter::from_server(server).await.unwrap();
        let ctx = ToolContext::default();
        let args = json!({"message": "hello"});
        let result = tools[0].call(args, &ctx).await;
        assert!(!result.is_error);
        let text = result.as_text().unwrap();
        assert!(text.contains("called echo"));
        assert!(text.contains("hello"));
    }

    #[tokio::test]
    async fn call_returns_error_on_server_failure() {
        struct FailServer;
        #[async_trait]
        impl McpServer for FailServer {
            fn name(&self) -> &str {
                "fail"
            }
            async fn connect(&self) -> Result<()> {
                Ok(())
            }
            async fn list_tools(&self) -> Result<Vec<ToolDef>> {
                Ok(vec![echo_tool_def()])
            }
            async fn call_tool(
                &self,
                _tool_name: &str,
                _args: serde_json::Value,
            ) -> Result<String> {
                Err(crate::AgentError::Tool("server crashed".into()))
            }
            async fn disconnect(&self) -> Result<()> {
                Ok(())
            }
        }

        let server: Arc<dyn McpServer> = Arc::new(FailServer);
        let tools = McpToolAdapter::from_server(server).await.unwrap();
        let ctx = ToolContext::default();
        let result = tools[0].call(json!({}), &ctx).await;
        assert!(result.is_error);
        assert!(result.as_text().unwrap().contains("server crashed"));
    }

    #[tokio::test]
    async fn multiple_tools_from_single_server() {
        let add_def = ToolDef {
            name: "add".to_string(),
            description: "Add numbers".to_string(),
            input_schema: json!({
                "type": "object",
                "properties": {
                    "a": { "type": "number" },
                    "b": { "type": "number" }
                },
                "required": ["a", "b"]
            }),
        };
        let server = FakeServer::new("math", vec![echo_tool_def(), add_def]);
        let tools = McpToolAdapter::from_server(server).await.unwrap();
        assert_eq!(tools.len(), 2);
        assert_eq!(tools[0].def().name, "math__echo");
        assert_eq!(tools[1].def().name, "math__add");
    }
}
