//! Tool Executor & Registry — Phase-83 T83.1
//!
//! Provides the `ToolExecutor` trait and `ToolRegistry` for registering,
//! discovering, and executing tools that LLMs can call via Function Calling.
//!
//! # Architecture
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────┐
//! │                    ToolRegistry                         │
//! │  ┌───────────┐ ┌────────────┐ ┌──────────────────────┐ │
//! │  │vector_search│ │graph_query │ │  sql_query  │ ...   │ │
//! │  │ToolExecutor │ │ToolExecutor│ │ToolExecutor │       │ │
//! │  └──────┬──────┘ └─────┬──────┘ └──────┬──────┘       │ │
//! │         └──────────────┴───────────────┘               │ │
//! │                        │                               │ │
//! │              ToolRegistry::execute()                   │ │
//! │         ┌──────────────┴───────────────┐               │ │
//! │   LLM ToolCall ──► find executor ──► ToolResult        │ │
//! └─────────────────────────────────────────────────────────┘
//! ```
//!
//! # Usage
//!
//! ```rust,ignore
//! use aifastdb_core::llm::tool_executor::{ToolRegistry, ToolExecutor, ToolResult};
//!
//! let registry = ToolRegistry::new();
//! registry.register(MyVectorSearchTool::new(store));
//!
//! // Get tool definitions for LLM
//! let definitions = registry.tool_definitions();
//!
//! // Execute a tool call from LLM
//! let result = registry.execute("db_vector_search", r#"{"query":"hello","top_k":5}"#).await;
//! ```

use std::collections::HashMap;
use std::sync::Arc;

use async_trait::async_trait;
use dashmap::DashMap;
use serde::{Deserialize, Serialize};

use super::types::{FunctionDefinition, ToolDefinition};
use super::error::{LlmError, LlmResult};

// ============================================================================
// ToolResult
// ============================================================================

/// Result of a tool execution.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolResult {
    /// Whether the tool execution succeeded.
    pub success: bool,

    /// The output of the tool (JSON-formatted string for the LLM to consume).
    pub output: String,

    /// Error message if the tool failed.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub error: Option<String>,

    /// Execution duration in milliseconds.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub duration_ms: Option<u64>,

    /// Optional structured metadata about the execution.
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub metadata: HashMap<String, serde_json::Value>,
}

impl ToolResult {
    /// Create a successful tool result.
    pub fn ok(output: impl Into<String>) -> Self {
        Self {
            success: true,
            output: output.into(),
            error: None,
            duration_ms: None,
            metadata: HashMap::new(),
        }
    }

    /// Create a successful tool result from a serializable value.
    pub fn ok_json<T: Serialize>(value: &T) -> Self {
        match serde_json::to_string_pretty(value) {
            Ok(json) => Self::ok(json),
            Err(e) => Self::err(format!("Failed to serialize result: {}", e)),
        }
    }

    /// Create an error tool result.
    pub fn err(message: impl Into<String>) -> Self {
        let msg = message.into();
        Self {
            success: false,
            output: String::new(),
            error: Some(msg),
            duration_ms: None,
            metadata: HashMap::new(),
        }
    }

    /// Set execution duration.
    pub fn with_duration(mut self, ms: u64) -> Self {
        self.duration_ms = Some(ms);
        self
    }

    /// Add metadata.
    pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
        self.metadata.insert(key.into(), value);
        self
    }

    /// Get the content to send back to the LLM.
    ///
    /// For success: returns the output.
    /// For error: returns a formatted error message.
    pub fn to_llm_content(&self) -> String {
        if self.success {
            self.output.clone()
        } else {
            format!(
                "Tool execution failed: {}",
                self.error.as_deref().unwrap_or("unknown error")
            )
        }
    }
}

// ============================================================================
// ToolExecutor trait
// ============================================================================

/// Metadata about a tool for registration and discovery.
#[derive(Debug, Clone)]
pub struct ToolMetadata {
    /// Unique tool name (e.g., "db_vector_search").
    pub name: String,

    /// Human-readable description for the LLM.
    pub description: String,

    /// JSON Schema for the tool's parameters.
    pub parameters_schema: serde_json::Value,

    /// Category for grouping tools (e.g., "database", "search", "graph").
    pub category: String,

    /// Whether this tool performs read-only operations (safe for sandboxing).
    pub is_read_only: bool,
}

impl ToolMetadata {
    /// Create a new tool metadata.
    pub fn new(
        name: impl Into<String>,
        description: impl Into<String>,
        parameters_schema: serde_json::Value,
    ) -> Self {
        Self {
            name: name.into(),
            description: description.into(),
            parameters_schema,
            category: "general".to_string(),
            is_read_only: true,
        }
    }

    /// Set the category.
    pub fn with_category(mut self, category: impl Into<String>) -> Self {
        self.category = category.into();
        self
    }

    /// Mark as read-write (not read-only).
    pub fn read_write(mut self) -> Self {
        self.is_read_only = false;
        self
    }

    /// Convert to LLM Gateway `ToolDefinition` for inclusion in ChatConfig.
    pub fn to_tool_definition(&self) -> ToolDefinition {
        ToolDefinition {
            tool_type: "function".to_string(),
            function: FunctionDefinition {
                name: self.name.clone(),
                description: Some(self.description.clone()),
                parameters: self.parameters_schema.clone(),
            },
        }
    }
}

/// Trait for implementing a tool that an LLM can call.
///
/// Each tool provides:
/// - Metadata (name, description, parameter schema) for the LLM
/// - An async `execute` method that runs the tool with given arguments
///
/// # Example
///
/// ```rust,ignore
/// struct VectorSearchTool { store: Arc<CollectionStore> }
///
/// #[async_trait]
/// impl ToolExecutor for VectorSearchTool {
///     fn metadata(&self) -> ToolMetadata {
///         ToolMetadata::new(
///             "db_vector_search",
///             "Search for similar documents using vector similarity",
///             json!({
///                 "type": "object",
///                 "properties": {
///                     "collection": { "type": "string", "description": "Collection name" },
///                     "query": { "type": "string", "description": "Search query text" },
///                     "top_k": { "type": "integer", "description": "Number of results", "default": 5 }
///                 },
///                 "required": ["collection", "query"]
///             }),
///         ).with_category("search")
///     }
///
///     async fn execute(&self, arguments: &str) -> LlmResult<ToolResult> {
///         let args: serde_json::Value = serde_json::from_str(arguments)?;
///         // ... execute search ...
///         Ok(ToolResult::ok_json(&results))
///     }
/// }
/// ```
#[async_trait]
pub trait ToolExecutor: Send + Sync {
    /// Get metadata about this tool (name, description, schema).
    fn metadata(&self) -> ToolMetadata;

    /// Execute the tool with the given JSON arguments string.
    ///
    /// # Arguments
    /// * `arguments` - JSON string of arguments (from LLM's ToolCall.arguments)
    ///
    /// # Returns
    /// A `ToolResult` containing the output or error.
    async fn execute(&self, arguments: &str) -> LlmResult<ToolResult>;
}

// ============================================================================
// ToolRegistry
// ============================================================================

/// Registry that manages available tools for LLM function calling.
///
/// Thread-safe: uses `DashMap` for concurrent access.
///
/// # Features
/// - Register/unregister tools at runtime
/// - List available tools (optionally filtered by category or read-only status)
/// - Execute tools by name
/// - Generate `ToolDefinition` list for LLM `ChatConfig`
/// - Enable/disable individual tools without unregistering
pub struct ToolRegistry {
    /// Registered tools (name → executor).
    tools: DashMap<String, Arc<dyn ToolExecutor>>,

    /// Disabled tools (tools registered but temporarily disabled).
    disabled: DashMap<String, bool>,

    /// Maximum number of tool call rounds allowed per conversation turn.
    max_tool_rounds: usize,

    /// Arbitrary context data (e.g., schema info) shared across tools.
    context: DashMap<String, serde_json::Value>,
}

impl std::fmt::Debug for ToolRegistry {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ToolRegistry")
            .field("tool_count", &self.tools.len())
            .field("disabled_count", &self.disabled.len())
            .field("max_tool_rounds", &self.max_tool_rounds)
            .finish()
    }
}

impl Default for ToolRegistry {
    fn default() -> Self {
        Self::new()
    }
}

impl ToolRegistry {
    /// Create a new empty registry.
    pub fn new() -> Self {
        Self {
            tools: DashMap::new(),
            disabled: DashMap::new(),
            max_tool_rounds: 10,
            context: DashMap::new(),
        }
    }

    /// Set the maximum number of tool call rounds per conversation turn.
    pub fn with_max_rounds(mut self, max: usize) -> Self {
        self.max_tool_rounds = max;
        self
    }

    /// Get the maximum tool call rounds.
    pub fn max_tool_rounds(&self) -> usize {
        self.max_tool_rounds
    }

    /// Register a tool executor.
    ///
    /// If a tool with the same name already exists, it is replaced.
    pub fn register(&self, tool: impl ToolExecutor + 'static) {
        let name = tool.metadata().name.clone();
        self.tools.insert(name, Arc::new(tool));
    }

    /// Register a shared tool executor.
    pub fn register_arc(&self, tool: Arc<dyn ToolExecutor>) {
        let name = tool.metadata().name.clone();
        self.tools.insert(name, tool);
    }

    /// Unregister a tool by name.
    pub fn unregister(&self, name: &str) -> bool {
        self.disabled.remove(name);
        self.tools.remove(name).is_some()
    }

    /// Enable a previously disabled tool.
    pub fn enable(&self, name: &str) {
        self.disabled.remove(name);
    }

    /// Disable a tool (keeps it registered but won't appear in definitions or execute).
    pub fn disable(&self, name: &str) {
        if self.tools.contains_key(name) {
            self.disabled.insert(name.to_string(), true);
        }
    }

    /// Check if a tool is registered and enabled.
    pub fn is_available(&self, name: &str) -> bool {
        self.tools.contains_key(name) && !self.disabled.contains_key(name)
    }

    /// Get the number of registered tools.
    pub fn tool_count(&self) -> usize {
        self.tools.len()
    }

    /// Get the number of enabled tools.
    pub fn enabled_count(&self) -> usize {
        self.tools.len() - self.disabled.len()
    }

    /// List all registered tool names.
    pub fn list_tool_names(&self) -> Vec<String> {
        self.tools
            .iter()
            .map(|entry| entry.key().clone())
            .collect()
    }

    /// List metadata of all enabled tools.
    pub fn list_tools(&self) -> Vec<ToolMetadata> {
        self.tools
            .iter()
            .filter(|entry| !self.disabled.contains_key(entry.key()))
            .map(|entry| entry.value().metadata())
            .collect()
    }

    /// List metadata filtered by category.
    pub fn list_tools_by_category(&self, category: &str) -> Vec<ToolMetadata> {
        self.tools
            .iter()
            .filter(|entry| !self.disabled.contains_key(entry.key()))
            .map(|entry| entry.value().metadata())
            .filter(|m| m.category == category)
            .collect()
    }

    /// List only read-only tools.
    pub fn list_read_only_tools(&self) -> Vec<ToolMetadata> {
        self.tools
            .iter()
            .filter(|entry| !self.disabled.contains_key(entry.key()))
            .map(|entry| entry.value().metadata())
            .filter(|m| m.is_read_only)
            .collect()
    }

    /// Generate `ToolDefinition` list for inclusion in `ChatConfig.tools`.
    ///
    /// Only enabled tools are included.
    pub fn tool_definitions(&self) -> Vec<ToolDefinition> {
        self.list_tools()
            .iter()
            .map(|m| m.to_tool_definition())
            .collect()
    }

    /// Generate `ToolDefinition` list for a specific subset of tools.
    ///
    /// Only enabled tools with names in `names` are included.
    pub fn tool_definitions_for(&self, names: &[&str]) -> Vec<ToolDefinition> {
        self.tools
            .iter()
            .filter(|entry| {
                names.contains(&entry.key().as_str())
                    && !self.disabled.contains_key(entry.key())
            })
            .map(|entry| entry.value().metadata().to_tool_definition())
            .collect()
    }

    /// Execute a tool by name with the given arguments.
    ///
    /// # Arguments
    /// * `name` - Tool name (from `ToolCall.name`)
    /// * `arguments` - JSON arguments string (from `ToolCall.arguments`)
    ///
    /// # Errors
    /// - `LlmError::NotFound` if the tool is not registered or is disabled
    /// - Tool-specific errors wrapped in `ToolResult`
    pub async fn execute(&self, name: &str, arguments: &str) -> LlmResult<ToolResult> {
        // Check disabled
        if self.disabled.contains_key(name) {
            return Err(LlmError::NotFound(format!(
                "Tool '{}' is currently disabled",
                name
            )));
        }

        // Find the executor
        let executor = self
            .tools
            .get(name)
            .map(|entry| Arc::clone(entry.value()))
            .ok_or_else(|| {
                LlmError::NotFound(format!("Tool '{}' is not registered", name))
            })?;

        // Execute with timing
        let start = std::time::Instant::now();
        let result = executor.execute(arguments).await;
        let duration_ms = start.elapsed().as_millis() as u64;

        match result {
            Ok(mut tool_result) => {
                tool_result.duration_ms = Some(duration_ms);
                Ok(tool_result)
            }
            Err(e) => {
                // Wrap the error in a ToolResult so the LLM can see it
                Ok(ToolResult::err(format!("{}", e)).with_duration(duration_ms))
            }
        }
    }

    /// Execute a `ToolCall` from an LLM response.
    ///
    /// Convenience wrapper over `execute()` that takes a `ToolCall` directly.
    pub async fn execute_tool_call(
        &self,
        tool_call: &super::types::ToolCall,
    ) -> LlmResult<ToolResult> {
        self.execute(&tool_call.name, &tool_call.arguments).await
    }

    /// Set a context value (e.g., data source schema).
    pub fn set_context(&self, key: impl Into<String>, value: serde_json::Value) {
        self.context.insert(key.into(), value);
    }

    /// Get a context value.
    pub fn get_context(&self, key: &str) -> Option<serde_json::Value> {
        self.context.get(key).map(|v| v.value().clone())
    }
}

// ============================================================================
// Tests
// ============================================================================

#[cfg(test)]
mod tests {
    use super::*;

    /// A simple echo tool for testing.
    struct EchoTool;

    #[async_trait]
    impl ToolExecutor for EchoTool {
        fn metadata(&self) -> ToolMetadata {
            ToolMetadata::new(
                "echo",
                "Echoes the input back",
                serde_json::json!({
                    "type": "object",
                    "properties": {
                        "message": { "type": "string" }
                    },
                    "required": ["message"]
                }),
            )
            .with_category("test")
        }

        async fn execute(&self, arguments: &str) -> LlmResult<ToolResult> {
            let args: serde_json::Value = serde_json::from_str(arguments)
                .map_err(|e| LlmError::ResponseParseError(e.to_string()))?;
            let msg = args["message"].as_str().unwrap_or("(empty)");
            Ok(ToolResult::ok(format!("Echo: {}", msg)))
        }
    }

    /// A calculator tool for testing.
    struct AddTool;

    #[async_trait]
    impl ToolExecutor for AddTool {
        fn metadata(&self) -> ToolMetadata {
            ToolMetadata::new(
                "add",
                "Adds two numbers",
                serde_json::json!({
                    "type": "object",
                    "properties": {
                        "a": { "type": "number" },
                        "b": { "type": "number" }
                    },
                    "required": ["a", "b"]
                }),
            )
            .with_category("math")
        }

        async fn execute(&self, arguments: &str) -> LlmResult<ToolResult> {
            let args: serde_json::Value = serde_json::from_str(arguments)
                .map_err(|e| LlmError::ResponseParseError(e.to_string()))?;
            let a = args["a"].as_f64().unwrap_or(0.0);
            let b = args["b"].as_f64().unwrap_or(0.0);
            Ok(ToolResult::ok_json(&serde_json::json!({ "result": a + b })))
        }
    }

    #[tokio::test]
    async fn test_registry_register_and_list() {
        let registry = ToolRegistry::new();
        assert_eq!(registry.tool_count(), 0);

        registry.register(EchoTool);
        registry.register(AddTool);

        assert_eq!(registry.tool_count(), 2);
        assert_eq!(registry.enabled_count(), 2);

        let names = registry.list_tool_names();
        assert!(names.contains(&"echo".to_string()));
        assert!(names.contains(&"add".to_string()));
    }

    #[tokio::test]
    async fn test_registry_execute_echo() {
        let registry = ToolRegistry::new();
        registry.register(EchoTool);

        let result = registry
            .execute("echo", r#"{"message":"hello world"}"#)
            .await
            .unwrap();

        assert!(result.success);
        assert_eq!(result.output, "Echo: hello world");
        assert!(result.duration_ms.is_some());
    }

    #[tokio::test]
    async fn test_registry_execute_add() {
        let registry = ToolRegistry::new();
        registry.register(AddTool);

        let result = registry
            .execute("add", r#"{"a":3,"b":4}"#)
            .await
            .unwrap();

        assert!(result.success);
        let parsed: serde_json::Value = serde_json::from_str(&result.output).unwrap();
        assert_eq!(parsed["result"], 7.0);
    }

    #[tokio::test]
    async fn test_registry_execute_not_found() {
        let registry = ToolRegistry::new();

        let err = registry
            .execute("nonexistent", "{}")
            .await
            .unwrap_err();

        assert!(matches!(err, LlmError::NotFound(_)));
    }

    #[tokio::test]
    async fn test_registry_disable_enable() {
        let registry = ToolRegistry::new();
        registry.register(EchoTool);

        assert!(registry.is_available("echo"));

        // Disable
        registry.disable("echo");
        assert!(!registry.is_available("echo"));
        assert_eq!(registry.enabled_count(), 0);

        // Execute should fail when disabled
        let err = registry.execute("echo", "{}").await.unwrap_err();
        assert!(matches!(err, LlmError::NotFound(_)));

        // Tool definitions should be empty
        assert!(registry.tool_definitions().is_empty());

        // Re-enable
        registry.enable("echo");
        assert!(registry.is_available("echo"));
        assert_eq!(registry.enabled_count(), 1);
    }

    #[tokio::test]
    async fn test_registry_unregister() {
        let registry = ToolRegistry::new();
        registry.register(EchoTool);
        assert_eq!(registry.tool_count(), 1);

        let removed = registry.unregister("echo");
        assert!(removed);
        assert_eq!(registry.tool_count(), 0);

        let removed_again = registry.unregister("echo");
        assert!(!removed_again);
    }

    #[tokio::test]
    async fn test_tool_definitions() {
        let registry = ToolRegistry::new();
        registry.register(EchoTool);
        registry.register(AddTool);

        let defs = registry.tool_definitions();
        assert_eq!(defs.len(), 2);

        // Each definition should have function type
        for def in &defs {
            assert_eq!(def.tool_type, "function");
            assert!(def.function.description.is_some());
        }
    }

    #[tokio::test]
    async fn test_tool_definitions_for_subset() {
        let registry = ToolRegistry::new();
        registry.register(EchoTool);
        registry.register(AddTool);

        let defs = registry.tool_definitions_for(&["echo"]);
        assert_eq!(defs.len(), 1);
        assert_eq!(defs[0].function.name, "echo");
    }

    #[tokio::test]
    async fn test_list_by_category() {
        let registry = ToolRegistry::new();
        registry.register(EchoTool);
        registry.register(AddTool);

        let test_tools = registry.list_tools_by_category("test");
        assert_eq!(test_tools.len(), 1);
        assert_eq!(test_tools[0].name, "echo");

        let math_tools = registry.list_tools_by_category("math");
        assert_eq!(math_tools.len(), 1);
        assert_eq!(math_tools[0].name, "add");
    }

    #[tokio::test]
    async fn test_execute_tool_call() {
        let registry = ToolRegistry::new();
        registry.register(EchoTool);

        let tool_call = super::super::types::ToolCall {
            id: "call_123".to_string(),
            name: "echo".to_string(),
            arguments: r#"{"message":"via tool_call"}"#.to_string(),
        };

        let result = registry.execute_tool_call(&tool_call).await.unwrap();
        assert!(result.success);
        assert_eq!(result.output, "Echo: via tool_call");
    }

    #[tokio::test]
    async fn test_tool_result_to_llm_content() {
        let ok_result = ToolResult::ok("some output");
        assert_eq!(ok_result.to_llm_content(), "some output");

        let err_result = ToolResult::err("something went wrong");
        assert!(err_result.to_llm_content().contains("something went wrong"));
    }

    #[tokio::test]
    async fn test_tool_result_ok_json() {
        let data = serde_json::json!({ "count": 42, "items": ["a", "b"] });
        let result = ToolResult::ok_json(&data);
        assert!(result.success);

        let parsed: serde_json::Value = serde_json::from_str(&result.output).unwrap();
        assert_eq!(parsed["count"], 42);
    }

    #[tokio::test]
    async fn test_tool_metadata_to_definition() {
        let meta = ToolMetadata::new(
            "my_tool",
            "Does something",
            serde_json::json!({"type": "object"}),
        )
        .with_category("custom")
        .read_write();

        assert_eq!(meta.name, "my_tool");
        assert_eq!(meta.category, "custom");
        assert!(!meta.is_read_only);

        let def = meta.to_tool_definition();
        assert_eq!(def.tool_type, "function");
        assert_eq!(def.function.name, "my_tool");
        assert_eq!(def.function.description.as_deref(), Some("Does something"));
    }

    #[test]
    fn test_max_tool_rounds() {
        let registry = ToolRegistry::new().with_max_rounds(5);
        assert_eq!(registry.max_tool_rounds(), 5);
    }

    #[tokio::test]
    async fn test_execute_bad_json_handled_gracefully() {
        let registry = ToolRegistry::new();
        registry.register(EchoTool);

        // Bad JSON should be caught by the tool and returned as error ToolResult
        let result = registry.execute("echo", "not-json").await.unwrap();
        // The tool catches the parse error and returns it via ToolResult::err
        assert!(!result.success);
    }
}
