import importlib
import inspect
import logging
import os
import pkgutil
import sys
from typing import Callable, Optional, Type

from shared.abstractions.tool import Tool

logger = logging.getLogger(__name__)


class ToolRegistry:
    """
    Registry for discovering and managing tools from both
    built-in sources and user-defined extensions.
    """

    def __init__(
        self,
        built_in_path: str | None = None,
        user_tools_path: str | None = None,
    ):
        self.built_in_path = built_in_path or os.path.join(
            os.path.dirname(os.path.abspath(__file__)), "built_in"
        )
        self.user_tools_path = (
            user_tools_path
            or os.getenv("R2R_USER_TOOLS_PATH")
            or "../docker/user_tools"
        )

        # Tool storage
        self._built_in_tools: dict[str, Type[Tool]] = {}
        self._user_tools: dict[str, Type[Tool]] = {}

        # Discover tools
        self._discover_built_in_tools()
        if os.path.exists(self.user_tools_path):
            self._discover_user_tools()
        else:
            logger.warning(
                f"User tools directory not found: {self.user_tools_path}"
            )

    def _discover_built_in_tools(self):
        """Load all built-in tools from the built_in directory."""
        if not os.path.exists(self.built_in_path):
            logger.warning(
                f"Built-in tools directory not found: {self.built_in_path}"
            )
            return

        # Add to Python path if needed
        if self.built_in_path not in sys.path:
            sys.path.append(os.path.dirname(self.built_in_path))

        # Import the built_in package
        try:
            built_in_pkg = importlib.import_module("built_in")
        except ImportError:
            logger.error("Failed to import built_in tools package")
            return

        # Discover all modules in the package
        for _, module_name, is_pkg in pkgutil.iter_modules(
            [self.built_in_path]
        ):
            if is_pkg:  # Skip subpackages
                continue

            try:
                module = importlib.import_module(f"built_in.{module_name}")

                # Find all tool classes in the module
                for name, obj in inspect.getmembers(module, inspect.isclass):
                    if (
                        issubclass(obj, Tool)
                        and obj.__module__ == module.__name__
                        and obj != Tool
                    ):
                        try:
                            tool_instance = obj()
                            self._built_in_tools[tool_instance.name] = obj
                            logger.debug(
                                f"Loaded built-in tool: {tool_instance.name}"
                            )
                        except Exception as e:
                            logger.error(
                                f"Error instantiating built-in tool {name}: {e}"
                            )
            except Exception as e:
                logger.error(
                    f"Error loading built-in tool module {module_name}: {e}"
                )

    def _discover_user_tools(self):
        """Scan the user tools directory for custom tools."""
        # Add user_tools directory to Python path if needed
        if self.user_tools_path not in sys.path:
            sys.path.append(os.path.dirname(self.user_tools_path))

        user_tools_pkg_name = os.path.basename(self.user_tools_path)

        # Check all Python files in user_tools directory
        for filename in os.listdir(self.user_tools_path):
            if (
                not filename.endswith(".py")
                or filename.startswith("_")
                or filename.startswith(".")
            ):
                continue

            module_name = filename[:-3]  # Remove .py extension

            try:
                # Import the module
                module = importlib.import_module(
                    f"{user_tools_pkg_name}.{module_name}"
                )

                # Find all tool classes in the module
                for name, obj in inspect.getmembers(module, inspect.isclass):
                    if (
                        issubclass(obj, Tool)
                        and obj.__module__ == module.__name__
                        and obj != Tool
                    ):
                        try:
                            tool_instance = obj()
                            self._user_tools[tool_instance.name] = obj
                            logger.debug(
                                f"Loaded user tool: {tool_instance.name}"
                            )
                        except Exception as e:
                            logger.error(
                                f"Error instantiating user tool {name}: {e}"
                            )
            except Exception as e:
                logger.error(
                    f"Error loading user tool module {module_name}: {e}"
                )

    def get_tool_class(self, tool_name: str):
        """Get a tool class by name."""
        if tool_name in self._user_tools:
            return self._user_tools[tool_name]

        return self._built_in_tools.get(tool_name)

    def list_available_tools(
        self, include_built_in=True, include_user=True
    ) -> list[str]:
        """
        List all available tool names.
        Optionally filter by built-in or user-defined tools.
        """
        tools: set[str] = set()

        if include_built_in:
            tools.update(self._built_in_tools.keys())

        if include_user:
            tools.update(self._user_tools.keys())

        return sorted(list(tools))

    def create_tool_instance(
        self, tool_name: str, format_function: Callable, context=None
    ) -> Optional[Tool]:
        """
        Create, configure, and return an instance of the specified tool.
        Returns None if the tool doesn't exist or instantiation fails.
        """
        tool_class = self.get_tool_class(tool_name)
        if not tool_class:
            logger.warning(f"Tool class not found for '{tool_name}'")
            return None

        try:
            tool_instance = tool_class()
            if hasattr(tool_instance, "llm_format_function"):
                tool_instance.llm_format_function = format_function

            # Set the context on the specific tool instance
            tool_instance.set_context(context)

            return tool_instance

        except Exception as e:
            logger.error(
                f"Error creating or setting context for tool instance '{tool_name}': {e}"
            )
            return None
