"""
ToolRegistry - Centralized registry for managing groknroll tools

Following OpenCode architecture, provides:
- Singleton pattern for global tool access
- Lazy tool instantiation (PERF-002)
- Tool registration with duplicate name prevention
- Tool lookup by name
- Auto-discovery of tools in groknroll/tools/ directory
- Type-safe implementation
"""

import importlib
import inspect
from pathlib import Path
from typing import Any, Dict, Optional, Type

from groknroll.tools.base_tool import BaseTool


class ToolRegistry:
    """
    Centralized registry for managing tools

    Singleton pattern ensures one global registry instance.
    Tools are registered by name and can be retrieved for use.

    Features:
    - PERF-002: Lazy tool instantiation (tools created on first use)
    - Auto-discovery of tools in groknroll/tools/ directory
    - Duplicate name prevention
    - Fast O(1) lookup by name
    - List all registered tools

    Example:
        registry = ToolRegistry()
        registry.register(ReadTool())

        tool = registry.get("read")
        if tool:
            result = await tool.execute(path="file.txt")
    """

    _instance: Optional["ToolRegistry"] = None

    def __new__(cls) -> "ToolRegistry":
        """Singleton pattern - only one instance exists"""
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._tool_classes: Dict[str, Type[BaseTool]] = {}
            cls._instance._tool_instances: Dict[str, BaseTool] = {}
            cls._instance._tool_kwargs: Dict[str, Dict[str, Any]] = {}
            cls._instance._initialized = False
        return cls._instance

    def __init__(self):
        """Initialize registry (only runs once due to singleton)"""
        if not self._initialized:
            self._tool_classes: Dict[str, Type[BaseTool]] = {}
            self._tool_instances: Dict[str, BaseTool] = {}
            self._tool_kwargs: Dict[str, Dict[str, Any]] = {}
            self._initialized = True

    def register(self, tool: BaseTool) -> None:
        """
        Register a tool instance in the registry

        Args:
            tool: Tool instance to register (must inherit from BaseTool)

        Raises:
            TypeError: If tool does not inherit from BaseTool
            ValueError: If tool name already registered

        Example:
            registry = ToolRegistry()
            registry.register(ReadTool())
        """
        if not isinstance(tool, BaseTool):
            raise TypeError(f"Tool must inherit from BaseTool, got {type(tool).__name__}")

        tool_name = tool.name

        if tool_name in self._tool_classes or tool_name in self._tool_instances:
            raise ValueError(
                f"Tool '{tool_name}' is already registered. "
                f"Use a different name or unregister the existing tool first."
            )

        # Store the instance directly
        self._tool_instances[tool_name] = tool

    def register_class(
        self,
        tool_class: Type[BaseTool],
        name: str,
        **kwargs: Any
    ) -> None:
        """
        Register a tool class for lazy instantiation.

        PERF-002: Lazy loading - tool is only instantiated when first used.

        Args:
            tool_class: Tool class (must inherit from BaseTool)
            name: Name to register the tool under
            **kwargs: Arguments to pass when instantiating the tool

        Raises:
            TypeError: If tool_class does not inherit from BaseTool
            ValueError: If tool name already registered

        Example:
            registry = ToolRegistry()
            registry.register_class(ReadTool, "read", workspace_root="/path")
        """
        if not issubclass(tool_class, BaseTool):
            raise TypeError(f"Tool class must inherit from BaseTool, got {tool_class.__name__}")

        if name in self._tool_classes or name in self._tool_instances:
            raise ValueError(
                f"Tool '{name}' is already registered. "
                f"Use a different name or unregister the existing tool first."
            )

        self._tool_classes[name] = tool_class
        self._tool_kwargs[name] = kwargs

    def unregister(self, name: str) -> None:
        """
        Unregister a tool from the registry

        Args:
            name: Name of the tool to unregister

        Raises:
            KeyError: If tool name not found in registry

        Example:
            registry = ToolRegistry()
            registry.unregister("read")
        """
        found = False

        if name in self._tool_classes:
            del self._tool_classes[name]
            self._tool_kwargs.pop(name, None)
            found = True

        if name in self._tool_instances:
            del self._tool_instances[name]
            found = True

        if not found:
            raise KeyError(f"Tool '{name}' not found in registry")

    def get(self, name: str) -> Optional[BaseTool]:
        """
        Get a tool by name, instantiating lazily if needed.

        PERF-002: Lazy instantiation - tool is created on first access.

        Args:
            name: Name of the tool to retrieve

        Returns:
            Tool instance if found, None otherwise

        Example:
            registry = ToolRegistry()
            tool = registry.get("read")
            if tool:
                result = await tool.execute(path="file.txt")
        """
        # Check if already instantiated
        if name in self._tool_instances:
            return self._tool_instances[name]

        # Check if class is registered for lazy instantiation
        if name in self._tool_classes:
            tool_class = self._tool_classes[name]
            kwargs = self._tool_kwargs.get(name, {})

            # Instantiate and cache
            try:
                instance = tool_class(**kwargs)
                self._tool_instances[name] = instance
                return instance
            except Exception:
                return None

        return None

    def list_tools(self) -> list[BaseTool]:
        """
        List all registered tools (instantiates lazy tools).

        Returns:
            List of all tool instances in the registry

        Example:
            registry = ToolRegistry()
            for tool in registry.list_tools():
                print(f"{tool.name}: {tool.description}")
        """
        # Instantiate any lazy tools
        for name in self._tool_classes:
            if name not in self._tool_instances:
                self.get(name)

        return list(self._tool_instances.values())

    def list_tool_names(self) -> list[str]:
        """
        List all registered tool names without instantiating lazy tools.

        PERF-002: Avoids unnecessary instantiation.

        Returns:
            List of all registered tool names
        """
        names = set(self._tool_classes.keys())
        names.update(self._tool_instances.keys())
        return sorted(names)

    def has_tool(self, name: str) -> bool:
        """
        Check if a tool is registered

        Args:
            name: Name of the tool to check

        Returns:
            True if tool is registered, False otherwise

        Example:
            registry = ToolRegistry()
            if registry.has_tool("read"):
                tool = registry.get("read")
        """
        return name in self._tool_classes or name in self._tool_instances

    def clear(self) -> None:
        """
        Clear all registered tools

        Useful for testing or resetting the registry.

        Example:
            registry = ToolRegistry()
            registry.clear()  # Remove all tools
        """
        self._tool_classes.clear()
        self._tool_instances.clear()
        self._tool_kwargs.clear()

    def auto_discover(self, tools_dir: Optional[Path] = None, lazy: bool = True) -> int:
        """
        Auto-discover and register all tools in groknroll/tools/ directory

        PERF-002: By default, uses lazy registration (classes stored, not instantiated).

        Scans for all *_tool.py files (except base_tool.py and tool_registry.py),
        imports them, finds BaseTool subclasses, and registers them.

        Args:
            tools_dir: Directory to scan for tools (defaults to groknroll/tools/)
            lazy: If True (default), register classes for lazy instantiation.
                  If False, instantiate tools immediately (legacy behavior).

        Returns:
            Number of tools discovered and registered

        Raises:
            ImportError: If a tool module cannot be imported

        Example:
            registry = ToolRegistry()
            count = registry.auto_discover()
            print(f"Discovered {count} tools")
        """
        if tools_dir is None:
            # Get the directory containing this file (groknroll/tools/)
            tools_dir = Path(__file__).parent

        discovered_count = 0

        # Find all *_tool.py files except base_tool.py, tool_registry.py, and validators.py
        for tool_file in tools_dir.glob("*_tool.py"):
            if tool_file.name in ("base_tool.py", "tool_registry.py", "validators.py"):
                continue

            # Import the module
            module_name = f"groknroll.tools.{tool_file.stem}"
            try:
                module = importlib.import_module(module_name)
            except ImportError as e:
                raise ImportError(f"Failed to import {module_name}: {e}")

            # Find all BaseTool subclasses in the module
            for name, obj in inspect.getmembers(module, inspect.isclass):
                # Skip BaseTool itself and non-subclasses
                if obj is BaseTool or not issubclass(obj, BaseTool):
                    continue

                # Skip abstract classes (those with abstract methods)
                if inspect.isabstract(obj):
                    continue

                if lazy:
                    # PERF-002: Lazy registration - get tool name without instantiation
                    # We need to instantiate once to get the name, then discard
                    try:
                        temp_instance = obj()
                        tool_name = temp_instance.name

                        # Only register if not already registered
                        if not self.has_tool(tool_name):
                            self.register_class(obj, tool_name)
                            discovered_count += 1
                    except Exception:
                        # Skip tools that can't be instantiated
                        continue
                else:
                    # Legacy behavior: instantiate immediately
                    try:
                        tool_instance = obj()

                        # Only register if not already registered
                        if not self.has_tool(tool_instance.name):
                            self.register(tool_instance)
                            discovered_count += 1
                    except Exception:
                        # Skip tools that can't be instantiated
                        continue

        return discovered_count

    def __len__(self) -> int:
        """Return number of registered tools"""
        return len(self._tool_classes) + len(
            set(self._tool_instances.keys()) - set(self._tool_classes.keys())
        )

    def __contains__(self, name: str) -> bool:
        """Check if tool name is registered (supports 'in' operator)"""
        return name in self._tool_classes or name in self._tool_instances

    def __str__(self) -> str:
        """String representation"""
        total = len(self)
        instantiated = len(self._tool_instances)
        return f"ToolRegistry({total} tools, {instantiated} instantiated)"

    def __repr__(self) -> str:
        """Detailed string representation"""
        tool_names = self.list_tool_names()
        return f"ToolRegistry(tools={tool_names})"
