use rmcp::{ErrorData, ServerHandler};
use serde_json::Value;

use crate::tools;
use crate::tools::common;

#[derive(Clone, Default)]
pub struct EnvsealMcpServer;

impl EnvsealMcpServer {
    pub fn new() -> Self {
        Self
    }
}

fn enforce_json_bounds(value: &Value, field_path: &str) -> Result<(), ErrorData> {
    match value {
        Value::String(s) => {
            if s.len() > common::MAX_STRING_LENGTH {
                return Err(ErrorData::invalid_params(
                    format!(
                        "{field_path} exceeds maximum length of {} bytes",
                        common::MAX_STRING_LENGTH
                    ),
                    None,
                ));
            }
        }
        Value::Array(arr) => {
            if arr.len() > common::MAX_COMMAND_ARGS {
                return Err(ErrorData::invalid_params(
                    format!(
                        "{field_path} exceeds maximum array length of {}",
                        common::MAX_COMMAND_ARGS
                    ),
                    None,
                ));
            }
            for (i, item) in arr.iter().enumerate() {
                enforce_json_bounds(item, &format!("{field_path}[{i}]"))?;
            }
        }
        Value::Object(obj) => {
            for (k, v) in obj {
                enforce_json_bounds(v, &format!("{field_path}.{k}"))?;
            }
        }
        _ => {}
    }
    Ok(())
}

fn parse_params<T: serde::de::DeserializeOwned>(
    params: Option<rmcp::model::JsonObject>,
) -> Result<T, ErrorData> {
    let params = params.ok_or_else(|| ErrorData::invalid_params("missing tool arguments", None))?;
    let value = Value::Object(params);
    enforce_json_bounds(&value, "params")?;
    serde_json::from_value(value)
        .map_err(|e| ErrorData::invalid_params(format!("invalid arguments: {e}"), None))
}

fn internal_error(msg: impl Into<String>) -> ErrorData {
    ErrorData::internal_error(msg.into(), None)
}

fn tool_schema(
    required: &[&str],
    properties: serde_json::Map<String, Value>,
) -> rmcp::model::JsonObject {
    rmcp::model::object(serde_json::json!({
        "type": "object",
        "additionalProperties": false,
        "required": required,
        "properties": properties,
    }))
}

fn string_prop(min_len: usize, description: &str) -> Value {
    serde_json::json!({
        "type": "string",
        "minLength": min_len,
        "description": description,
    })
}

fn command_prop() -> Value {
    serde_json::json!({
        "type": "array",
        "description": "Command and args. First element is executable path/name.",
        "minItems": 1,
        "items": {
            "type": "string",
            "minLength": 1
        }
    })
}

#[allow(clippy::too_many_lines)]
impl ServerHandler for EnvsealMcpServer {
    fn get_info(&self) -> rmcp::model::ServerInfo {
        let mut info = rmcp::model::ServerInfo::default();
        info.server_info.name = "envseal".to_string();
        info.server_info.version = env!("CARGO_PKG_VERSION").to_string();
        info.instructions = Some(
            "Secure secret management for AI agents. Store, inject, and manage API keys."
                .to_string(),
        );
        info.capabilities.tools = Some(rmcp::model::ToolsCapability {
            list_changed: Some(false),
        });
        info
    }

    async fn list_tools(
        &self,
        _request: Option<rmcp::model::PaginatedRequestParams>,
        _context: rmcp::service::RequestContext<rmcp::RoleServer>,
    ) -> Result<rmcp::model::ListToolsResult, ErrorData> {
        use rmcp::model::Tool;
        let mk = |name: &str, description: &str, schema: rmcp::model::JsonObject| {
            Tool::new(name.to_string(), description.to_string(), schema)
        };

        let tools = vec![
            mk(
                "envseal_list",
                "List all stored secret names",
                tool_schema(
                    &[],
                    serde_json::Map::from_iter([(
                        "filter".to_string(),
                        string_prop(1, "Case-insensitive substring filter."),
                    )]),
                ),
            ),
            mk(
                "envseal_store",
                "Store a secret in the vault",
                tool_schema(
                    &["name", "value"],
                    serde_json::Map::from_iter([
                        ("name".to_string(), string_prop(1, "Secret identifier.")),
                        (
                            "value".to_string(),
                            string_prop(1, "Secret plaintext value."),
                        ),
                        (
                            "force".to_string(),
                            serde_json::json!({"type":"boolean","default":false}),
                        ),
                    ]),
                ),
            ),
            mk(
                "envseal_revoke",
                "Delete a secret from the vault",
                tool_schema(
                    &["name"],
                    serde_json::Map::from_iter([(
                        "name".to_string(),
                        string_prop(1, "Secret identifier to revoke."),
                    )]),
                ),
            ),
            mk(
                "envseal_copy",
                "Copy a secret to a new name",
                tool_schema(
                    &["from", "to"],
                    serde_json::Map::from_iter([
                        (
                            "from".to_string(),
                            string_prop(1, "Source secret identifier."),
                        ),
                        (
                            "to".to_string(),
                            string_prop(1, "Destination secret identifier."),
                        ),
                        (
                            "force".to_string(),
                            serde_json::json!({"type":"boolean","default":false}),
                        ),
                    ]),
                ),
            ),
            mk(
                "envseal_rename",
                "Rename a secret",
                tool_schema(
                    &["from", "to"],
                    serde_json::Map::from_iter([
                        (
                            "from".to_string(),
                            string_prop(1, "Current secret identifier."),
                        ),
                        ("to".to_string(), string_prop(1, "New secret identifier.")),
                        (
                            "force".to_string(),
                            serde_json::json!({"type":"boolean","default":false}),
                        ),
                    ]),
                ),
            ),
            mk(
                "envseal_rotate",
                "Rotate a secret value",
                tool_schema(
                    &["name"],
                    serde_json::Map::from_iter([(
                        "name".to_string(),
                        string_prop(1, "Secret identifier to rotate."),
                    )]),
                ),
            ),
            mk(
                "envseal_inject",
                "Inject secrets into a child process",
                tool_schema(
                    &["mappings", "command"],
                    serde_json::Map::from_iter([
                        (
                            "mappings".to_string(),
                            serde_json::json!({
                                "type":"array",
                                "minItems": 1,
                                "items": {
                                    "type": "object",
                                    "additionalProperties": false,
                                    "required": ["secret", "env_var"],
                                    "properties": {
                                        "secret": {"type":"string","minLength":1},
                                        "env_var": {"type":"string","minLength":1}
                                    }
                                }
                            }),
                        ),
                        ("command".to_string(), command_prop()),
                    ]),
                ),
            ),
            mk(
                "envseal_inject_file",
                "Inject secrets from a .envseal file",
                tool_schema(
                    &["file", "command"],
                    serde_json::Map::from_iter([
                        (
                            "file".to_string(),
                            string_prop(1, "Path to .envseal mapping file."),
                        ),
                        ("command".to_string(), command_prop()),
                    ]),
                ),
            ),
            mk(
                "envseal_run",
                "Run command with discovered .envseal mappings",
                tool_schema(
                    &["command"],
                    serde_json::Map::from_iter([("command".to_string(), command_prop())]),
                ),
            ),
            mk(
                "envseal_pipe",
                "Pipe a secret to stdin of a process",
                tool_schema(
                    &["secret_name", "command"],
                    serde_json::Map::from_iter([
                        (
                            "secret_name".to_string(),
                            string_prop(1, "Secret identifier."),
                        ),
                        ("command".to_string(), command_prop()),
                    ]),
                ),
            ),
            mk(
                "envseal_supervised",
                "Run with runtime leak supervision",
                tool_schema(
                    &["secret_name", "env_var", "command"],
                    serde_json::Map::from_iter([
                        (
                            "secret_name".to_string(),
                            string_prop(1, "Secret identifier."),
                        ),
                        (
                            "env_var".to_string(),
                            string_prop(1, "Destination environment variable."),
                        ),
                        ("command".to_string(), command_prop()),
                        (
                            "sandbox".to_string(),
                            serde_json::json!({
                                "type":"string",
                                "enum": ["standard", "hardened", "lockdown"],
                                "description": "Sandbox profile. Defaults to standard."
                            }),
                        ),
                    ]),
                ),
            ),
            mk(
                "envseal_import",
                "Import secrets from a .env file",
                tool_schema(
                    &["env_file"],
                    serde_json::Map::from_iter([
                        (
                            "env_file".to_string(),
                            string_prop(1, "Path to source .env file."),
                        ),
                        (
                            "delete_after".to_string(),
                            serde_json::json!({"type":"boolean","default":false}),
                        ),
                    ]),
                ),
            ),
            mk(
                "envseal_check",
                "Validate .envseal against vault",
                tool_schema(
                    &[],
                    serde_json::Map::from_iter([(
                        "file".to_string(),
                        string_prop(1, "Path to .envseal file (defaults to .envseal)."),
                    )]),
                ),
            ),
            mk(
                "envseal_status",
                "Vault status",
                tool_schema(&[], serde_json::Map::new()),
            ),
            mk(
                "envseal_env",
                "Dry-run env mappings",
                tool_schema(
                    &[],
                    serde_json::Map::from_iter([(
                        "file".to_string(),
                        string_prop(1, "Optional explicit .envseal path."),
                    )]),
                ),
            ),
            mk(
                "envseal_health",
                "Secret health report",
                tool_schema(
                    &[],
                    serde_json::Map::from_iter([(
                        "max_age_days".to_string(),
                        serde_json::json!({
                            "type":"integer",
                            "minimum": 1,
                            "maximum": 3650
                        }),
                    )]),
                ),
            ),
            mk(
                "envseal_policy_show",
                "Show policy rules",
                tool_schema(&[], serde_json::Map::new()),
            ),
            mk(
                "envseal_policy_revoke_binary",
                "Revoke binary policy rules",
                tool_schema(
                    &["binary_path"],
                    serde_json::Map::from_iter([(
                        "binary_path".to_string(),
                        string_prop(1, "Absolute or relative binary path."),
                    )]),
                ),
            ),
            mk(
                "envseal_request_key",
                "Request secret via GUI popup",
                tool_schema(
                    &["key_name"],
                    serde_json::Map::from_iter([
                        ("key_name".to_string(), string_prop(1, "Secret identifier.")),
                        (
                            "description".to_string(),
                            string_prop(0, "Optional operator guidance text."),
                        ),
                    ]),
                ),
            ),
            mk(
                "envseal_doctor",
                "System security diagnostics",
                tool_schema(&[], serde_json::Map::new()),
            ),
            mk(
                "envseal_audit",
                "Read audit log entries",
                tool_schema(
                    &[],
                    serde_json::Map::from_iter([
                        (
                            "limit".to_string(),
                            serde_json::json!({
                                "type":"integer",
                                "minimum": 1,
                                "maximum": 500
                            }),
                        ),
                        (
                            "query".to_string(),
                            serde_json::json!({
                                "type": "string",
                                "description": "Case-insensitive substring filter across all fields."
                            }),
                        ),
                        (
                            "event_type".to_string(),
                            serde_json::json!({
                                "type": "string",
                                "description": "Filter by event type (e.g. secret_stored, approval_result, signal_recorded, secret_revoked, secret_accessed, tier_changed, supervisor_leak_detected)."
                            }),
                        ),
                    ]),
                ),
            ),
        ];
        Ok(rmcp::model::ListToolsResult {
            tools,
            next_cursor: None,
            meta: None,
        })
    }

    async fn call_tool(
        &self,
        request: rmcp::model::CallToolRequestParams,
        _context: rmcp::service::RequestContext<rmcp::RoleServer>,
    ) -> Result<rmcp::model::CallToolResult, ErrorData> {
        use rmcp::model::Content;

        let tool_name: &str = request.name.as_ref();
        let out = match tool_name {
            "envseal_list" => tools::list::execute(parse_params(request.arguments)?).await,
            "envseal_store" => tools::store::execute(parse_params(request.arguments)?).await,
            "envseal_revoke" => tools::revoke::execute(parse_params(request.arguments)?).await,
            "envseal_copy" => tools::copy::execute(parse_params(request.arguments)?).await,
            "envseal_rename" => tools::rename::execute(parse_params(request.arguments)?).await,
            "envseal_rotate" => tools::rotate::execute(parse_params(request.arguments)?).await,
            "envseal_inject" => tools::inject::execute(parse_params(request.arguments)?).await,
            "envseal_inject_file" => {
                tools::inject_file::execute(parse_params(request.arguments)?).await
            }
            "envseal_run" => tools::run::execute(parse_params(request.arguments)?).await,
            "envseal_pipe" => tools::pipe::execute(parse_params(request.arguments)?).await,
            "envseal_supervised" => {
                tools::supervised::execute(parse_params(request.arguments)?).await
            }
            "envseal_import" => tools::import::execute(parse_params(request.arguments)?).await,
            "envseal_check" => tools::check::execute(parse_params(request.arguments)?).await,
            "envseal_status" => tools::status::execute(parse_params(request.arguments)?).await,
            "envseal_env" => tools::env::execute(parse_params(request.arguments)?).await,
            "envseal_health" => tools::health::execute(parse_params(request.arguments)?).await,
            "envseal_policy_show" => tools::policy::show(parse_params(request.arguments)?).await,
            "envseal_policy_revoke_binary" => {
                tools::policy::revoke_binary(parse_params(request.arguments)?).await
            }
            "envseal_request_key" => {
                tools::request_key::execute(parse_params(request.arguments)?).await
            }
            "envseal_doctor" => tools::doctor::execute(parse_params(request.arguments)?).await,
            "envseal_audit" => tools::audit::execute(parse_params(request.arguments)?).await,
            unknown => {
                return Err(ErrorData::invalid_params(
                    format!("unknown tool: {unknown}"),
                    None,
                ))
            }
        };

        match out {
            Ok(out) => Ok(rmcp::model::CallToolResult {
                content: vec![Content::text(
                    serde_json::to_string_pretty(&out)
                        .map_err(|e| internal_error(e.to_string()))?,
                )],
                structured_content: Some(out),
                is_error: Some(false),
                meta: None,
            }),
            Err(msg) => {
                let err_payload = serde_json::json!({
                    "error": {
                        "code": "TOOL_EXECUTION_FAILED",
                        "message": msg,
                    }
                });
                Ok(rmcp::model::CallToolResult {
                    content: vec![Content::text(
                        serde_json::to_string_pretty(&err_payload)
                            .map_err(|e| internal_error(e.to_string()))?,
                    )],
                    structured_content: Some(err_payload),
                    is_error: Some(true),
                    meta: None,
                })
            }
        }
    }
}
