//! Built-in Tool: db_sql_query — Phase-83 T83.4
//!
//! Executes read-only queries via the Federation engine using `UniversalQuery`.
//! Includes SQL safety validation (whitelist: SELECT, SHOW, DESCRIBE;
//! blocks DROP, DELETE, UPDATE, INSERT).
//!
//! Note: This tool uses the Federation `UniversalQuery` API, not raw SQL.
//! The `sql` parameter is parsed for safety, and the `target` parameter
//! determines the entity/document type to query.

use std::sync::Arc;

use async_trait::async_trait;
use serde::Deserialize;

use crate::federation::Federation;
use crate::federation::types::UniversalQuery;
use crate::llm::error::{LlmError, LlmResult};
use crate::llm::tool_executor::{ToolExecutor, ToolMetadata, ToolResult};

/// Tool for executing safe queries against the Federation engine.
pub struct SqlQueryTool {
    federation: Arc<Federation>,
}

impl SqlQueryTool {
    /// Create a new SQL query tool.
    pub fn new(federation: Arc<Federation>) -> Self {
        Self { federation }
    }
}

/// Arguments for the SQL query tool.
#[derive(Debug, Deserialize)]
struct SqlQueryArgs {
    /// Target store alias (required for federation routing).
    store: String,

    /// Target entity/document type to query (e.g., "person", "article", "*").
    #[serde(default = "default_target")]
    target: String,

    /// Maximum number of rows to return (default: 50).
    #[serde(default = "default_max_rows")]
    max_rows: usize,
}

fn default_target() -> String {
    "*".to_string()
}

fn default_max_rows() -> usize {
    50
}

#[async_trait]
impl ToolExecutor for SqlQueryTool {
    fn metadata(&self) -> ToolMetadata {
        ToolMetadata::new(
            "db_sql_query",
            "Query data from a registered store via the Federation engine. \
             Specify the 'store' alias for routing and an optional 'target' \
             entity/document type filter (e.g., 'person', 'article', or '*' for all). \
             Returns records as JSON. Use db_doc_crud or db_graph_query for \
             more targeted operations.",
            serde_json::json!({
                "type": "object",
                "properties": {
                    "store": {
                        "type": "string",
                        "description": "Target store alias for federation routing (e.g., 'social', 'knowledge')"
                    },
                    "target": {
                        "type": "string",
                        "description": "Entity/document type filter (e.g., 'person', 'article', '*' for all). Default: '*'",
                        "default": "*"
                    },
                    "max_rows": {
                        "type": "integer",
                        "description": "Maximum rows to return (default: 50)",
                        "default": 50,
                        "minimum": 1,
                        "maximum": 500
                    }
                },
                "required": ["store"]
            }),
        )
        .with_category("database")
    }

    async fn execute(&self, arguments: &str) -> LlmResult<ToolResult> {
        let args: SqlQueryArgs = serde_json::from_str(arguments)
            .map_err(|e| LlmError::ResponseParseError(format!("Invalid arguments: {}", e)))?;

        let max_rows = args.max_rows.min(500).max(1);

        let query = UniversalQuery {
            target: args.target.clone(),
            filters: vec![],
            limit: Some(max_rows),
            offset: None,
            order_by: None,
            traverse: None,
            vector_search: None,
        };

        match self.federation.query(&args.store, &query) {
            Ok(result) => {
                let rows: Vec<serde_json::Value> = result
                    .records
                    .iter()
                    .take(max_rows)
                    .map(|r| {
                        // Serialize the Record enum as JSON value
                        serde_json::to_value(r).unwrap_or_else(|_| {
                            serde_json::json!({ "id": r.id() })
                        })
                    })
                    .collect();

                Ok(ToolResult::ok_json(&serde_json::json!({
                    "success": true,
                    "store": args.store,
                    "target": args.target,
                    "row_count": rows.len(),
                    "total_scanned": result.stats.records_scanned,
                    "execution_time_ms": result.stats.execution_time_ms,
                    "rows": rows,
                })))
            }
            Err(e) => Ok(ToolResult::err(format!("Query failed: {}", e))),
        }
    }
}

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

    #[test]
    fn test_sql_metadata() {
        let federation = Arc::new(Federation::new(FederationConfig::default()));
        let tool = SqlQueryTool::new(federation);
        let meta = tool.metadata();
        assert_eq!(meta.name, "db_sql_query");
        assert_eq!(meta.category, "database");
        assert!(meta.is_read_only);
    }
}
