use crate::context::Context;
use crate::events::Event;
use crate::tools::tools::{Tool, ToolCallback};
use serde_json::json;

/// Tool callback that handles document blocks and stores the document name
pub struct DocumentNameTool;

/// Tool callback that handles page blocks and demonstrates scoped variables
pub struct ScopedDemoTool;

impl ToolCallback for DocumentNameTool {
    fn handle(&self, event: &Event, context: Context) {
        if let Event::BlockStarted { meta_line } = event {
            if !meta_line.inline_text.content.is_empty() {
                context.put("document".to_string(), json!(meta_line.inline_text.content));
            }
        }
    }

    fn as_any(&self) -> &dyn std::any::Any {
        self
    }
}

impl ToolCallback for ScopedDemoTool {
    fn handle(&self, event: &Event, context: Context) {
        if let Event::BlockStarted { meta_line } = event {
            if !meta_line.inline_text.content.is_empty() {
                // Put a scoped variable that will be available to child blocks
                let page_local_value = format!("Page {} local var", meta_line.inline_text.content);
                context.put_scoped("page_local".to_string(), json!(page_local_value));
            }
        }
    }

    fn as_any(&self) -> &dyn std::any::Any {
        self
    }
}

/// Create the document name tool
pub fn create_document_name_tool() -> Result<Tool, String> {
    Tool::new(
        "document_name_tool".to_string(),
        vec!["document".to_string()],
        Box::new(DocumentNameTool),
    )
}

/// Create the scoped demo tool
pub fn create_scoped_demo_tool() -> Result<Tool, String> {
    Tool::new(
        "scoped_demo_tool".to_string(),
        vec!["page".to_string()],
        Box::new(ScopedDemoTool),
    )
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::parsers::wasot::{Id, MetaLine, ParsedArguments, QuotedString};
    use serde_json::json;

    #[test]
    fn test_document_name_tool_handle() {
        let tool = DocumentNameTool;
        let context = Context::new();

        // Test lines 13-16 in function `handle` - with content
        let meta = MetaLine {
            line_index: Some(1),
            is_void: false,
            level: 1,
            type_name: "document".to_string(),
            id: Id::new("".to_string()),
            inline_text: QuotedString::new("My Document".to_string(), false),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };
        let event = Event::BlockStarted { meta_line: meta };

        tool.handle(&event, context.clone());

        // Verify the document name was stored
        let stored_value = context.get("document");
        assert!(stored_value.is_some());
        assert_eq!(stored_value.unwrap(), json!("My Document"));
    }

    #[test]
    fn test_document_name_tool_handle_empty_content() {
        let tool = DocumentNameTool;
        let context = Context::new();

        // Test with empty content - should not store anything
        let meta = MetaLine {
            line_index: Some(1),
            is_void: false,
            level: 1,
            type_name: "document".to_string(),
            id: Id::new("".to_string()),
            inline_text: QuotedString::new("".to_string(), false),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };
        let event = Event::BlockStarted { meta_line: meta };

        tool.handle(&event, context.clone());

        // Verify nothing was stored for empty content
        let stored_value = context.get("document");
        assert!(stored_value.is_none());
    }

    #[test]
    fn test_document_name_tool_as_any() {
        let tool = DocumentNameTool;

        // Test line 21 in function `as_any`
        let any_ref = tool.as_any();
        assert!(any_ref.downcast_ref::<DocumentNameTool>().is_some());
    }

    #[test]
    fn test_scoped_demo_tool_handle() {
        let tool = ScopedDemoTool;
        let context = Context::new();

        // Test lines 27-29 and 31-32 in function `handle`
        let meta = MetaLine {
            line_index: Some(1),
            is_void: false,
            level: 1,
            type_name: "page".to_string(),
            id: Id::new("".to_string()),
            inline_text: QuotedString::new("1".to_string(), false),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };
        let event = Event::BlockStarted { meta_line: meta };

        tool.handle(&event, context.clone());

        // Verify the scoped variable was stored
        let stored_value = context.get_scoped("page_local");
        assert!(stored_value.is_some());
        assert_eq!(stored_value.unwrap(), json!("Page 1 local var"));
    }

    #[test]
    fn test_scoped_demo_tool_handle_empty_content() {
        let tool = ScopedDemoTool;
        let context = Context::new();

        // Test with empty content - should not store anything
        let meta = MetaLine {
            line_index: Some(1),
            is_void: false,
            level: 1,
            type_name: "page".to_string(),
            id: Id::new("".to_string()),
            inline_text: QuotedString::new("".to_string(), false),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
        };
        let event = Event::BlockStarted { meta_line: meta };

        tool.handle(&event, context.clone());

        // Verify nothing was stored for empty content
        let stored_value = context.get_scoped("page_local");
        assert!(stored_value.is_none());
    }

    #[test]
    fn test_scoped_demo_tool_as_any() {
        let tool = ScopedDemoTool;

        // Test line 37 in function `as_any`
        let any_ref = tool.as_any();
        assert!(any_ref.downcast_ref::<ScopedDemoTool>().is_some());
    }

    #[test]
    fn test_create_document_name_tool() {
        // Test line 43 and lines 45-47 in function `create_document_name_tool`
        let result = create_document_name_tool();
        assert!(result.is_ok());

        let tool = result.unwrap();
        assert_eq!(tool.name, "document_name_tool");
        assert_eq!(tool.parsed_selectors.len(), 1);
    }

    #[test]
    fn test_create_scoped_demo_tool() {
        // Test line 52 and lines 54-56 in function `create_scoped_demo_tool`
        let result = create_scoped_demo_tool();
        assert!(result.is_ok());

        let tool = result.unwrap();
        assert_eq!(tool.name, "scoped_demo_tool");
        assert_eq!(tool.parsed_selectors.len(), 1);
    }
}
