use crate::context::Context;
use crate::events::Event;
use crate::parsers::selectors::{matches_prefix, parse_selector, SimpleSelectorGroup};
use crate::StackFrame;
use std::sync::{Arc, Mutex, OnceLock};

/// Trait for tool callbacks that handle events and can modify context
pub trait ToolCallback: Send + Sync {
    /// Handle an event with the given context and return context mutations
    fn handle(&self, event: &Event, context: Context);

    /// Provide access to Any for downcasting
    fn as_any(&self) -> &dyn std::any::Any;
}

/// A tool that responds to events matching certain selectors
#[derive(Clone)]
pub struct Tool {
    /// Name of the tool
    pub name: String,
    /// Parsed selector groups that trigger this tool
    pub parsed_selectors: Vec<SimpleSelectorGroup>,
    /// The callback that handles events - can't be cloned, so we'll use a different approach
    pub callback: std::sync::Arc<dyn ToolCallback>,
}

impl Tool {
    /// Create a new tool with the given name, selectors, and callback
    pub fn new(
        name: String,
        selectors: Vec<String>,
        callback: Box<dyn ToolCallback>,
    ) -> Result<Self, String> {
        let mut selector_groups = Vec::new();

        for selector_str in selectors {
            let group = parse_selector(&selector_str)
                .map_err(|e| format!("Failed to parse selector '{selector_str}': {e}"))?;
            selector_groups.push(group);
        }

        Ok(Self {
            name,
            parsed_selectors: selector_groups,
            callback: std::sync::Arc::from(callback),
        })
    }

    /// Check if any of the tool's selectors match the current stack state
    pub fn matches(&self, stack: &[StackFrame]) -> bool {
        self.parsed_selectors
            .iter()
            .any(|group| matches_prefix(stack, group))
    }
}

impl std::fmt::Debug for Tool {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Tool")
            .field("name", &self.name)
            .field("parsed_selectors", &self.parsed_selectors)
            .field("callback", &"<dyn ToolCallback>")
            .finish()
    }
}

/// Registry for managing and looking up tools
#[derive(Clone)]
pub struct ToolRegistry {
    tools: Vec<Tool>,
}

impl ToolRegistry {
    /// Create a new empty tool registry
    pub fn new() -> Self {
        Self { tools: Vec::new() }
    }

    /// Register a new tool in the registry
    pub fn register(&mut self, tool: Tool) {
        self.tools.push(tool);
    }

    /// Get all tools that match the given stack
    pub fn get_matching_tools(&self, stack: &[StackFrame]) -> Vec<&Tool> {
        self.tools
            .iter()
            .filter(|tool| tool.matches(stack))
            .collect()
    }

    /// Get all registered tools
    pub fn get_all_tools(&self) -> &[Tool] {
        &self.tools
    }

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

    /// Check if the registry is empty
    pub fn is_empty(&self) -> bool {
        self.tools.is_empty()
    }

    /// Find a tool by name
    pub fn find_tool_by_name(&self, name: &str) -> Option<&Tool> {
        self.tools.iter().find(|tool| tool.name == name)
    }
}

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

// Global tool registry instance
static GLOBAL_TOOL_REGISTRY: OnceLock<Arc<Mutex<ToolRegistry>>> = OnceLock::new();

/// Get access to the global tool registry
pub fn get_global_registry() -> Arc<Mutex<ToolRegistry>> {
    GLOBAL_TOOL_REGISTRY
        .get_or_init(|| {
            let registry = Arc::new(Mutex::new(ToolRegistry::new()));

            // Register stdlib tools directly with the registry instance
            // to avoid circular calls to get_global_registry()
            if let Err(e) = register_stdlib_tools_to_registry(&registry) {
                eprintln!("Warning: Failed to register stdlib tools: {e}");
            }

            registry
        })
        .clone()
}

/// Internal function to register stdlib tools to a specific registry
fn register_stdlib_tools_to_registry(registry: &Arc<Mutex<ToolRegistry>>) -> Result<(), String> {
    let import_tool = crate::tools::stdlib::create_import_tool()?;
    let mut registry_guard = registry.lock().unwrap();
    registry_guard.register(import_tool);
    Ok(())
}

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

    // Mock tool callback for testing
    #[allow(dead_code)]
    struct MockToolCallback {
        name: String,
    }

    impl MockToolCallback {
        fn new(name: String) -> Self {
            Self { name }
        }
    }

    impl ToolCallback for MockToolCallback {
        fn handle(&self, _event: &Event, _context: Context) {
            // Mock implementation - just do nothing for testing
        }

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

    #[test]
    fn test_tool_new_with_valid_selectors() {
        let callback = Box::new(MockToolCallback::new("test".to_string()));
        let selectors = vec!["div".to_string(), "p".to_string()];

        let result = Tool::new("test_tool".to_string(), selectors, callback);

        assert!(result.is_ok());
        let tool = result.unwrap();
        assert_eq!(tool.name, "test_tool");
        assert_eq!(tool.parsed_selectors.len(), 2);
    }

    #[test]
    fn test_tool_new_with_invalid_selectors() {
        let callback = Box::new(MockToolCallback::new("test".to_string()));
        let selectors = vec!["invalid[[[selector".to_string()]; // Invalid CSS selector

        let result = Tool::new("test_tool".to_string(), selectors, callback);

        assert!(result.is_err());
        assert!(result.unwrap_err().contains("Failed to parse selector"));
    }

    #[test]
    fn test_tool_new_with_empty_selectors() {
        let callback = Box::new(MockToolCallback::new("test".to_string()));
        let selectors = vec![];

        let result = Tool::new("test_tool".to_string(), selectors, callback);

        assert!(result.is_ok());
        let tool = result.unwrap();
        assert_eq!(tool.parsed_selectors.len(), 0);
    }

    #[test]
    fn test_tool_matches_with_matching_stack() {
        let callback = Box::new(MockToolCallback::new("test".to_string()));
        let selectors = vec!["div".to_string()];

        let tool = Tool::new("test_tool".to_string(), selectors, callback).unwrap();

        // Create a stack frame that should match "div" selector
        let meta_line = MetaLine {
            level: 1,
            type_name: "div".to_string(),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
            inline_text: QuotedString::new("".to_string(), false),
            line_index: None,
            is_void: false,
        };

        let stack_frame = StackFrame {
            metaline: meta_line,
            child_index: 0,
        };

        let stack = vec![stack_frame];
        assert!(tool.matches(&stack));
    }

    #[test]
    fn test_tool_matches_with_non_matching_stack() {
        let callback = Box::new(MockToolCallback::new("test".to_string()));
        let selectors = vec!["div".to_string()];

        let tool = Tool::new("test_tool".to_string(), selectors, callback).unwrap();

        // Create a stack frame that should NOT match "div" selector
        let meta_line = MetaLine {
            level: 1,
            type_name: "p".to_string(), // Different type
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
            inline_text: QuotedString::new("".to_string(), false),
            line_index: None,
            is_void: false,
        };

        let stack_frame = StackFrame {
            metaline: meta_line,
            child_index: 0,
        };

        let stack = vec![stack_frame];
        assert!(!tool.matches(&stack));
    }

    #[test]
    fn test_tool_debug_format() {
        let callback = Box::new(MockToolCallback::new("test".to_string()));
        let selectors = vec!["div".to_string()];

        let tool = Tool::new("test_tool".to_string(), selectors, callback).unwrap();

        let debug_str = format!("{tool:?}");
        assert!(debug_str.contains("test_tool"));
        assert!(debug_str.contains("<dyn ToolCallback>"));
    }

    #[test]
    fn test_tool_registry_new() {
        let registry = ToolRegistry::new();

        assert_eq!(registry.len(), 0);
        assert!(registry.is_empty());
        assert_eq!(registry.get_all_tools().len(), 0);
    }

    #[test]
    fn test_tool_registry_register() {
        let mut registry = ToolRegistry::new();
        let callback = Box::new(MockToolCallback::new("test".to_string()));
        let tool = Tool::new("test_tool".to_string(), vec!["div".to_string()], callback).unwrap();

        registry.register(tool);

        assert_eq!(registry.len(), 1);
        assert!(!registry.is_empty());
        assert_eq!(registry.get_all_tools().len(), 1);
    }

    #[test]
    fn test_tool_registry_get_matching_tools() {
        let mut registry = ToolRegistry::new();

        // Add a tool that matches "div"
        let callback1 = Box::new(MockToolCallback::new("div_tool".to_string()));
        let div_tool =
            Tool::new("div_tool".to_string(), vec!["div".to_string()], callback1).unwrap();
        registry.register(div_tool);

        // Add a tool that matches "p"
        let callback2 = Box::new(MockToolCallback::new("p_tool".to_string()));
        let p_tool = Tool::new("p_tool".to_string(), vec!["p".to_string()], callback2).unwrap();
        registry.register(p_tool);

        // Create stack with "div"
        let meta_line = MetaLine {
            level: 1,
            type_name: "div".to_string(),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
            inline_text: QuotedString::new("".to_string(), false),
            line_index: None,
            is_void: false,
        };

        let stack_frame = StackFrame {
            metaline: meta_line,
            child_index: 0,
        };

        let stack = vec![stack_frame];
        let matching_tools = registry.get_matching_tools(&stack);

        assert_eq!(matching_tools.len(), 1);
        assert_eq!(matching_tools[0].name, "div_tool");
    }

    #[test]
    fn test_tool_registry_find_tool_by_name() {
        let mut registry = ToolRegistry::new();
        let callback = Box::new(MockToolCallback::new("test".to_string()));
        let tool = Tool::new("test_tool".to_string(), vec!["div".to_string()], callback).unwrap();

        registry.register(tool);

        // Should find the tool
        let found = registry.find_tool_by_name("test_tool");
        assert!(found.is_some());
        assert_eq!(found.unwrap().name, "test_tool");

        // Should not find non-existent tool
        let not_found = registry.find_tool_by_name("nonexistent");
        assert!(not_found.is_none());
    }

    #[test]
    fn test_tool_registry_default() {
        let registry = ToolRegistry::default();

        assert_eq!(registry.len(), 0);
        assert!(registry.is_empty());
    }

    #[test]
    fn test_get_global_registry() {
        let registry = get_global_registry();

        // Should get a valid registry
        assert!(registry.lock().is_ok());

        // Should get the same instance on subsequent calls
        let registry2 = get_global_registry();
        assert!(Arc::ptr_eq(&registry, &registry2));
    }

    #[test]
    fn test_global_registry_has_import_tool() {
        let registry = get_global_registry();
        let registry_guard = registry.lock().unwrap();

        // The global registry should have at least the import tool registered
        assert!(!registry_guard.is_empty());

        // Should be able to find the import tool
        let import_tool = registry_guard.find_tool_by_name("import");
        assert!(import_tool.is_some());
    }

    #[test]
    fn test_tool_callback_trait() {
        let callback = MockToolCallback::new("test".to_string());

        // Test as_any method
        let any_ref = callback.as_any();
        assert!(any_ref.downcast_ref::<MockToolCallback>().is_some());

        // Test handle method (should not panic)
        let event = Event::BlockFinished;
        let context = Context::new();
        callback.handle(&event, context); // Should complete without error
    }

    #[test]
    fn test_tool_multiple_selectors() {
        let callback = Box::new(MockToolCallback::new("multi".to_string()));
        let selectors = vec!["div".to_string(), "p".to_string(), "span".to_string()];

        let tool = Tool::new("multi_tool".to_string(), selectors, callback).unwrap();

        assert_eq!(tool.parsed_selectors.len(), 3);

        // Test each selector matches appropriately
        let test_types = ["div", "p", "span", "h1"];

        for type_name in &test_types {
            let meta_line = MetaLine {
                level: 1,
                type_name: type_name.to_string(),
                id: Id::empty(),
                arguments: ParsedArguments {
                    args: vec![],
                    kwargs: vec![],
                },
                inline_text: QuotedString::new("".to_string(), false),
                line_index: None,
                is_void: false,
            };

            let stack_frame = StackFrame {
                metaline: meta_line,
                child_index: 0,
            };

            let stack = vec![stack_frame];

            if *type_name == "div" || *type_name == "p" || *type_name == "span" {
                assert!(tool.matches(&stack), "Should match {type_name}");
            } else {
                assert!(!tool.matches(&stack), "Should not match {type_name}");
            }
        }
    }

    #[test]
    fn test_tool_registry_multiple_matching_tools() {
        let mut registry = ToolRegistry::new();

        // Add multiple tools that match "div"
        let callback1 = Box::new(MockToolCallback::new("tool1".to_string()));
        let tool1 = Tool::new("tool1".to_string(), vec!["div".to_string()], callback1).unwrap();
        registry.register(tool1);

        let callback2 = Box::new(MockToolCallback::new("tool2".to_string()));
        let tool2 = Tool::new("tool2".to_string(), vec!["div".to_string()], callback2).unwrap();
        registry.register(tool2);

        // Create stack with "div"
        let meta_line = MetaLine {
            level: 1,
            type_name: "div".to_string(),
            id: Id::empty(),
            arguments: ParsedArguments {
                args: vec![],
                kwargs: vec![],
            },
            inline_text: QuotedString::new("".to_string(), false),
            line_index: None,
            is_void: false,
        };

        let stack_frame = StackFrame {
            metaline: meta_line,
            child_index: 0,
        };

        let stack = vec![stack_frame];
        let matching_tools = registry.get_matching_tools(&stack);

        assert_eq!(matching_tools.len(), 2);

        let tool_names: Vec<&str> = matching_tools.iter().map(|t| t.name.as_str()).collect();
        assert!(tool_names.contains(&"tool1"));
        assert!(tool_names.contains(&"tool2"));
    }

    #[test]
    fn test_tool_empty_stack() {
        let callback = Box::new(MockToolCallback::new("test".to_string()));
        let selectors = vec!["div".to_string()];

        let tool = Tool::new("test_tool".to_string(), selectors, callback).unwrap();

        // Empty stack should not match any selector
        let empty_stack = vec![];
        assert!(!tool.matches(&empty_stack));
    }
}
