use std::collections::HashMap;
use std::sync::Arc;

use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use tracing::{debug, info, warn};

// ---------------------------------------------------------------------------
// Tool definition
// ---------------------------------------------------------------------------

/// Schema-level description of a single MCP tool that is exposed to external
/// LLM clients.  The `input_schema` follows JSON-Schema conventions so that
/// callers can validate arguments before sending them.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolDefinition {
    pub name: String,
    pub description: String,
    pub input_schema: Value,
}

// ---------------------------------------------------------------------------
// Tool handler trait
// ---------------------------------------------------------------------------

/// Async handler that backs a single MCP tool.
/// Implementations receive the caller-supplied arguments as a generic
/// `serde_json::Value` and return a JSON response (or an error).
#[async_trait::async_trait]
pub trait ToolHandler: Send + Sync {
    async fn handle(&self, input: Value) -> Result<Value>;
}

// ---------------------------------------------------------------------------
// Convenience: use a closure / fn pointer as a handler
// ---------------------------------------------------------------------------

/// A `ToolHandler` backed by an async function.
pub struct FnHandler<F>(pub F);

#[async_trait::async_trait]
impl<F, Fut> ToolHandler for FnHandler<F>
where
    F: Fn(Value) -> Fut + Send + Sync + 'static,
    Fut: std::future::Future<Output = Result<Value>> + Send + 'static,
{
    async fn handle(&self, input: Value) -> Result<Value> {
        (self.0)(input).await
    }
}

// ---------------------------------------------------------------------------
// Placeholder handler (returns a "not implemented" message)
// ---------------------------------------------------------------------------

/// Placeholder handler used during `register_default_tools` so that every
/// built-in tool has *something* wired up even before concrete backends are
/// available.
struct PlaceholderHandler {
    tool_name: String,
}

#[async_trait::async_trait]
impl ToolHandler for PlaceholderHandler {
    async fn handle(&self, _input: Value) -> Result<Value> {
        Ok(json!({
            "error": format!("Tool '{}' is registered but has no backend implementation yet.", self.tool_name)
        }))
    }
}

// ---------------------------------------------------------------------------
// MCP Server
// ---------------------------------------------------------------------------

/// MCP Server that manages tool registration and request dispatching.
///
/// Tools are registered as `(ToolDefinition, Arc<dyn ToolHandler>)` pairs.
/// External transports (stdio, SSE, ...) can later call `handle_tool_call`
/// to route incoming requests to the correct handler.
pub struct McpServer {
    tools: HashMap<String, ToolDefinition>,
    handlers: HashMap<String, Arc<dyn ToolHandler>>,
}

impl McpServer {
    // -- construction -------------------------------------------------------

    /// Create an empty MCP server with no tools registered.
    pub fn new() -> Self {
        info!("Creating new MCP server");
        Self {
            tools: HashMap::new(),
            handlers: HashMap::new(),
        }
    }

    // -- registration -------------------------------------------------------

    /// Register a single tool with its definition and handler.
    ///
    /// If a tool with the same name already exists it will be silently
    /// replaced.
    pub fn register_tool(&mut self, def: ToolDefinition, handler: Arc<dyn ToolHandler>) {
        debug!("Registering MCP tool: {}", def.name);
        self.handlers.insert(def.name.clone(), handler);
        self.tools.insert(def.name.clone(), def);
    }

    /// Register all built-in FURNACE tools with placeholder handlers.
    ///
    /// This is useful during startup so that the tool catalog is immediately
    /// available for discovery; concrete handlers can be swapped in later via
    /// `register_tool`.
    pub fn register_default_tools(&mut self) {
        info!("Registering default FURNACE tools");

        // 1. search_code
        self.register_tool(
            ToolDefinition {
                name: "search_code".into(),
                description: "Search the codebase using FURNACE's tiered search engine. \
                              Supports semantic, exact-match, and agentic search strategies."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "query": {
                            "type": "string",
                            "description": "The search query (natural language or code pattern)"
                        },
                        "max_results": {
                            "type": "integer",
                            "description": "Maximum number of results to return",
                            "default": 10
                        },
                        "file_types": {
                            "type": "array",
                            "items": { "type": "string" },
                            "description": "File extensions to include (e.g. [\"rs\", \"toml\"])"
                        },
                        "exclude_paths": {
                            "type": "array",
                            "items": { "type": "string" },
                            "description": "Path prefixes to exclude from results"
                        }
                    },
                    "required": ["query"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "search_code".into(),
            }),
        );

        // 2. create_scroll
        self.register_tool(
            ToolDefinition {
                name: "create_scroll".into(),
                description: "IMPORTANT: Always create a scroll before making code changes \
                              through FURNACE. This is the entry point for all code generation \
                              workflows. Creates a new SCROLL from a natural-language intent \
                              and extracts constraints from the workspace knowledge graph. \
                              After creating, call `add_tasks` to decompose the intent into \
                              file-level tasks with generated code, then call `execute_scroll` \
                              to apply changes."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "intent": {
                            "type": "string",
                            "description": "Natural-language description of the desired change"
                        },
                        "recipe": {
                            "type": "string",
                            "description": "Optional recipe name to guide code generation (e.g. 'refactor', 'test_gen')"
                        }
                    },
                    "required": ["intent"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "create_scroll".into(),
            }),
        );

        // 2b. add_tasks
        self.register_tool(
            ToolDefinition {
                name: "add_tasks".into(),
                description: "Call this immediately after create_scroll. Add tasks to an \
                              existing SCROLL. Each task targets a specific file with a \
                              Create, Modify, Delete, or Test operation. Include \
                              `generated_code` with the full file content for each task — \
                              FURNACE will execute them in the correct dependency order. \
                              Tasks can declare dependencies on other tasks for ordering."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "scroll_id": {
                            "type": "string",
                            "description": "The SCROLL ID to add tasks to"
                        },
                        "tasks": {
                            "type": "array",
                            "description": "Array of tasks to add to the scroll",
                            "items": {
                                "type": "object",
                                "properties": {
                                    "description": {
                                        "type": "string",
                                        "description": "What this task does"
                                    },
                                    "target_file": {
                                        "type": "string",
                                        "description": "File path this task operates on"
                                    },
                                    "task_type": {
                                        "type": "string",
                                        "description": "Operation type",
                                        "enum": ["Create", "Modify", "Delete", "Test"]
                                    },
                                    "generated_code": {
                                        "type": "string",
                                        "description": "Pre-generated code content for this task (full file content)"
                                    },
                                    "dependencies": {
                                        "type": "array",
                                        "items": { "type": "string" },
                                        "description": "Task IDs that must complete before this one"
                                    },
                                    "group": {
                                        "type": "string",
                                        "description": "Optional group label to link related tasks (e.g. a module + its tests). Grouped tasks are generated with shared context for consistency."
                                    }
                                },
                                "required": ["description", "target_file", "task_type"]
                            }
                        }
                    },
                    "required": ["scroll_id", "tasks"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "add_tasks".into(),
            }),
        );

        // 3. execute_scroll
        self.register_tool(
            ToolDefinition {
                name: "execute_scroll".into(),
                description: "This is the final step — call after create_scroll and add_tasks. \
                              FURNACE handles all task ordering via dependency DAG — do not \
                              execute tasks manually. Orchestrates task execution respecting \
                              dependency order, then runs AutoForge: applies code to filesystem, \
                              runs tests, and verifies code quality. Returns execution report \
                              with DAG execution plan, test results, and quality analysis."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "scroll_id": {
                            "type": "string",
                            "description": "The unique identifier of the SCROLL to execute"
                        },
                        "completions": {
                            "type": "array",
                            "description": "LLM completions from a previous prefetch round. Each item must have 'id' (matching a prompt id) and 'response' (the LLM answer).",
                            "items": {
                                "type": "object",
                                "properties": {
                                    "id": { "type": "string" },
                                    "response": { "type": "string" }
                                },
                                "required": ["id", "response"]
                            }
                        },
                        "prefetch_round": {
                            "type": "integer",
                            "description": "Which prefetch round these completions are for (1=augmentation, 2=code generation, 3=test+review)"
                        }
                    },
                    "required": ["scroll_id"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "execute_scroll".into(),
            }),
        );

        // 3b. update_scroll
        self.register_tool(
            ToolDefinition {
                name: "update_scroll".into(),
                description: "Update an existing SCROLL's metadata. Can change the title, intent, \
                              execution mode, or AutoForge configuration. Changes are synced to \
                              linked external systems (Jira, Linear, Notion)."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "scroll_id": {
                            "type": "string",
                            "description": "The SCROLL ID to update"
                        },
                        "title": {
                            "type": "string",
                            "description": "New title for the scroll"
                        },
                        "intent": {
                            "type": "string",
                            "description": "Updated intent description"
                        },
                        "execution_mode": {
                            "type": "string",
                            "description": "Execution mode",
                            "enum": ["Forge", "AutoForge"]
                        },
                        "auto_forge_config": {
                            "type": "object",
                            "description": "AutoForge configuration overrides",
                            "properties": {
                                "auto_apply": { "type": "boolean" },
                                "auto_test": { "type": "boolean" },
                                "auto_verify": { "type": "boolean" },
                                "max_iterations": { "type": "integer" },
                                "test_command": { "type": "string" }
                            }
                        }
                    },
                    "required": ["scroll_id"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "update_scroll".into(),
            }),
        );

        // 3c. update_task
        self.register_tool(
            ToolDefinition {
                name: "update_task".into(),
                description: "Update an existing task within a SCROLL. Can change description, \
                              target file, task type, generated code, or dependencies. Changes \
                              are synced to linked external systems."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "scroll_id": {
                            "type": "string",
                            "description": "The SCROLL this task belongs to"
                        },
                        "task_id": {
                            "type": "string",
                            "description": "The task ID to update"
                        },
                        "description": {
                            "type": "string",
                            "description": "New task description"
                        },
                        "target_file": {
                            "type": "string",
                            "description": "New target file path"
                        },
                        "task_type": {
                            "type": "string",
                            "description": "New operation type",
                            "enum": ["Create", "Modify", "Delete", "Test"]
                        },
                        "generated_code": {
                            "type": "string",
                            "description": "Updated pre-generated code content"
                        },
                        "dependencies": {
                            "type": "array",
                            "items": { "type": "string" },
                            "description": "Updated dependency task IDs"
                        }
                    },
                    "required": ["scroll_id", "task_id"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "update_task".into(),
            }),
        );

        // 3d. remove_tasks
        self.register_tool(
            ToolDefinition {
                name: "remove_tasks".into(),
                description: "Remove tasks from a SCROLL by their IDs. Use this when the user \
                              wants to delete tasks during scroll review. Changes are synced to \
                              linked external systems."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "scroll_id": {
                            "type": "string",
                            "description": "The SCROLL these tasks belong to"
                        },
                        "task_ids": {
                            "type": "array",
                            "items": { "type": "string" },
                            "description": "Task IDs to remove"
                        }
                    },
                    "required": ["scroll_id", "task_ids"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "remove_tasks".into(),
            }),
        );

        // 4. validate_scroll
        self.register_tool(
            ToolDefinition {
                name: "validate_scroll".into(),
                description: "Validate a SCROLL by its ID. Checks constraints, dependencies, \
                              types, and potential breaking changes. Returns a validation report."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "scroll_id": {
                            "type": "string",
                            "description": "The unique identifier of the SCROLL to validate"
                        }
                    },
                    "required": ["scroll_id"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "validate_scroll".into(),
            }),
        );

        // 5. list_scrolls
        self.register_tool(
            ToolDefinition {
                name: "list_scrolls".into(),
                description: "List all SCROLLs, optionally filtered by status (Draft, \
                              Validated, InProgress, Completed, Failed, Archived)."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "status_filter": {
                            "type": "string",
                            "description": "Optional status to filter by",
                            "enum": ["Draft", "Validated", "InProgress", "Completed", "Failed", "Archived"]
                        }
                    }
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "list_scrolls".into(),
            }),
        );

        // 6. get_scroll
        self.register_tool(
            ToolDefinition {
                name: "get_scroll".into(),
                description: "Retrieve a specific SCROLL by its unique ID, including all \
                              tasks, constraints, and metadata."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "scroll_id": {
                            "type": "string",
                            "description": "The unique identifier of the SCROLL to retrieve"
                        }
                    },
                    "required": ["scroll_id"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "get_scroll".into(),
            }),
        );

        // 7. analyze_file
        self.register_tool(
            ToolDefinition {
                name: "analyze_file".into(),
                description: "Analyze a single file for code quality issues, complexity, \
                              and potential problems."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "file_path": {
                            "type": "string",
                            "description": "Absolute or workspace-relative path to the file"
                        }
                    },
                    "required": ["file_path"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "analyze_file".into(),
            }),
        );

        // 8. search_feedback
        self.register_tool(
            ToolDefinition {
                name: "search_feedback".into(),
                description: "Provide feedback on a search result to improve future search quality. \
                              Marks a previous search query as satisfactory or unsatisfactory."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "query_id": {
                            "type": "string",
                            "description": "The ID of the search query to provide feedback on"
                        },
                        "satisfied": {
                            "type": "boolean",
                            "description": "Whether the search results were satisfactory"
                        }
                    },
                    "required": ["query_id", "satisfied"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "search_feedback".into(),
            }),
        );

        // 9. list_recipes
        self.register_tool(
            ToolDefinition {
                name: "list_recipes".into(),
                description: "List all available FURNACE recipes (portable prompt templates)."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {}
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "list_recipes".into(),
            }),
        );

        // 10. configure_sync
        self.register_tool(
            ToolDefinition {
                name: "configure_sync".into(),
                description: "Configure an external system (Linear, Jira, Notion) for \
                              bidirectional sync. Stores the API token in the OS keyring and \
                              registers the MCP server connection."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "system": {
                            "type": "string",
                            "description": "External system name",
                            "enum": ["Linear", "Jira", "Notion"]
                        },
                        "api_token": {
                            "type": "string",
                            "description": "API token for the external system"
                        },
                        "command": {
                            "type": "string",
                            "description": "Command to spawn the MCP server (e.g. 'npx')"
                        },
                        "args": {
                            "type": "array",
                            "items": { "type": "string" },
                            "description": "Arguments for the MCP server command (e.g. ['-y', '@anthropic/linear-mcp-server'])"
                        }
                    },
                    "required": ["system", "api_token", "command", "args"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "configure_sync".into(),
            }),
        );

        // 11. link_issue
        self.register_tool(
            ToolDefinition {
                name: "link_issue".into(),
                description: "Link a FURNACE scroll to an external issue/page. Creates a \
                              bidirectional reference between the scroll and the external entity."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "scroll_id": {
                            "type": "string",
                            "description": "The FURNACE scroll ID to link"
                        },
                        "system": {
                            "type": "string",
                            "description": "External system name",
                            "enum": ["Linear", "Jira", "Notion"]
                        },
                        "external_id": {
                            "type": "string",
                            "description": "The external entity ID (e.g. 'LIN-123', 'PROJ-456')"
                        },
                        "url": {
                            "type": "string",
                            "description": "URL to the external entity"
                        }
                    },
                    "required": ["scroll_id", "system", "external_id", "url"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "link_issue".into(),
            }),
        );

        // 12. push_scroll
        self.register_tool(
            ToolDefinition {
                name: "push_scroll".into(),
                description: "Push a scroll's current status to an external system via its MCP \
                              server. Creates or updates the linked external entity."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "scroll_id": {
                            "type": "string",
                            "description": "The FURNACE scroll ID to push"
                        },
                        "system": {
                            "type": "string",
                            "description": "External system to push to",
                            "enum": ["Linear", "Jira", "Notion"]
                        }
                    },
                    "required": ["scroll_id", "system"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "push_scroll".into(),
            }),
        );

        // 13. sync_status
        self.register_tool(
            ToolDefinition {
                name: "sync_status".into(),
                description: "Compute the mapped status between a FURNACE scroll and an external \
                              system. Shows what the external status would be without pushing."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "scroll_id": {
                            "type": "string",
                            "description": "The FURNACE scroll ID to check"
                        },
                        "system": {
                            "type": "string",
                            "description": "External system to map status for",
                            "enum": ["Linear", "Jira", "Notion"]
                        }
                    },
                    "required": ["scroll_id", "system"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "sync_status".into(),
            }),
        );

        // 14. list_connections
        self.register_tool(
            ToolDefinition {
                name: "list_connections".into(),
                description: "List all external system connections (linked scrolls). \
                              Optionally filter by a specific scroll ID."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "scroll_id": {
                            "type": "string",
                            "description": "Optional scroll ID to filter connections for"
                        }
                    }
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "list_connections".into(),
            }),
        );

        // 15. get_metrics
        self.register_tool(
            ToolDefinition {
                name: "get_metrics".into(),
                description: "Get aggregate usage metrics and telemetry statistics. \
                              Returns counts of scrolls created/executed, searches performed, \
                              auto-forge runs, and average search latency."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "since_timestamp": {
                            "type": "integer",
                            "description": "Optional Unix epoch timestamp to filter events from (inclusive)"
                        }
                    }
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "get_metrics".into(),
            }),
        );

        // 16. configure_kb
        self.register_tool(
            ToolDefinition {
                name: "configure_kb".into(),
                description: "Configure a knowledge base connection (ChromaDB, Pinecone, etc.). \
                              Registers an MCP server that FURNACE can query for external knowledge."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "source": {
                            "type": "string",
                            "description": "KB source type (e.g. 'ChromaDB', 'Pinecone', 'Weaviate')"
                        },
                        "command": {
                            "type": "string",
                            "description": "Command to spawn the KB MCP server (e.g. 'uvx')"
                        },
                        "args": {
                            "type": "array",
                            "items": { "type": "string" },
                            "description": "Arguments for the MCP server command"
                        },
                        "collection": {
                            "type": "string",
                            "description": "Default collection to query"
                        },
                        "collection_pattern": {
                            "type": "string",
                            "description": "Glob pattern to match multiple collections (e.g. 'requirements-*')"
                        },
                        "env_vars": {
                            "type": "object",
                            "description": "Environment variables to set for the MCP server"
                        }
                    },
                    "required": ["source", "command", "args"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "configure_kb".into(),
            }),
        );

        // 17. query_kb
        self.register_tool(
            ToolDefinition {
                name: "query_kb".into(),
                description: "Query a knowledge base for relevant documents. Returns matching \
                              entries without modifying the knowledge graph."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "source": {
                            "type": "string",
                            "description": "Name of the registered KB source to query"
                        },
                        "query": {
                            "type": "string",
                            "description": "Search query"
                        },
                        "max_results": {
                            "type": "integer",
                            "description": "Maximum results to return",
                            "default": 5
                        },
                        "collection": {
                            "type": "string",
                            "description": "Specific collection to query"
                        },
                        "collection_pattern": {
                            "type": "string",
                            "description": "Glob pattern to match collections (e.g. 'requirements-*')"
                        }
                    },
                    "required": ["source", "query"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "query_kb".into(),
            }),
        );

        // 18. sync_kb
        self.register_tool(
            ToolDefinition {
                name: "sync_kb".into(),
                description: "Query a knowledge base and assimilate results into the knowledge graph. \
                              Creates Knowledge and Concept nodes with edges to related File nodes."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "source": {
                            "type": "string",
                            "description": "Name of the registered KB source to sync"
                        },
                        "query": {
                            "type": "string",
                            "description": "Search query for fetching documents to assimilate"
                        },
                        "max_results": {
                            "type": "integer",
                            "description": "Maximum results to fetch",
                            "default": 10
                        },
                        "collection": {
                            "type": "string",
                            "description": "Specific collection to sync"
                        },
                        "collection_pattern": {
                            "type": "string",
                            "description": "Glob pattern to match collections"
                        }
                    },
                    "required": ["source", "query"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "sync_kb".into(),
            }),
        );

        // 19. ingest_kb
        self.register_tool(
            ToolDefinition {
                name: "ingest_kb".into(),
                description: "Push a document to a knowledge base collection. Useful for storing \
                              design decisions, requirements, or analysis results externally."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "source": {
                            "type": "string",
                            "description": "Name of the registered KB source"
                        },
                        "title": {
                            "type": "string",
                            "description": "Document title"
                        },
                        "content": {
                            "type": "string",
                            "description": "Document content"
                        },
                        "metadata": {
                            "type": "object",
                            "description": "Additional metadata for the document"
                        },
                        "collection": {
                            "type": "string",
                            "description": "Target collection (defaults to source's default)"
                        }
                    },
                    "required": ["source", "title", "content"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "ingest_kb".into(),
            }),
        );

        // 20. kb_status
        self.register_tool(
            ToolDefinition {
                name: "kb_status".into(),
                description: "Get the status of knowledge base connections and graph enrichment. \
                              Shows registered sources, Knowledge node count, and Concept node count."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "source": {
                            "type": "string",
                            "description": "Optional: filter status to a specific KB source"
                        }
                    }
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "kb_status".into(),
            }),
        );

        // 21. render_recipe
        self.register_tool(
            ToolDefinition {
                name: "render_recipe".into(),
                description: "Render a recipe template with variable substitution. Returns the \
                              fully rendered prompt ready to use for code generation."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "name": {
                            "type": "string",
                            "description": "Recipe name (e.g. 'refactor', 'test_gen', 'doc_gen')"
                        },
                        "variables": {
                            "type": "object",
                            "description": "Key-value pairs for template variables"
                        }
                    },
                    "required": ["name"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "render_recipe".into(),
            }),
        );

        // 22. reload_scroll
        self.register_tool(
            ToolDefinition {
                name: "reload_scroll".into(),
                description: "Import a .scroll file from disk back into SQLite. Use this when \
                              the user has edited the .scroll file directly (in an IDE or text \
                              editor) and wants to sync those changes. This is called \
                              automatically before `validate_scroll` and `execute_scroll`."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "scroll_id": {
                            "type": "string",
                            "description": "The SCROLL ID to reload from disk"
                        }
                    },
                    "required": ["scroll_id"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "reload_scroll".into(),
            }),
        );

        // 23. furnace_status
        self.register_tool(
            ToolDefinition {
                name: "furnace_status".into(),
                description: "Get the overall FURNACE workspace status: scan results, recipes, \
                              knowledge bases, external connections, telemetry, and actionable \
                              setup suggestions. Call this first to understand what needs setup."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {}
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "furnace_status".into(),
            }),
        );

        // 24. commit_scroll
        self.register_tool(
            ToolDefinition {
                name: "commit_scroll".into(),
                description: "Commit all changes from a completed scroll to git. Creates a \
                              conventional commit with scroll metadata, task listing, and \
                              FURNACE as co-author. Only works on completed scrolls in a git repo."
                    .into(),
                input_schema: json!({
                    "type": "object",
                    "properties": {
                        "scroll_id": {
                            "type": "string",
                            "description": "The SCROLL ID to commit changes for"
                        },
                        "commit_type": {
                            "type": "string",
                            "description": "Override the auto-detected conventional commit type",
                            "enum": ["feat", "fix", "chore", "refactor", "test", "docs"]
                        },
                        "message": {
                            "type": "string",
                            "description": "Custom commit message (overrides auto-generated message)"
                        }
                    },
                    "required": ["scroll_id"]
                }),
            },
            Arc::new(PlaceholderHandler {
                tool_name: "commit_scroll".into(),
            }),
        );

        info!(
            "Registered {} default FURNACE tools",
            self.tools.len()
        );
    }

    // -- dispatching --------------------------------------------------------

    /// Route an incoming tool call to the appropriate handler.
    ///
    /// Returns `Err` if the tool name is unknown.
    pub async fn handle_tool_call(&self, tool_name: &str, args: Value) -> Result<Value> {
        let handler = self
            .handlers
            .get(tool_name)
            .ok_or_else(|| anyhow!("Unknown tool: {}", tool_name))?;

        debug!("Dispatching tool call: {}", tool_name);
        handler.handle(args).await
    }

    // -- introspection ------------------------------------------------------

    /// Return references to all registered tool definitions (useful for
    /// implementing the MCP `tools/list` endpoint).
    pub fn list_tools(&self) -> Vec<&ToolDefinition> {
        let mut defs: Vec<&ToolDefinition> = self.tools.values().collect();
        // Sort for deterministic ordering in tests / API responses.
        defs.sort_by(|a, b| a.name.cmp(&b.name));
        defs
    }

    /// Return a JSON array of tool schemas following the MCP protocol
    /// structure.  Each entry contains `name`, `description`, and
    /// `inputSchema`.
    pub fn tool_schemas(&self) -> Value {
        let mut schemas: Vec<Value> = self
            .tools
            .values()
            .map(|def| {
                json!({
                    "name": def.name,
                    "description": def.description,
                    "inputSchema": def.input_schema,
                })
            })
            .collect();
        // Sort for deterministic output.
        schemas.sort_by(|a, b| {
            a["name"]
                .as_str()
                .unwrap_or("")
                .cmp(b["name"].as_str().unwrap_or(""))
        });
        Value::Array(schemas)
    }

    /// Return the number of registered tools.
    pub fn tool_count(&self) -> usize {
        self.tools.len()
    }

    /// Check whether a tool with the given name is registered.
    pub fn has_tool(&self, name: &str) -> bool {
        self.tools.contains_key(name)
    }

    /// Remove a previously registered tool.  Returns `true` if the tool
    /// existed.
    pub fn unregister_tool(&mut self, name: &str) -> bool {
        let had = self.tools.remove(name).is_some();
        self.handlers.remove(name);
        if had {
            debug!("Unregistered MCP tool: {}", name);
        } else {
            warn!("Attempted to unregister unknown tool: {}", name);
        }
        had
    }

    /// Replace *only* the handler for an existing tool, leaving the
    /// definition unchanged.  Returns `Err` if the tool does not exist.
    pub fn replace_handler(
        &mut self,
        tool_name: &str,
        handler: Arc<dyn ToolHandler>,
    ) -> Result<()> {
        if !self.tools.contains_key(tool_name) {
            return Err(anyhow!("Cannot replace handler: tool '{}' not found", tool_name));
        }
        self.handlers.insert(tool_name.to_string(), handler);
        debug!("Replaced handler for tool: {}", tool_name);
        Ok(())
    }
}

impl Default for McpServer {
    fn default() -> Self {
        Self::new()
    }
}

// ---------------------------------------------------------------------------
// MCP protocol message types (simplified)
// ---------------------------------------------------------------------------

/// A simplified MCP JSON-RPC request envelope.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpRequest {
    pub jsonrpc: String,
    pub id: Value,
    pub method: String,
    pub params: Option<Value>,
}

/// A simplified MCP JSON-RPC response envelope.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpResponse {
    pub jsonrpc: String,
    pub id: Value,
    pub result: Option<Value>,
    pub error: Option<McpError>,
}

/// MCP error payload.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpError {
    pub code: i64,
    pub message: String,
    pub data: Option<Value>,
}

// Standard JSON-RPC / MCP error codes.
const METHOD_NOT_FOUND: i64 = -32601;
const INTERNAL_ERROR: i64 = -32603;

impl McpServer {
    /// Handle a raw MCP JSON-RPC request and produce a response.
    ///
    /// Supported methods:
    /// - `initialize` -- returns server capabilities
    /// - `tools/list` -- returns the tool catalog
    /// - `tools/call` -- dispatches to the appropriate handler
    pub async fn handle_request(&self, request: &McpRequest) -> McpResponse {
        match request.method.as_str() {
            "initialize" => McpResponse {
                jsonrpc: "2.0".into(),
                id: request.id.clone(),
                result: Some(json!({
                    "protocolVersion": "2024-11-05",
                    "capabilities": {
                        "tools": {
                            "listChanged": false
                        }
                    },
                    "serverInfo": {
                        "name": "furnace",
                        "version": env!("CARGO_PKG_VERSION")
                    }
                })),
                error: None,
            },
            "tools/list" => McpResponse {
                jsonrpc: "2.0".into(),
                id: request.id.clone(),
                result: Some(json!({ "tools": self.tool_schemas() })),
                error: None,
            },
            "tools/call" => {
                let params = request.params.clone().unwrap_or(json!({}));
                let tool_name = params["name"].as_str().unwrap_or("");
                let args = params.get("arguments").cloned().unwrap_or(json!({}));

                match self.handle_tool_call(tool_name, args).await {
                    Ok(result) => McpResponse {
                        jsonrpc: "2.0".into(),
                        id: request.id.clone(),
                        result: Some(json!({
                            "content": [{
                                "type": "text",
                                "text": serde_json::to_string(&result).unwrap_or_default()
                            }]
                        })),
                        error: None,
                    },
                    Err(e) => McpResponse {
                        jsonrpc: "2.0".into(),
                        id: request.id.clone(),
                        result: None,
                        error: Some(McpError {
                            code: INTERNAL_ERROR,
                            message: e.to_string(),
                            data: None,
                        }),
                    },
                }
            }
            _ => McpResponse {
                jsonrpc: "2.0".into(),
                id: request.id.clone(),
                result: None,
                error: Some(McpError {
                    code: METHOD_NOT_FOUND,
                    message: format!("Method not found: {}", request.method),
                    data: None,
                }),
            },
        }
    }

    /// Convenience: parse a JSON string into an `McpRequest`, handle it, and
    /// return the serialised JSON response.
    pub async fn handle_json(&self, input: &str) -> Result<String> {
        let request: McpRequest = serde_json::from_str(input)?;
        let response = self.handle_request(&request).await;
        Ok(serde_json::to_string(&response)?)
    }
}

// ===========================================================================
// Tests
// ===========================================================================

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

    // -- helpers ------------------------------------------------------------

    /// Simple echo handler for testing.
    struct EchoHandler;

    #[async_trait::async_trait]
    impl ToolHandler for EchoHandler {
        async fn handle(&self, input: Value) -> Result<Value> {
            Ok(json!({ "echo": input }))
        }
    }

    /// Handler that always returns an error.
    struct ErrorHandler;

    #[async_trait::async_trait]
    impl ToolHandler for ErrorHandler {
        async fn handle(&self, _input: Value) -> Result<Value> {
            Err(anyhow!("intentional test error"))
        }
    }

    fn make_tool(name: &str) -> ToolDefinition {
        ToolDefinition {
            name: name.into(),
            description: format!("Test tool: {}", name),
            input_schema: json!({
                "type": "object",
                "properties": {
                    "input": { "type": "string" }
                }
            }),
        }
    }

    // -- tests --------------------------------------------------------------

    #[test]
    fn test_new_server_is_empty() {
        let server = McpServer::new();
        assert_eq!(server.tool_count(), 0);
        assert!(server.list_tools().is_empty());
    }

    #[test]
    fn test_register_tool() {
        let mut server = McpServer::new();
        server.register_tool(make_tool("alpha"), Arc::new(EchoHandler));
        assert_eq!(server.tool_count(), 1);
        assert!(server.has_tool("alpha"));
        assert!(!server.has_tool("beta"));
    }

    #[test]
    fn test_register_default_tools() {
        let mut server = McpServer::new();
        server.register_default_tools();

        let expected_tools = vec![
            "search_code",
            "create_scroll",
            "add_tasks",
            "execute_scroll",
            "update_scroll",
            "update_task",
            "remove_tasks",
            "validate_scroll",
            "list_scrolls",
            "get_scroll",
            "analyze_file",
            "search_feedback",
            "list_recipes",
            "render_recipe",
            "configure_sync",
            "link_issue",
            "push_scroll",
            "sync_status",
            "list_connections",
            "get_metrics",
            "configure_kb",
            "query_kb",
            "sync_kb",
            "ingest_kb",
            "kb_status",
            "reload_scroll",
            "furnace_status",
            "commit_scroll",
        ];

        assert_eq!(server.tool_count(), expected_tools.len());
        for name in &expected_tools {
            assert!(
                server.has_tool(name),
                "Expected tool '{}' to be registered",
                name
            );
        }
    }

    #[test]
    fn test_list_tools_sorted() {
        let mut server = McpServer::new();
        server.register_tool(make_tool("charlie"), Arc::new(EchoHandler));
        server.register_tool(make_tool("alpha"), Arc::new(EchoHandler));
        server.register_tool(make_tool("bravo"), Arc::new(EchoHandler));

        let names: Vec<&str> = server.list_tools().iter().map(|d| d.name.as_str()).collect();
        assert_eq!(names, vec!["alpha", "bravo", "charlie"]);
    }

    #[test]
    fn test_tool_schemas_structure() {
        let mut server = McpServer::new();
        server.register_tool(make_tool("test_tool"), Arc::new(EchoHandler));

        let schemas = server.tool_schemas();
        let arr = schemas.as_array().expect("should be array");
        assert_eq!(arr.len(), 1);
        assert_eq!(arr[0]["name"], "test_tool");
        assert!(arr[0]["description"].is_string());
        assert!(arr[0]["inputSchema"].is_object());
    }

    #[test]
    fn test_unregister_tool() {
        let mut server = McpServer::new();
        server.register_tool(make_tool("removable"), Arc::new(EchoHandler));
        assert!(server.has_tool("removable"));

        assert!(server.unregister_tool("removable"));
        assert!(!server.has_tool("removable"));
        assert_eq!(server.tool_count(), 0);

        // Removing again should return false.
        assert!(!server.unregister_tool("removable"));
    }

    #[test]
    fn test_replace_handler_success() {
        let mut server = McpServer::new();
        server.register_tool(make_tool("swappable"), Arc::new(EchoHandler));

        let result = server.replace_handler("swappable", Arc::new(ErrorHandler));
        assert!(result.is_ok());
    }

    #[test]
    fn test_replace_handler_unknown_tool() {
        let mut server = McpServer::new();
        let result = server.replace_handler("nonexistent", Arc::new(EchoHandler));
        assert!(result.is_err());
        assert!(
            result.unwrap_err().to_string().contains("not found"),
            "Error should mention 'not found'"
        );
    }

    #[tokio::test]
    async fn test_handle_tool_call_echo() {
        let mut server = McpServer::new();
        server.register_tool(make_tool("echo"), Arc::new(EchoHandler));

        let result = server
            .handle_tool_call("echo", json!({ "msg": "hello" }))
            .await
            .expect("should succeed");
        assert_eq!(result["echo"]["msg"], "hello");
    }

    #[tokio::test]
    async fn test_handle_tool_call_unknown() {
        let server = McpServer::new();
        let result = server.handle_tool_call("nope", json!({})).await;
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("Unknown tool"));
    }

    #[tokio::test]
    async fn test_handle_tool_call_error_handler() {
        let mut server = McpServer::new();
        server.register_tool(make_tool("fail"), Arc::new(ErrorHandler));

        let result = server.handle_tool_call("fail", json!({})).await;
        assert!(result.is_err());
        assert!(result
            .unwrap_err()
            .to_string()
            .contains("intentional test error"));
    }

    #[tokio::test]
    async fn test_default_tools_return_placeholder() {
        let mut server = McpServer::new();
        server.register_default_tools();

        let result = server
            .handle_tool_call("search_code", json!({ "query": "test" }))
            .await
            .expect("placeholder should not error");
        assert!(result["error"]
            .as_str()
            .unwrap_or("")
            .contains("no backend implementation"));
    }

    #[tokio::test]
    async fn test_handle_request_initialize() {
        let mut server = McpServer::new();
        server.register_default_tools();

        let req = McpRequest {
            jsonrpc: "2.0".into(),
            id: json!(1),
            method: "initialize".into(),
            params: None,
        };

        let resp = server.handle_request(&req).await;
        assert!(resp.error.is_none());
        let result = resp.result.unwrap();
        assert_eq!(result["protocolVersion"], "2024-11-05");
        assert!(result["capabilities"]["tools"].is_object());
        assert_eq!(result["serverInfo"]["name"], "furnace");
    }

    #[tokio::test]
    async fn test_handle_request_tools_list() {
        let mut server = McpServer::new();
        server.register_default_tools();

        let req = McpRequest {
            jsonrpc: "2.0".into(),
            id: json!(2),
            method: "tools/list".into(),
            params: None,
        };

        let resp = server.handle_request(&req).await;
        assert!(resp.error.is_none());
        let tools = resp.result.unwrap()["tools"].clone();
        assert_eq!(tools.as_array().unwrap().len(), 28);
    }

    #[tokio::test]
    async fn test_handle_request_tools_call() {
        let mut server = McpServer::new();
        server.register_tool(make_tool("echo"), Arc::new(EchoHandler));

        let req = McpRequest {
            jsonrpc: "2.0".into(),
            id: json!(3),
            method: "tools/call".into(),
            params: Some(json!({
                "name": "echo",
                "arguments": { "data": 42 }
            })),
        };

        let resp = server.handle_request(&req).await;
        assert!(resp.error.is_none());
        let content = resp.result.unwrap();
        // The result wraps handler output in MCP content blocks.
        assert!(content["content"][0]["type"] == "text");
    }

    #[tokio::test]
    async fn test_handle_request_unknown_method() {
        let server = McpServer::new();

        let req = McpRequest {
            jsonrpc: "2.0".into(),
            id: json!(4),
            method: "bogus/method".into(),
            params: None,
        };

        let resp = server.handle_request(&req).await;
        assert!(resp.result.is_none());
        let err = resp.error.unwrap();
        assert_eq!(err.code, METHOD_NOT_FOUND);
        assert!(err.message.contains("bogus/method"));
    }

    #[tokio::test]
    async fn test_handle_json_roundtrip() {
        let mut server = McpServer::new();
        server.register_default_tools();

        let input = serde_json::to_string(&McpRequest {
            jsonrpc: "2.0".into(),
            id: json!(5),
            method: "tools/list".into(),
            params: None,
        })
        .unwrap();

        let output = server.handle_json(&input).await.expect("should succeed");
        let resp: McpResponse = serde_json::from_str(&output).expect("valid JSON response");
        assert!(resp.error.is_none());
        assert!(resp.result.is_some());
    }

    #[tokio::test]
    async fn test_fn_handler() {
        let handler: Arc<dyn ToolHandler> = Arc::new(FnHandler(|input: Value| async move {
            Ok(json!({ "doubled": input["n"].as_i64().unwrap_or(0) * 2 }))
        }));

        let mut server = McpServer::new();
        server.register_tool(make_tool("double"), handler);

        let result = server
            .handle_tool_call("double", json!({ "n": 21 }))
            .await
            .expect("should succeed");
        assert_eq!(result["doubled"], 42);
    }

    #[test]
    fn test_default_tools_have_required_fields_in_schema() {
        let mut server = McpServer::new();
        server.register_default_tools();

        // search_code requires "query"
        let search = server.tools.get("search_code").unwrap();
        let required = search.input_schema["required"]
            .as_array()
            .expect("should have required array");
        assert!(required.contains(&json!("query")));

        // create_scroll requires "intent"
        let create = server.tools.get("create_scroll").unwrap();
        let required = create.input_schema["required"]
            .as_array()
            .expect("should have required array");
        assert!(required.contains(&json!("intent")));

        // list_scrolls has no required fields
        let list = server.tools.get("list_scrolls").unwrap();
        assert!(list.input_schema.get("required").is_none());
    }
}
