use std::collections::HashMap;

use crate::ai::{ParameterProperty, Tool, ToolParameters};
use crate::mcp::models::McpTool;

/// Sanitize a string for use as part of a tool key:
/// lowercase → non-alphanumeric to `_` → collapse consecutive `_` → trim `_`.
pub fn sanitize_name(s: &str) -> String {
    let lower = s.to_lowercase();
    let replaced: String = lower
        .chars()
        .map(|c| if c.is_alphanumeric() { c } else { '_' })
        .collect();

    // Collapse consecutive underscores
    let mut collapsed = String::with_capacity(replaced.len());
    let mut prev_underscore = false;
    for c in replaced.chars() {
        if c == '_' {
            if !prev_underscore {
                collapsed.push(c);
            }
            prev_underscore = true;
        } else {
            collapsed.push(c);
            prev_underscore = false;
        }
    }

    // Trim leading/trailing underscores
    collapsed.trim_matches('_').to_string()
}

/// Build a unique, AI-safe tool key: `mcp_{server_name}_{tool_name}`.
pub fn build_tool_key(server_name: &str, tool_name: &str) -> String {
    format!(
        "mcp_{}_{}",
        sanitize_name(server_name),
        sanitize_name(tool_name)
    )
}

/// Convert stored McpTool records into AI Tool definitions.
pub fn mcp_tools_to_ai_tools(tools: &[McpTool]) -> Vec<Tool> {
    tools
        .iter()
        .map(|t| {
            let parameters = parse_parameters(&t.parameters);
            Tool {
                name: t.tool_key.clone(),
                description: t
                    .description
                    .clone()
                    .unwrap_or_else(|| format!("MCP tool: {}", t.name)),
                parameters,
            }
        })
        .collect()
}

/// Parse a JSON schema string into AI ToolParameters.
/// Falls back to an empty object schema on any parse error.
fn parse_parameters(schema_json: &str) -> ToolParameters {
    let value: serde_json::Value = serde_json::from_str(schema_json).unwrap_or_default();

    let properties = value
        .get("properties")
        .and_then(|p| p.as_object())
        .map(|obj| {
            obj.iter()
                .map(|(k, v)| {
                    let prop_type = v
                        .get("type")
                        .and_then(|t| t.as_str())
                        .unwrap_or("string")
                        .to_string();
                    let description = v
                        .get("description")
                        .and_then(|d| d.as_str())
                        .unwrap_or("")
                        .to_string();
                    (
                        k.clone(),
                        ParameterProperty {
                            prop_type,
                            description,
                            enum_values: None,
                        },
                    )
                })
                .collect::<HashMap<_, _>>()
        })
        .unwrap_or_default();

    let required = value
        .get("required")
        .and_then(|r| r.as_array())
        .map(|arr| {
            arr.iter()
                .filter_map(|v| v.as_str().map(|s| s.to_string()))
                .collect()
        })
        .unwrap_or_default();

    ToolParameters {
        param_type: "object".to_string(),
        properties,
        required,
    }
}

/// Async wrapper — fetch enabled MCP tools from state and convert to AI tools.
pub async fn get_enabled_mcp_tools(state: &crate::state::AppState) -> Result<Vec<Tool>, String> {
    let tool_records = {
        let db = state.db.lock().map_err(|e| e.to_string())?;
        crate::mcp::store::get_enabled_tools(&db)?
    };

    let tools = tool_records
        .iter()
        .map(|(t, _url)| {
            let parameters = parse_parameters(&t.parameters);
            Tool {
                name: t.tool_key.clone(),
                description: t
                    .description
                    .clone()
                    .unwrap_or_else(|| format!("MCP tool: {}", t.name)),
                parameters,
            }
        })
        .collect();

    Ok(tools)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::mcp::models::McpTool;

    #[test]
    fn test_tool_name_sanitization() {
        assert_eq!(sanitize_name("My Weather API"), "my_weather_api");
        assert_eq!(sanitize_name("get_forecast"), "get_forecast");
        assert_eq!(sanitize_name("foo--bar"), "foo_bar");
        assert_eq!(sanitize_name("  leading trailing  "), "leading_trailing");
        assert_eq!(sanitize_name("CamelCase"), "camelcase");
        assert_eq!(sanitize_name("v1.0.0"), "v1_0_0");
        assert_eq!(sanitize_name("___underscores___"), "underscores");
        assert_eq!(sanitize_name("hello world"), "hello_world");
    }

    #[test]
    fn test_build_tool_key() {
        assert_eq!(
            build_tool_key("My Weather API", "get_forecast"),
            "mcp_my_weather_api_get_forecast"
        );
        assert_eq!(build_tool_key("simple", "ping"), "mcp_simple_ping");
        assert_eq!(
            build_tool_key("My Server", "search files"),
            "mcp_my_server_search_files"
        );
    }

    #[test]
    fn test_mcp_tool_to_ai_tool_conversion() {
        let tool = McpTool {
            id: "1".to_string(),
            server_id: "srv".to_string(),
            name: "echo".to_string(),
            tool_key: "mcp_test_echo".to_string(),
            description: Some("Echoes text back".to_string()),
            parameters: r#"{
                "type": "object",
                "properties": {
                    "message": { "type": "string", "description": "The text to echo" }
                },
                "required": ["message"]
            }"#
            .to_string(),
        };

        let ai_tools = mcp_tools_to_ai_tools(&[tool]);
        assert_eq!(ai_tools.len(), 1);

        let ai_tool = &ai_tools[0];
        assert_eq!(ai_tool.name, "mcp_test_echo");
        assert_eq!(ai_tool.description, "Echoes text back");
        assert_eq!(ai_tool.parameters.param_type, "object");
        assert!(ai_tool.parameters.properties.contains_key("message"));
        assert_eq!(ai_tool.parameters.required, vec!["message".to_string()]);

        let msg_prop = &ai_tool.parameters.properties["message"];
        assert_eq!(msg_prop.prop_type, "string");
        assert_eq!(msg_prop.description, "The text to echo");
    }

    #[test]
    fn test_mcp_tool_missing_description_uses_fallback() {
        let tool = McpTool {
            id: "2".to_string(),
            server_id: "srv".to_string(),
            name: "ping".to_string(),
            tool_key: "mcp_test_ping".to_string(),
            description: None,
            parameters: "{}".to_string(),
        };

        let ai_tools = mcp_tools_to_ai_tools(&[tool]);
        assert_eq!(ai_tools[0].description, "MCP tool: ping");
    }

    #[test]
    fn test_parse_parameters_malformed_json() {
        let params = parse_parameters("{invalid json");
        assert_eq!(params.param_type, "object");
        assert!(params.properties.is_empty());
        assert!(params.required.is_empty());
    }
}
