use std::{path::PathBuf, sync::Arc};

use rmcp::{
    model::*, service::RequestContext, transport::stdio, ErrorData, RoleServer, ServerHandler,
    ServiceExt,
};
use serde_json::{json, Map, Value};

use crate::{
    automation::{self, RunRequest},
    error::{HenError, HenErrorKind},
    report::{self, BodyReportOptions},
    request::ExecutionOptions,
};

const SYNTAX_REFERENCE: &str = include_str!("../syntax-reference.md");
const README_GUIDE: &str = include_str!("../README.md");
const AUTHORING_GUIDE_URI: &str = "hen://authoring-guide";
const README_URI: &str = "hen://readme";

#[derive(Clone)]
pub struct HenMcpServer {
    root: PathBuf,
}

impl HenMcpServer {
    pub fn new(root: PathBuf) -> Self {
        Self { root }
    }
}

impl ServerHandler for HenMcpServer {
    fn get_info(&self) -> ServerInfo {
        ServerInfo {
            capabilities: ServerCapabilities::builder()
                .enable_tools()
                .enable_resources()
                .build(),
            server_info: Implementation {
                name: "hen-mcp".to_string(),
                title: Some("Hen MCP Server".to_string()),
                version: env!("CARGO_PKG_VERSION").to_string(),
                description: Some(
                    "Run, verify, and author .hen collections without using the interactive CLI."
                        .to_string(),
                ),
                icons: None,
                website_url: None,
            },
            instructions: Some(
                "Run, verify, and author .hen collections without using the interactive CLI."
                    .to_string(),
            ),
            ..Default::default()
        }
    }

    async fn list_tools(
        &self,
        _request: Option<PaginatedRequestParams>,
        _context: RequestContext<RoleServer>,
    ) -> Result<ListToolsResult, ErrorData> {
        Ok(ListToolsResult {
            tools: vec![
                Tool::new(
                    "run_hen",
                    "Run a .hen collection or a single request non-interactively.",
                    Arc::new(
                        json!({
                            "type": "object",
                            "properties": {
                                "path": {
                                    "type": "string",
                                    "description": "Path to a .hen file, or a directory containing exactly one .hen file. Relative paths resolve from the server working directory."
                                },
                                "selector": {
                                    "type": "string",
                                    "description": "Request selector. Use an integer index or 'all'. Required when the collection has multiple requests."
                                },
                                "inputs": {
                                    "type": "object",
                                    "description": "Values for [[ prompt ]] placeholders.",
                                    "additionalProperties": { "type": "string" }
                                },
                                "parallel": {
                                    "type": "boolean",
                                    "description": "Run independent requests concurrently."
                                },
                                "maxConcurrency": {
                                    "type": "integer",
                                    "minimum": 1,
                                    "description": "Maximum number of concurrent requests."
                                },
                                "continueOnError": {
                                    "type": "boolean",
                                    "description": "Continue running unaffected branches after a failure."
                                },
                                "includeBody": {
                                    "type": "boolean",
                                    "description": "Include response bodies in the returned execution records. Defaults to true."
                                },
                                "maxBodyChars": {
                                    "type": "integer",
                                    "minimum": 1,
                                    "description": "Trim response bodies to at most this many characters."
                                }
                            },
                            "required": ["path"]
                        })
                        .as_object()
                        .unwrap()
                        .clone(),
                    ),
                ),
                Tool::new(
                    "verify_hen_syntax",
                    "Parse and verify .hen syntax without executing shell variables or network requests.",
                    Arc::new(
                        json!({
                            "type": "object",
                            "properties": {
                                "path": {
                                    "type": "string",
                                    "description": "Path to the .hen file to verify."
                                },
                                "source": {
                                    "type": "string",
                                    "description": "Inline .hen source to verify. Provide this instead of path when validating editor content."
                                },
                                "workingDirectory": {
                                    "type": "string",
                                    "description": "Working directory used to resolve << fragment imports when source is provided."
                                }
                            }
                        })
                        .as_object()
                        .unwrap()
                        .clone(),
                    ),
                ),
                Tool::new(
                    "get_hen_authoring_guide",
                    "Return the built-in Hen syntax and usage guide.",
                    Arc::new(
                        json!({
                            "type": "object",
                            "properties": {
                                "topic": {
                                    "type": "string",
                                    "description": "Optional guide topic. Use 'syntax' or 'usage'."
                                }
                            }
                        })
                        .as_object()
                        .unwrap()
                        .clone(),
                    ),
                ),
            ],
            meta: None,
            next_cursor: None,
        })
    }

    async fn call_tool(
        &self,
        request: CallToolRequestParams,
        _context: RequestContext<RoleServer>,
    ) -> Result<CallToolResult, ErrorData> {
        let empty = Map::new();
        let args = request.arguments.as_ref().unwrap_or(&empty);

        match request.name.as_ref() {
            "run_hen" => self.run_hen(args).await,
            "verify_hen_syntax" => self.verify_hen_syntax(args),
            "get_hen_authoring_guide" => self.get_hen_authoring_guide(args),
            _ => Err(ErrorData::new(
                ErrorCode::METHOD_NOT_FOUND,
                "Unknown tool",
                None,
            )),
        }
    }

    async fn list_resources(
        &self,
        _request: Option<PaginatedRequestParams>,
        _context: RequestContext<RoleServer>,
    ) -> Result<ListResourcesResult, ErrorData> {
        Ok(ListResourcesResult {
            resources: vec![
                RawResource::new(AUTHORING_GUIDE_URI, "Hen authoring guide").no_annotation(),
                RawResource::new(README_URI, "Hen README").no_annotation(),
            ],
            next_cursor: None,
            meta: None,
        })
    }

    async fn read_resource(
        &self,
        request: ReadResourceRequestParams,
        _context: RequestContext<RoleServer>,
    ) -> Result<ReadResourceResult, ErrorData> {
        match request.uri.as_str() {
            AUTHORING_GUIDE_URI => Ok(ReadResourceResult {
                contents: vec![ResourceContents::text(SYNTAX_REFERENCE, &request.uri)],
            }),
            README_URI => Ok(ReadResourceResult {
                contents: vec![ResourceContents::text(README_GUIDE, &request.uri)],
            }),
            _ => Err(ErrorData::resource_not_found(
                "Resource not found",
                Some(json!({ "uri": request.uri })),
            )),
        }
    }

    async fn list_resource_templates(
        &self,
        _request: Option<PaginatedRequestParams>,
        _context: RequestContext<RoleServer>,
    ) -> Result<ListResourceTemplatesResult, ErrorData> {
        Ok(ListResourceTemplatesResult {
            resource_templates: vec![],
            next_cursor: None,
            meta: None,
        })
    }
}

impl HenMcpServer {
    async fn run_hen(&self, args: &Map<String, Value>) -> Result<CallToolResult, ErrorData> {
        let path = required_string(args, "path")?;
        let selector = optional_selector(args.get("selector"))?;
        let inputs = optional_string_map(args.get("inputs"))?;
        let parallel = optional_bool(args.get("parallel")).unwrap_or(false);
        let max_concurrency = optional_usize(args.get("maxConcurrency"))?;
        let continue_on_error = optional_bool(args.get("continueOnError")).unwrap_or(false);
        let include_body = optional_bool(args.get("includeBody")).unwrap_or(true);
        let max_body_chars = optional_usize(args.get("maxBodyChars"))?;

        let outcome = automation::run_path(RunRequest {
            path: self.resolve_path(&path),
            selector,
            inputs,
            execution_options: ExecutionOptions {
                parallel: parallel || max_concurrency.is_some(),
                max_concurrency,
                continue_on_error,
            },
        })
        .await
        .map_err(hen_error_to_mcp)?;

        json_result(report::run_outcome_json(
            &outcome,
            BodyReportOptions {
                include_body,
                max_body_chars,
            },
        ))
    }

    fn verify_hen_syntax(&self, args: &Map<String, Value>) -> Result<CallToolResult, ErrorData> {
        let path = optional_string(args.get("path"))?;
        let source = optional_string(args.get("source"))?;
        let working_directory = optional_string(args.get("workingDirectory"))?;

        let result = match (path, source) {
            (Some(path), None) => automation::verify_path(self.resolve_path(&path)),
            (None, Some(source)) => {
                let working_directory = working_directory
                    .map(|value| self.resolve_path(&value))
                    .unwrap_or_else(|| self.root.clone());
                automation::verify_source(source, working_directory)
            }
            (Some(_), Some(_)) => {
                return Err(invalid_params(
                    "Provide either 'path' or 'source', but not both.",
                ));
            }
            (None, None) => {
                return Err(invalid_params(
                    "Provide either 'path' or 'source' to verify Hen syntax.",
                ));
            }
        }
        .map_err(hen_error_to_mcp)?;

        json_result(report::verification_result_json(&result))
    }

    fn get_hen_authoring_guide(
        &self,
        args: &Map<String, Value>,
    ) -> Result<CallToolResult, ErrorData> {
        let topic = optional_string(args.get("topic"))?;
        let guide = match topic.as_deref() {
            None => format!(
                "{}\n\n---\n\n{}",
                SYNTAX_REFERENCE.trim(),
                README_GUIDE.trim()
            ),
            Some("syntax") => SYNTAX_REFERENCE.to_string(),
            Some("usage") => README_GUIDE.to_string(),
            Some(other) => {
                return Err(invalid_params(format!(
                    "Unknown topic '{}'. Use 'syntax' or 'usage'.",
                    other
                )));
            }
        };

        Ok(CallToolResult::success(vec![Content::text(guide)]))
    }

    fn resolve_path(&self, raw: &str) -> PathBuf {
        let path = PathBuf::from(raw);
        if path.is_absolute() {
            path
        } else {
            self.root.join(path)
        }
    }
}

pub async fn run_stdio_server(root: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
    let service = HenMcpServer::new(root).serve(stdio()).await?;
    service.waiting().await?;
    Ok(())
}

fn required_string(args: &Map<String, Value>, field: &str) -> Result<String, ErrorData> {
    match args.get(field).and_then(Value::as_str) {
        Some(value) if !value.trim().is_empty() => Ok(value.to_string()),
        _ => Err(invalid_params(format!("Field '{}' is required.", field))),
    }
}

fn optional_string(value: Option<&Value>) -> Result<Option<String>, ErrorData> {
    match value {
        None | Some(Value::Null) => Ok(None),
        Some(Value::String(text)) => Ok(Some(text.clone())),
        Some(_) => Err(invalid_params("Expected a string value.")),
    }
}

fn optional_selector(value: Option<&Value>) -> Result<Option<String>, ErrorData> {
    match value {
        None | Some(Value::Null) => Ok(None),
        Some(Value::String(text)) => Ok(Some(text.clone())),
        Some(Value::Number(number)) => Ok(Some(number.to_string())),
        Some(_) => Err(invalid_params("Selector must be a string or integer.")),
    }
}

fn optional_bool(value: Option<&Value>) -> Option<bool> {
    value.and_then(Value::as_bool)
}

fn optional_usize(value: Option<&Value>) -> Result<Option<usize>, ErrorData> {
    match value {
        None | Some(Value::Null) => Ok(None),
        Some(Value::Number(number)) => number
            .as_u64()
            .map(|value| value as usize)
            .map(Some)
            .ok_or_else(|| invalid_params("Expected a positive integer value.")),
        Some(_) => Err(invalid_params("Expected a positive integer value.")),
    }
}

fn optional_string_map(
    value: Option<&Value>,
) -> Result<std::collections::HashMap<String, String>, ErrorData> {
    match value {
        None | Some(Value::Null) => Ok(std::collections::HashMap::new()),
        Some(Value::Object(map)) => {
            let mut values = std::collections::HashMap::new();
            for (key, value) in map {
                let value = value.as_str().ok_or_else(|| {
                    invalid_params(format!("inputs.{} must be a string value.", key))
                })?;
                values.insert(key.clone(), value.to_string());
            }
            Ok(values)
        }
        Some(_) => Err(invalid_params("Expected an object with string values.")),
    }
}

fn json_result(value: Value) -> Result<CallToolResult, ErrorData> {
    Ok(CallToolResult::structured(value))
}

fn invalid_params(message: impl Into<String>) -> ErrorData {
    ErrorData::new(ErrorCode::INVALID_PARAMS, message.into(), None)
}

fn hen_error_to_mcp(error: HenError) -> ErrorData {
    let code = match error.kind() {
        HenErrorKind::Input | HenErrorKind::Prompt | HenErrorKind::Parse => {
            ErrorCode::INVALID_PARAMS
        }
        _ => ErrorCode::INTERNAL_ERROR,
    };

    ErrorData::new(code, error.to_string(), None)
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn hen_error_to_mcp_marks_input_errors_as_invalid_params() {
        let error = HenError::new(HenErrorKind::Input, "bad input");

        let mapped = hen_error_to_mcp(error);

        assert_eq!(mapped.code, ErrorCode::INVALID_PARAMS);
    }

    #[test]
    fn hen_error_to_mcp_marks_execution_errors_as_internal() {
        let error = HenError::new(HenErrorKind::Execution, "boom");

        let mapped = hen_error_to_mcp(error);

        assert_eq!(mapped.code, ErrorCode::INTERNAL_ERROR);
    }
}
