//! `diffsift mcp` — Model Context Protocol server (stdio mode).
//!
//! Exposes DiffSift classification as MCP tools for AI agents.
//! Protocol: JSON-RPC 2.0 over stdin/stdout.
//!
//! Usage:
//!   diffsift mcp              # starts stdio MCP server
//!
//! Tools exposed:
//!   classify_diff     — classify a git diff between two refs
//!   classify_patch    — classify a raw unified diff string
//!   get_high_risk     — get only HIGH/MODERATE findings
//!   summarize         — natural language summary of changes

use std::io::{self, BufRead, Write};
use std::path::Path;

use clap::Args;
use serde::{Deserialize, Serialize};

#[derive(Args)]
pub struct McpArgs {
    /// Working directory for git operations
    #[arg(long, default_value = ".")]
    pub repo: std::path::PathBuf,
}

pub fn run(args: McpArgs) -> miette::Result<()> {
    let stdin = io::stdin();
    let stdout = io::stdout();

    // Process JSON-RPC messages line by line
    for line in stdin.lock().lines() {
        let line = line.map_err(|e| miette::miette!("stdin read error: {e}"))?;
        if line.trim().is_empty() {
            continue;
        }

        let request: JsonRpcRequest = match serde_json::from_str(&line) {
            Ok(r) => r,
            Err(e) => {
                write_response(
                    &stdout,
                    &JsonRpcResponse::error(
                        serde_json::Value::Null,
                        -32700,
                        format!("Parse error: {e}"),
                    ),
                );
                continue;
            }
        };

        let response = handle_request(&request, &args.repo);
        write_response(&stdout, &response);
    }

    Ok(())
}

fn handle_request(req: &JsonRpcRequest, repo: &Path) -> JsonRpcResponse {
    match req.method.as_str() {
        "initialize" => JsonRpcResponse::success(
            req.id.clone(),
            serde_json::json!({
                "protocolVersion": "2024-11-05",
                "capabilities": {
                    "tools": {}
                },
                "serverInfo": {
                    "name": "diffsift",
                    "version": env!("CARGO_PKG_VERSION")
                }
            }),
        ),
        "tools/list" => JsonRpcResponse::success(
            req.id.clone(),
            serde_json::json!({
                "tools": [
                    {
                        "name": "classify_diff",
                        "description": "Classify a git diff between two refs by semantic impact. Returns categorized hunks with priority levels.",
                        "inputSchema": {
                            "type": "object",
                            "properties": {
                                "base_ref": { "type": "string", "description": "Base git ref (branch, tag, or commit SHA)" },
                                "head_ref": { "type": "string", "description": "Head git ref" },
                                "repo_path": { "type": "string", "description": "Path to git repo (default: current directory)" }
                            },
                            "required": ["base_ref", "head_ref"]
                        }
                    },
                    {
                        "name": "classify_patch",
                        "description": "Classify a raw unified diff/patch string. No git repo needed.",
                        "inputSchema": {
                            "type": "object",
                            "properties": {
                                "patch": { "type": "string", "description": "Unified diff text" }
                            },
                            "required": ["patch"]
                        }
                    },
                    {
                        "name": "get_high_risk",
                        "description": "Get only HIGH and MODERATE priority findings from a git diff. Use this for quick risk assessment.",
                        "inputSchema": {
                            "type": "object",
                            "properties": {
                                "base_ref": { "type": "string", "description": "Base git ref" },
                                "head_ref": { "type": "string", "description": "Head git ref" },
                                "repo_path": { "type": "string", "description": "Path to git repo" }
                            },
                            "required": ["base_ref", "head_ref"]
                        }
                    },
                    {
                        "name": "summarize",
                        "description": "Get a concise text summary of changes: file count, category breakdown, top risks.",
                        "inputSchema": {
                            "type": "object",
                            "properties": {
                                "base_ref": { "type": "string", "description": "Base git ref" },
                                "head_ref": { "type": "string", "description": "Head git ref" },
                                "repo_path": { "type": "string", "description": "Path to git repo" }
                            },
                            "required": ["base_ref", "head_ref"]
                        }
                    }
                ]
            }),
        ),
        "tools/call" => {
            let params = req.params.as_ref();
            let tool_name = params.and_then(|p| p["name"].as_str()).unwrap_or("");
            let args = params
                .and_then(|p| p.get("arguments"))
                .cloned()
                .unwrap_or(serde_json::Value::Object(serde_json::Map::new()));

            match tool_name {
                "classify_diff" => handle_classify_diff(&args, repo, &req.id),
                "classify_patch" => handle_classify_patch(&args, &req.id),
                "get_high_risk" => handle_get_high_risk(&args, repo, &req.id),
                "summarize" => handle_summarize(&args, repo, &req.id),
                _ => JsonRpcResponse::error(
                    req.id.clone(),
                    -32601,
                    format!("Unknown tool: {tool_name}"),
                ),
            }
        }
        "notifications/initialized" | "notifications/cancelled" => {
            // Notifications don't get responses
            JsonRpcResponse::success(req.id.clone(), serde_json::Value::Null)
        }
        _ => JsonRpcResponse::error(
            req.id.clone(),
            -32601,
            format!("Method not found: {}", req.method),
        ),
    }
}

fn handle_classify_diff(
    args: &serde_json::Value,
    default_repo: &Path,
    id: &serde_json::Value,
) -> JsonRpcResponse {
    let base = args["base_ref"].as_str().unwrap_or("main");
    let head = args["head_ref"].as_str().unwrap_or("HEAD");
    let repo_path = args["repo_path"]
        .as_str()
        .map(Path::new)
        .unwrap_or(default_repo);

    match diffsift_core::classify_diff(repo_path, base, head) {
        Ok(result) => {
            let json = serde_json::to_value(&result).unwrap_or_default();
            JsonRpcResponse::success(
                id.clone(),
                serde_json::json!({
                    "content": [{
                        "type": "text",
                        "text": serde_json::to_string_pretty(&json).unwrap_or_default()
                    }]
                }),
            )
        }
        Err(e) => JsonRpcResponse::error(id.clone(), -32000, format!("Classification failed: {e}")),
    }
}

fn handle_classify_patch(args: &serde_json::Value, id: &serde_json::Value) -> JsonRpcResponse {
    let patch = args["patch"].as_str().unwrap_or("");
    if patch.is_empty() {
        return JsonRpcResponse::error(id.clone(), -32602, "patch is required".to_string());
    }

    match diffsift_core::classify_patch(patch) {
        Ok(result) => {
            let json = serde_json::to_value(&result).unwrap_or_default();
            JsonRpcResponse::success(
                id.clone(),
                serde_json::json!({
                    "content": [{
                        "type": "text",
                        "text": serde_json::to_string_pretty(&json).unwrap_or_default()
                    }]
                }),
            )
        }
        Err(e) => JsonRpcResponse::error(id.clone(), -32000, format!("Classification failed: {e}")),
    }
}

fn handle_get_high_risk(
    args: &serde_json::Value,
    default_repo: &Path,
    id: &serde_json::Value,
) -> JsonRpcResponse {
    let base = args["base_ref"].as_str().unwrap_or("main");
    let head = args["head_ref"].as_str().unwrap_or("HEAD");
    let repo_path = args["repo_path"]
        .as_str()
        .map(Path::new)
        .unwrap_or(default_repo);

    match diffsift_core::classify_diff(repo_path, base, head) {
        Ok(result) => {
            let high_risk: Vec<serde_json::Value> = result
                .files
                .iter()
                .flat_map(|f| {
                    f.hunks
                        .iter()
                        .filter(|h| {
                            h.priority >= diffsift_core::Priority::Moderate
                        })
                        .map(move |h| {
                            serde_json::json!({
                                "file": f.path,
                                "lines": format!("{}-{}", h.start_line, h.end_line),
                                "category": format!("{:?}", h.category),
                                "priority": format!("{:?}", h.priority),
                                "rules": h.patterns.iter().map(|p| &p.pattern_id).collect::<Vec<_>>(),
                                "diff": h.diff_text,
                            })
                        })
                })
                .collect();

            JsonRpcResponse::success(
                id.clone(),
                serde_json::json!({
                    "content": [{
                        "type": "text",
                        "text": serde_json::to_string_pretty(&high_risk).unwrap_or_default()
                    }]
                }),
            )
        }
        Err(e) => JsonRpcResponse::error(id.clone(), -32000, format!("Classification failed: {e}")),
    }
}

fn handle_summarize(
    args: &serde_json::Value,
    default_repo: &Path,
    id: &serde_json::Value,
) -> JsonRpcResponse {
    let base = args["base_ref"].as_str().unwrap_or("main");
    let head = args["head_ref"].as_str().unwrap_or("HEAD");
    let repo_path = args["repo_path"]
        .as_str()
        .map(Path::new)
        .unwrap_or(default_repo);

    match diffsift_core::classify_diff(repo_path, base, head) {
        Ok(result) => {
            let s = &result.summary;
            let mut summary = format!(
                "DiffSift Analysis: {} files, {} hunks in {}ms\n\n",
                s.total_files, s.total_hunks, result.classification_time_ms
            );
            summary.push_str(&format!(
                "Priority: {} HIGH, {} MODERATE, {} LOW, {} FILTERED\n\n",
                s.priority_counts.high,
                s.priority_counts.moderate,
                s.priority_counts.low,
                s.priority_counts.filtered
            ));

            if !s.category_counts.is_empty() {
                summary.push_str("Categories:\n");
                for cc in &s.category_counts {
                    summary.push_str(&format!("  {:?}: {}\n", cc.category, cc.count));
                }
            }

            let high_count = s.priority_counts.high;
            if high_count > 0 {
                summary.push_str(&format!(
                    "\n⚠ {} HIGH priority findings require review.\n",
                    high_count
                ));
            } else {
                summary.push_str("\n✓ No high-priority findings.\n");
            }

            JsonRpcResponse::success(
                id.clone(),
                serde_json::json!({
                    "content": [{
                        "type": "text",
                        "text": summary
                    }]
                }),
            )
        }
        Err(e) => JsonRpcResponse::error(id.clone(), -32000, format!("Classification failed: {e}")),
    }
}

fn write_response(stdout: &io::Stdout, response: &JsonRpcResponse) {
    let json = serde_json::to_string(response).unwrap_or_default();
    let mut out = stdout.lock();
    let _ = writeln!(out, "{json}");
    let _ = out.flush();
}

// ── JSON-RPC types ──────────────────────────────────────────────────────────

#[derive(Deserialize)]
struct JsonRpcRequest {
    #[allow(dead_code)]
    jsonrpc: String,
    method: String,
    params: Option<serde_json::Value>,
    id: serde_json::Value,
}

#[derive(Serialize)]
struct JsonRpcResponse {
    jsonrpc: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    result: Option<serde_json::Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    error: Option<JsonRpcError>,
    id: serde_json::Value,
}

#[derive(Serialize)]
struct JsonRpcError {
    code: i32,
    message: String,
}

impl JsonRpcResponse {
    fn success(id: serde_json::Value, result: serde_json::Value) -> Self {
        Self {
            jsonrpc: "2.0".to_string(),
            result: Some(result),
            error: None,
            id,
        }
    }

    fn error(id: serde_json::Value, code: i32, message: String) -> Self {
        Self {
            jsonrpc: "2.0".to_string(),
            result: None,
            error: Some(JsonRpcError { code, message }),
            id,
        }
    }
}
