//! Built-in Tool: db_doc_crud — Phase-83 T83.5
//!
//! Provides document CRUD operations (get, list, create, delete) on collections.

use std::sync::Arc;

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

use crate::storage::collection::CollectionStore;
use crate::llm::error::{LlmError, LlmResult};
use crate::llm::tool_executor::{ToolExecutor, ToolMetadata, ToolResult};

/// Tool for document CRUD operations on collections.
pub struct DocCrudTool {
    collections: Arc<CollectionStore>,
}

impl DocCrudTool {
    /// Create a new document CRUD tool.
    pub fn new(collections: Arc<CollectionStore>) -> Self {
        Self { collections }
    }
}

/// Arguments for the document CRUD tool.
#[derive(Debug, Deserialize)]
struct DocCrudArgs {
    /// The CRUD operation to perform.
    operation: DocOperation,

    /// Collection name.
    collection: String,

    /// Document ID (required for get, delete).
    #[serde(default)]
    id: Option<String>,

    /// Maximum number of documents to list (for list operation).
    #[serde(default = "default_limit")]
    limit: usize,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
enum DocOperation {
    /// Get a single document by ID.
    Get,
    /// List documents in the collection (paginated).
    List,
    /// Get collection info (document count, vector count, config).
    Info,
}

fn default_limit() -> usize {
    20
}

#[async_trait]
impl ToolExecutor for DocCrudTool {
    fn metadata(&self) -> ToolMetadata {
        ToolMetadata::new(
            "db_doc_crud",
            "Perform document operations on collections: \
             get (retrieve a document by ID), \
             list (list documents in a collection), \
             info (get collection metadata and statistics).",
            serde_json::json!({
                "type": "object",
                "properties": {
                    "operation": {
                        "type": "string",
                        "enum": ["get", "list", "info"],
                        "description": "The operation: get (by ID), list (paginated), info (collection stats)"
                    },
                    "collection": {
                        "type": "string",
                        "description": "Name of the collection"
                    },
                    "id": {
                        "type": "string",
                        "description": "Document ID (required for 'get' operation)"
                    },
                    "limit": {
                        "type": "integer",
                        "description": "Max documents to return for 'list' (default: 20)",
                        "default": 20,
                        "minimum": 1,
                        "maximum": 100
                    }
                },
                "required": ["operation", "collection"]
            }),
        )
        .with_category("database")
    }

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

        // List all available collections if collection not found
        let collection = match self.collections.get_collection(&args.collection) {
            Ok(Some(c)) => c,
            Ok(None) => {
                let available = self.collections.list_collections();
                return Ok(ToolResult::err(format!(
                    "Collection '{}' not found. Available collections: {}",
                    args.collection,
                    if available.is_empty() {
                        "(none)".to_string()
                    } else {
                        available.join(", ")
                    }
                )));
            }
            Err(e) => {
                return Ok(ToolResult::err(format!(
                    "Failed to access collection: {}",
                    e
                )));
            }
        };

        match args.operation {
            DocOperation::Get => {
                let id = args.id.as_deref().ok_or_else(|| {
                    LlmError::ResponseParseError("'id' is required for 'get' operation".into())
                })?;

                match collection.docs.get(id) {
                    Ok(Some(doc)) => Ok(ToolResult::ok_json(&serde_json::json!({
                        "found": true,
                        "document": {
                            "id": doc.id,
                            "content": doc.content,
                            "metadata": doc.metadata,
                            "tags": doc.tags,
                            "created_at": doc.created_at,
                        }
                    }))),
                    Ok(None) => Ok(ToolResult::ok_json(&serde_json::json!({
                        "found": false,
                        "message": format!("Document '{}' not found in collection '{}'", id, args.collection)
                    }))),
                    Err(e) => Ok(ToolResult::err(format!("Failed to get document: {}", e))),
                }
            }

            DocOperation::List => {
                let limit = args.limit.min(100).max(1);

                match collection.docs.all() {
                    Ok(docs) => {
                        let total = docs.len();
                        let items: Vec<serde_json::Value> = docs
                            .into_iter()
                            .take(limit)
                            .map(|doc| {
                                let preview = truncate(&doc.content, 200);
                                serde_json::json!({
                                    "id": doc.id,
                                    "content_preview": preview,
                                    "tags": doc.tags,
                                })
                            })
                            .collect();

                        Ok(ToolResult::ok_json(&serde_json::json!({
                            "collection": args.collection,
                            "total_documents": total,
                            "returned": items.len(),
                            "documents": items,
                        })))
                    }
                    Err(e) => Ok(ToolResult::err(format!("Failed to list documents: {}", e))),
                }
            }

            DocOperation::Info => {
                let doc_count = collection.document_count();
                let vec_count = collection.vector_count();
                let fts_available = collection.docs.supports_fts();

                Ok(ToolResult::ok_json(&serde_json::json!({
                    "collection": args.collection,
                    "document_count": doc_count,
                    "vector_count": vec_count,
                    "dimension": collection.config.dimension,
                    "full_text_search_available": fts_available,
                })))
            }
        }
    }
}

fn truncate(s: &str, max: usize) -> String {
    if s.len() <= max {
        s.to_string()
    } else {
        let mut end = max;
        while !s.is_char_boundary(end) && end > 0 {
            end -= 1;
        }
        format!("{}...", &s[..end])
    }
}

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

    #[test]
    fn test_doc_crud_metadata() {
        let store = Arc::new(
            CollectionStore::open_or_create(std::env::temp_dir().join("test_dc_meta")).unwrap(),
        );
        let tool = DocCrudTool::new(store);
        let meta = tool.metadata();
        assert_eq!(meta.name, "db_doc_crud");
        assert_eq!(meta.category, "database");
        assert!(meta.is_read_only);
    }
}
