//! Tool handlers — tools/list, tools/call

use serde_json::Value;

use crate::protocol::errors::{ErrorType, McpError, codes};
use crate::protocol::types::JsonRpcResponse;
use crate::protocol::types::RequestId;
use crate::registry::tools::{ToolError, ToolOutput};
use crate::server::core::Server;
use crate::server::handler::{
    RequestContext, error_response, require_initialization, success_response,
};
use crate::server::visibility::VisibilityContext;

fn tool_error_to_mcp_error(tool_name: &str, err: ToolError) -> McpError {
    match err {
        ToolError::NotFound(_) => McpError::not_found(
            codes::TOOL_NOT_FOUND,
            format!("Tool not found: {}", tool_name),
        ),
        ToolError::InvalidArguments(message) => {
            McpError::validation(codes::INVALID_TOOL_ARGS, message)
        }
        ToolError::Execution(message) | ToolError::Internal(message) => {
            McpError::internal(codes::TOOL_EXECUTION_FAILED, message)
        }
        ToolError::CircuitOpen { tool, message } => {
            McpError::builder(ErrorType::CircuitOpen, codes::CIRCUIT_BREAKER_OPEN)
                .message(message)
                .detail("tool", tool)
                .build()
        }
    }
}

impl Server {
    /// Handle tools/list request (with pagination support)
    pub(crate) async fn handle_tools_list(&self, ctx: RequestContext) -> JsonRpcResponse {
        if let Err(err) = require_initialization(&ctx) {
            return error_response(ctx.request.id, err.to_jsonrpc());
        }

        // Parse cursor if present
        let cursor = ctx
            .params()
            .and_then(|p| p.get("cursor"))
            .and_then(|c| c.as_str());

        let visibility_ctx = self.visibility_context(&ctx.session);
        let all_tools = self
            .tool_registry
            .list_for_session(&ctx.session, &visibility_ctx);

        // Apply pagination
        let result = crate::utils::paginate(&all_tools, cursor, crate::utils::DEFAULT_PAGE_SIZE);

        // Build response (only include nextCursor if present)
        let mut response = serde_json::json!({"tools": result.items});
        if let Some(next) = result.next_cursor {
            response["nextCursor"] = serde_json::Value::String(next);
        }
        success_response(ctx.request.id, response)
    }

    /// Handle tools/call request
    pub(crate) async fn handle_tools_call(&self, ctx: RequestContext) -> JsonRpcResponse {
        if let Err(err) = require_initialization(&ctx) {
            return error_response(ctx.request.id, err.to_jsonrpc());
        }

        // Parse tool call params
        let params = ctx.params().cloned().unwrap_or(Value::Null);
        let tool_name = match params.get("name").and_then(|v| v.as_str()) {
            Some(name) => name,
            None => {
                return error_response(
                    ctx.request.id,
                    McpError::validation("invalid_params", "Missing 'name' field").to_jsonrpc(),
                );
            }
        };

        let tool_params = params.get("arguments").cloned().unwrap_or(Value::Null);

        // Extract client-provided progress token from _meta.progressToken
        let progress_token: Option<String> = params
            .get("_meta")
            .and_then(|m| m.get("progressToken"))
            .and_then(|t| t.as_str())
            .map(|s| s.to_owned());

        // Check for task-augmented execution
        let task_meta: Option<crate::protocol::types::TaskMetadata> = params
            .get("task")
            .and_then(|t| serde_json::from_value(t.clone()).ok());

        if let Some(task_metadata) = task_meta {
            // Task-augmented execution
            return self
                .handle_task_augmented_tool_call(ctx, tool_name, tool_params, task_metadata)
                .await;
        }

        // Regular synchronous execution
        let client_requester = self.create_client_requester(&ctx.session.id);
        let visibility_ctx = self.visibility_context(&ctx.session);
        let json_rpc_request_id = ctx.request.id.as_ref().and_then(RequestId::from_value);

        match self
            .tool_registry
            .call_for_session(
                tool_name,
                tool_params,
                progress_token,
                &ctx.session,
                &self.logger,
                &visibility_ctx,
                client_requester,
                json_rpc_request_id,
            )
            .await
        {
            Ok(output) => {
                let result = match output {
                    ToolOutput::Content(items) => {
                        let content_values: Vec<Value> =
                            items.iter().map(|c| c.to_value()).collect();
                        serde_json::json!({"content": content_values})
                    }
                    ToolOutput::Structured(value) => {
                        serde_json::json!({"structuredContent": value})
                    }
                };
                success_response(ctx.request.id, result)
            }
            Err(e) => error_response(
                ctx.request.id,
                tool_error_to_mcp_error(tool_name, e).to_jsonrpc(),
            ),
        }
    }

    /// Handle task-augmented tool call
    pub(crate) async fn handle_task_augmented_tool_call(
        &self,
        ctx: RequestContext,
        tool_name: &str,
        tool_params: Value,
        task_metadata: crate::protocol::types::TaskMetadata,
    ) -> JsonRpcResponse {
        // Create task
        let (task, _result_rx) =
            self.task_store
                .create_task(&ctx.session.id, ctx.request.clone(), task_metadata.ttl);

        let task_id = task.task_id.clone();

        // Spawn background execution
        let task_store = self.task_store.clone();
        let tool_registry = self.tool_registry.clone();
        let logger = self.logger.clone();
        let session = ctx.session.clone();
        let environment = self.environment.clone();
        let client_requester = self.create_client_requester(&ctx.session.id);
        let tool_name = tool_name.to_string();

        tokio::spawn(async move {
            // Execute tool
            let visibility_ctx = match environment.as_deref() {
                Some(env) => VisibilityContext::with_environment(&session, env),
                None => VisibilityContext::new(&session),
            };

            match tool_registry
                .call_for_session(
                    &tool_name,
                    tool_params,
                    None, // task-augmented calls don't carry a separate progress token
                    &session,
                    &logger,
                    &visibility_ctx,
                    client_requester,
                    None, // task-augmented calls have no originating JSON-RPC request id
                )
                .await
            {
                Ok(output) => {
                    // Success - store result
                    let result = match output {
                        ToolOutput::Content(items) => {
                            let content_values: Vec<Value> =
                                items.iter().map(|c| c.to_value()).collect();
                            serde_json::json!({"content": content_values})
                        }
                        ToolOutput::Structured(value) => {
                            serde_json::json!({"structuredContent": value})
                        }
                    };

                    let _ = task_store.complete_task(&task_id, result).await;
                }
                Err(e) => {
                    // Failure - store error
                    let error_message = e.to_string();
                    let _ = task_store.fail_task(&task_id, error_message).await;
                }
            }
        });

        // Return immediately with task info
        success_response(
            ctx.request.id,
            serde_json::to_value(crate::protocol::types::CreateTaskResult { task }).unwrap(),
        )
    }
}
