use std::io::IsTerminal;
use std::sync::Arc;

use anyhow::Result;
use rmcp::model::{
    CallToolRequestParam, CallToolResult, Content, Implementation, ListToolsResult,
    PaginatedRequestParam, ServerCapabilities, ServerInfo, Tool,
};
use rmcp::handler::server::ServerHandler;
use rmcp::service::{Peer, RequestContext};
use rmcp::{RoleServer, ServiceExt};
use serde_json::Value;
use tokio::sync::OnceCell;
use tracing_subscriber::EnvFilter;

use furnace_mcp::config::FurnaceConfig;
use furnace_mcp::lazy_init::{self, ProjectKeepAlive};
use furnace_mcp::mcp::server::McpServer;

/// Tools that can respond without triggering full project initialization.
/// These return lightweight, pre-init responses so that `.furnace/` is never
/// created merely because the host's LLM proactively calls an informational
/// tool (e.g., Claude following the "Getting Started" instructions).
const NO_INIT_TOOLS: &[&str] = &[
    "furnace_status",
    "get_metrics",
    "list_scrolls",
    "list_recipes",
    "list_connections",
];

const VERSION: &str = env!("CARGO_PKG_VERSION");

// ---------------------------------------------------------------------------
// FurnaceHandler -- bridges our McpServer to the rmcp ServerHandler trait
// ---------------------------------------------------------------------------

/// The rmcp-compatible server handler that wraps the internal McpServer and
/// all FURNACE subsystems. It implements `ServerHandler` so that rmcp can
/// drive the MCP protocol over stdio (or any other transport).
///
/// Project-level resources (databases, agents, workspace scan) are lazily
/// initialised on the first tool call via `project_init`. This ensures that
/// `.furnace/` is never created in the user's workspace until they actually
/// invoke a FURNACE tool.
#[derive(Clone)]
struct FurnaceHandler {
    /// McpServer behind a RwLock so that lazy init can replace placeholder
    /// handlers with real implementations on first tool call.
    inner: Arc<tokio::sync::RwLock<McpServer>>,
    /// Shared cell for the MCP peer, populated on first tool call.
    /// Used by SamplingLlmProvider to send sampling requests to the host.
    peer_cell: Arc<OnceCell<Peer<RoleServer>>>,
    /// One-shot project initializer.  Populated on first tool call.
    project_init: Arc<OnceCell<ProjectKeepAlive>>,
    /// Immutable configuration snapshot (paths, search settings, etc.).
    config: Arc<FurnaceConfig>,
}

impl FurnaceHandler {
    fn new(
        inner: McpServer,
        peer_cell: Arc<OnceCell<Peer<RoleServer>>>,
        config: Arc<FurnaceConfig>,
    ) -> Self {
        Self {
            inner: Arc::new(tokio::sync::RwLock::new(inner)),
            peer_cell,
            project_init: Arc::new(OnceCell::new()),
            config,
        }
    }
}

impl ServerHandler for FurnaceHandler {
    fn get_info(&self) -> ServerInfo {
        ServerInfo {
            protocol_version: Default::default(),
            capabilities: ServerCapabilities::builder()
                .enable_tools()
                .build(),
            server_info: Implementation {
                name: "furnace".into(),
                version: env!("CARGO_PKG_VERSION").into(),
            },
            instructions: Some(r#"FURNACE — Formal Unified Runtime for Network-Augmented Code Engineering.
An AI-powered code generation, analysis, and knowledge management MCP server.

## How to Use FURNACE (Step-by-Step)

### Step 1: Create a SCROLL
Call `create_scroll` with your intent. This creates a specification and extracts constraints from the workspace knowledge graph.

### Step 2: Decompose & Add Tasks
Break your intent into concrete file-level tasks. For each file you need to create, modify, delete, or test — generate the code and call `add_tasks` with:
- `scroll_id` from Step 1
- `tasks[]` array where each task has `description`, `target_file`, `task_type` (Create/Modify/Delete/Test), and `generated_code`

### Step 3: Review & Edit (Courtesy Step)
Present the scroll to the user for review using `get_scroll`. Show each task with its target file, description, and operation type. The user can:
- Edit tasks via `update_task` (change code, description, target file)
- Add more tasks via `add_tasks`
- Remove tasks via `remove_tasks`
- Edit scroll metadata via `update_scroll` (title, intent, execution mode)
All edits are automatically synced to linked external systems (Jira, Linear, Notion).

### Step 4: Validate (Optional)
Call `validate_scroll` to check constraints, circular dependencies, type errors, and breaking changes.

### Step 5: Execute
Call `execute_scroll` to apply all changes. FURNACE will:
- Verify and augment tasks using LLM analysis (Gandalf)
- Orchestrate tasks respecting dependency order (Aragorn)
- Monitor execution health and halt bad plans early (Elrond)
- Apply generated code to the filesystem (AutoForge)
- Run the project's test suite (AutoForge)
- Verify code quality — no secrets, no TODOs, reasonable complexity (AutoForge)
- Re-index changed files for up-to-date search results (Elrond)
- Return a comprehensive report

### Step 6: Commit (Optional)
Call `commit_scroll` to commit all changes to git as a conventional commit with scroll metadata.

**Note:** FURNACE uses MCP sampling to generate code and tests. For smooth AutoForge
execution, consider auto-approving sampling requests in your MCP client settings.

## Tool Categories

### Search & Analysis
- `search_code` — Tiered search: semantic (Tier 1), hybrid (Tier 2), agentic (Tier 3)
- `search_feedback` — Improve future search quality by rating results
- `analyze_file` — Code quality analysis for a single file

### Scrolls (Code Change Specifications)
- `create_scroll` — Create a new SCROLL from an intent
- `add_tasks` — Decompose intent into file-level tasks with generated code
- `update_scroll` — Edit scroll metadata (title, intent, execution mode, AutoForge config)
- `update_task` — Edit a task (description, target file, code, dependencies)
- `remove_tasks` — Remove tasks from a scroll
- `reload_scroll` — Import .scroll file edits from disk into SQLite (auto-called by validate/execute)
- `validate_scroll` — Validate a SCROLL before execution
- `execute_scroll` — Orchestrate + AutoForge: apply, test, verify
- `list_scrolls` — List all SCROLLs, optionally filtered by status
- `get_scroll` — Retrieve a specific SCROLL with full details
- `commit_scroll` — Commit completed scroll changes to git

### Recipes (Portable Prompt Templates)
- `list_recipes` — List available recipes with descriptions
- `render_recipe` — Render a recipe template with variable substitution

### External Sync (Linear, Jira, Notion)
- `configure_sync` — Connect an external system with API token
- `link_issue` — Link a SCROLL to an external issue/page
- `push_scroll` — Push SCROLL status to an external system
- `sync_status` — Preview status mapping without pushing
- `list_connections` — Show all external system connections

### Knowledge Base
- `configure_kb` — Register a KB source (ChromaDB, Pinecone, etc.)
- `query_kb` — Search a knowledge base for documents
- `sync_kb` — Fetch KB documents and assimilate into the knowledge graph
- `ingest_kb` — Push a document to a KB collection
- `kb_status` — Check KB connection and graph enrichment status

### Monitoring
- `get_metrics` — Aggregate usage metrics and telemetry statistics
- `furnace_status` — Check workspace health, setup completeness, and get suggestions

## FURNACE Agents
- **Gandalf** — Extracts constraints and LLM-verifies task plans before execution
- **Gimli** — Code generation (used when tasks don't have pre-generated code)
- **Trinity** — Test generation during AutoForge verification
- **Legolas** — File-level code quality analysis
- **Morpheus** — Scroll validation (constraints, dependencies, types, breaking changes)
- **Elrond** — Workspace indexing, code cartography, and execution health monitoring
- **Aragorn** — Task orchestration with parallel execution and health checks
- **AutoForge** — Automated apply → test → verify pipeline

## Getting Started
Use `create_scroll` to begin a code change workflow. Use `search_code` to explore the codebase.
`furnace_status` shows workspace health — it is safe to call without side effects."#.into(),
            ),
        }
    }

    fn list_tools(
        &self,
        _request: PaginatedRequestParam,
        _context: RequestContext<RoleServer>,
    ) -> impl std::future::Future<Output = Result<ListToolsResult, rmcp::Error>> + Send + '_ {
        let inner = Arc::clone(&self.inner);
        async move {
            let server = inner.read().await;
            let tools: Vec<Tool> = server
                .list_tools()
                .into_iter()
                .map(|def| {
                    let schema_map: serde_json::Map<String, Value> = def
                        .input_schema
                        .as_object()
                        .cloned()
                        .unwrap_or_default();
                    Tool::new(
                        def.name.clone(),
                        def.description.clone(),
                        Arc::new(schema_map),
                    )
                })
                .collect();

            Ok(ListToolsResult {
                tools,
                next_cursor: None,
            })
        }
    }

    fn call_tool(
        &self,
        request: CallToolRequestParam,
        context: RequestContext<RoleServer>,
    ) -> impl std::future::Future<Output = Result<CallToolResult, rmcp::Error>> + Send + '_ {
        // Capture the peer from the request context so SamplingLlmProvider can use it.
        let _ = self.peer_cell.set(context.peer.clone());

        let inner = Arc::clone(&self.inner);
        let config = Arc::clone(&self.config);
        let peer_cell = Arc::clone(&self.peer_cell);
        let project_init = Arc::clone(&self.project_init);
        let tool_name = request.name.to_string();
        let args: Value = request
            .arguments
            .map(Value::Object)
            .unwrap_or(Value::Object(Default::default()));

        async move {
            // If the project hasn't been initialized yet AND the requested tool
            // is informational, return a lightweight pre-init response instead
            // of triggering full initialization (which creates .furnace/).
            if project_init.get().is_none() && NO_INIT_TOOLS.contains(&tool_name.as_str()) {
                let response = pre_init_response(&tool_name, &config);
                let text = serde_json::to_string_pretty(&response).unwrap_or_default();
                return Ok(CallToolResult::success(vec![Content::text(text)]));
            }

            // Lazy project initialization — runs exactly once on first tool call.
            // Creates .furnace/, databases, agents, scans workspace, wires handlers.
            project_init
                .get_or_try_init(|| {
                    lazy_init::initialize_project(config, peer_cell, Arc::clone(&inner))
                })
                .await
                .map_err(|e: anyhow::Error| {
                    rmcp::Error::internal_error(
                        format!("FURNACE project initialization failed: {e}"),
                        None,
                    )
                })?;

            // Dispatch to the (now-real) handler
            let server = inner.read().await;
            match server.handle_tool_call(&tool_name, args).await {
                Ok(result) => {
                    let text = serde_json::to_string_pretty(&result).unwrap_or_default();
                    Ok(CallToolResult::success(vec![Content::text(text)]))
                }
                Err(e) => Ok(CallToolResult::error(vec![Content::text(e.to_string())])),
            }
        }
    }
}

// ---------------------------------------------------------------------------
// Pre-init responses for NO_INIT_TOOLS
// ---------------------------------------------------------------------------

/// Returns a lightweight JSON response for tools that should NOT trigger full
/// project initialization.  This prevents `.furnace/` from being created in
/// workspaces where the user never actually uses FURNACE tools.
fn pre_init_response(tool_name: &str, config: &FurnaceConfig) -> Value {
    match tool_name {
        "furnace_status" => {
            let project_dir = config.workspace_root.join(".furnace");
            let initialized = project_dir.exists();
            serde_json::json!({
                "status": if initialized { "initialized" } else { "not_initialized" },
                "workspace": {
                    "root": config.workspace_root.display().to_string(),
                    "files_indexed": 0,
                    "scan_complete": false,
                },
                "directories": {
                    "project": project_dir.display().to_string(),
                    "user": config.user_dir.display().to_string(),
                    "first_run": !initialized,
                    "gitignored": false,
                },
                "note": if initialized {
                    "FURNACE project directory exists but agents are not yet active. \
                     Call any scroll or search tool to activate the full engine."
                } else {
                    "FURNACE has not been initialized in this workspace. \
                     The .furnace/ directory will be created when you first call a \
                     scroll, search, or analysis tool. No action needed until then."
                },
                "workflow": {
                    "steps": [
                        "1. search_code — find relevant code and understand the codebase",
                        "2. create_scroll — create a scroll with your intent",
                        "3. add_tasks — decompose into file-level tasks with generated_code",
                        "4. execute_scroll — FURNACE handles ordering, code gen, tests, and verification"
                    ],
                    "important": "Always use scrolls for code changes. execute_scroll runs the full pipeline automatically."
                },
                "recipes": { "count": 0, "list": [] },
                "knowledge_bases": [],
                "external_systems": [],
                "telemetry": null,
                "scrolls": { "total": 0 },
                "suggestions": [],
            })
        }
        "get_metrics" => {
            serde_json::json!({
                "status": "not_initialized",
                "note": "FURNACE has not been activated in this workspace yet. \
                         Metrics will be available after the first scroll or search tool call.",
                "metrics": {}
            })
        }
        "list_scrolls" => {
            serde_json::json!({
                "status": "not_initialized",
                "scrolls": [],
                "note": "No scrolls yet. Use create_scroll to start a code change workflow."
            })
        }
        "list_recipes" => {
            serde_json::json!({
                "status": "not_initialized",
                "recipes": [],
                "note": "FURNACE project not yet initialized. Recipes will be available after \
                         the first scroll or search tool call."
            })
        }
        "list_connections" => {
            serde_json::json!({
                "status": "not_initialized",
                "connections": [],
                "note": "No external connections configured. Use configure_sync after \
                         FURNACE is initialized."
            })
        }
        _ => {
            serde_json::json!({
                "error": format!("Tool '{}' called before project initialization", tool_name)
            })
        }
    }
}

// ---------------------------------------------------------------------------
// main
// ---------------------------------------------------------------------------

#[tokio::main]
async fn main() -> Result<()> {
    // Handle --version / --help before any initialization
    let args: Vec<String> = std::env::args().collect();
    if args.len() > 1 {
        match args[1].as_str() {
            "--version" | "-V" => {
                eprintln!("furnace {}", VERSION);
                return Ok(());
            }
            "--help" | "-h" => {
                eprintln!("furnace {} - AI-powered code generation and analysis MCP server", VERSION);
                eprintln!();
                eprintln!("USAGE:");
                eprintln!("    furnace [OPTIONS]");
                eprintln!();
                eprintln!("OPTIONS:");
                eprintln!("    -h, --help       Print help information");
                eprintln!("    -V, --version    Print version information");
                eprintln!();
                eprintln!("DESCRIPTION:");
                eprintln!("    FURNACE is an MCP server that provides code generation, analysis,");
                eprintln!("    and search tools to LLM clients over stdio transport.");
                eprintln!();
                eprintln!("    Configure your MCP client to run this binary. For example:");
                eprintln!();
                eprintln!("    Claude Desktop (claude_desktop_config.json):");
                eprintln!(r#"      {{"mcpServers": {{"furnace": {{"command": "furnace"}}}}}}"#);
                eprintln!();
                eprintln!("    All logs are written to stderr. stdout is reserved for MCP JSON-RPC.");
                return Ok(());
            }
            _ => {
                eprintln!("furnace: unknown option '{}'. Try 'furnace --help'.", args[1]);
                std::process::exit(1);
            }
        }
    }

    // If stdin is a terminal (not piped from an MCP client), show help and exit.
    // This prevents the confusing "EOF while parsing" error when users run
    // `furnace` or `npx furnace-mcp` directly from a shell.
    if std::io::stdin().is_terminal() {
        eprintln!("furnace {} - AI-powered code generation and analysis MCP server", VERSION);
        eprintln!();
        eprintln!("FURNACE is an MCP server and must be launched by an MCP client.");
        eprintln!("It communicates via JSON-RPC over stdio, not interactively.");
        eprintln!();
        eprintln!("SETUP:");
        eprintln!();
        eprintln!("  Claude Code / Claude Desktop (claude_desktop_config.json):");
        eprintln!(r#"    {{"mcpServers": {{"furnace": {{"command": "npx", "args": ["-y", "furnace-mcp"]}}}}}}"#);
        eprintln!();
        eprintln!("  Or with a local binary:");
        eprintln!(r#"    {{"mcpServers": {{"furnace": {{"command": "furnace"}}}}}}"#);
        eprintln!();
        eprintln!("INSTALL:");
        eprintln!("  npm install -g furnace-mcp   # installs 'furnace' binary globally");
        eprintln!("  cargo install furnace-mcp    # build from source via crates.io");
        eprintln!();
        eprintln!("For more info: https://github.com/forgewithfurnace/furnace-releases");
        return Ok(());
    }

    // Direct tracing to stderr so stdout stays clean for MCP JSON-RPC.
    tracing_subscriber::fmt()
        .with_env_filter(EnvFilter::from_default_env())
        .with_writer(std::io::stderr)
        .init();

    tracing::info!("FURNACE v{} starting...", VERSION);

    // 1. Load configuration — only create user-level dirs at startup.
    //    Project-level dirs (.furnace/) are deferred to the first tool call
    //    via LazyProjectInit, so opening a project in an MCP client doesn't
    //    pollute the workspace.
    let config = FurnaceConfig::load()?;
    config.ensure_user_dirs()?;

    tracing::info!(
        user_dir = %config.user_dir.display(),
        workspace = %config.workspace_root.display(),
        "Config loaded (project init deferred to first tool call)"
    );

    // 2. Build the MCP server with placeholder handlers.
    //    Tool definitions (schemas) are registered now so that `tools/list`
    //    works immediately during MCP initialization. Real handler backends
    //    are wired on the first `call_tool` invocation.
    let mut mcp_server = McpServer::new();
    mcp_server.register_default_tools();

    // 3. The peer_cell is shared with FurnaceHandler and (eventually) with
    //    SamplingLlmProvider.  It gets populated on the first tool call
    //    when rmcp provides the peer via RequestContext.
    let peer_cell: Arc<OnceCell<Peer<RoleServer>>> = Arc::new(OnceCell::new());

    // 4. Wrap in the rmcp-compatible handler and serve over stdio.
    let handler = FurnaceHandler::new(mcp_server, Arc::clone(&peer_cell), Arc::new(config));

    tracing::info!("Starting MCP server on stdio...");
    let transport = (tokio::io::stdin(), tokio::io::stdout());
    let server = handler.serve(transport).await?;

    // 5. Wait for shutdown
    let reason = server.waiting().await?;
    tracing::info!(?reason, "FURNACE server shut down");

    Ok(())
}
