"""工具注册表 - 插件式架构核心

支持多来源工具发现和加载：
- 内置工具 - 代码中自带的工具
- 本地插件 - ~/.jude_agent/plugins/ 目录
- pip 安装的工具 - 通过 entry_points 发现
- MCP 配置 - 现有的 MCP 服务器配置
"""

import importlib
import importlib.util
import logging
import os
import sys
from abc import ABC, abstractmethod
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Dict, List, Optional, Type

from .base import BaseTool, ToolCollection

logger = logging.getLogger("agent")


# ============================================================================
# 工具包标准
# ============================================================================


@dataclass
class ToolPackageInfo:
    """工具包信息"""
    name: str
    version: str = "0.1.0"
    description: str = ""
    author: str = ""
    source: str = "builtin"  # builtin, local, pip, mcp
    path: Optional[Path] = None
    tool_class: Optional[Type[BaseTool]] = None


class ToolSource(ABC):
    """工具来源抽象基类"""

    @abstractmethod
    def discover(self) -> List[ToolPackageInfo]:
        """发现可用的工具包"""
        pass

    @abstractmethod
    def load(self, package_info: ToolPackageInfo) -> Optional[BaseTool]:
        """加载工具包中的工具"""
        pass


# ============================================================================
# 内置工具来源
# ============================================================================


class BuiltinToolSource(ToolSource):
    """内置工具来源"""

    def __init__(self):
        self._tool_classes: Dict[str, Type[BaseTool]] = {}
        self._register_builtin_tools()

    def _register_builtin_tools(self):
        """注册内置工具类"""
        from .builtin import (
            CalculatorTool,
            ExecuteCommandTool,
            FetchUrlTool,
            GetCurrentTimeTool,
            ListDirectoryTool,
            ParseDateTool,
            ReadFileTool,
            SearchDocumentsTool,
            WebSearchTool,
            WriteFileTool,
        )

        tool_classes = [
            CalculatorTool,
            SearchDocumentsTool,
            WebSearchTool,
            GetCurrentTimeTool,
            ParseDateTool,
            ReadFileTool,
            WriteFileTool,
            ListDirectoryTool,
            FetchUrlTool,
            ExecuteCommandTool,
        ]

        for tool_class in tool_classes:
            self._tool_classes[tool_class.name] = tool_class

    def discover(self) -> List[ToolPackageInfo]:
        """发现内置工具"""
        packages = []
        for name, tool_class in self._tool_classes.items():
            packages.append(ToolPackageInfo(
                name=name,
                description=tool_class.description,
                source="builtin",
                tool_class=tool_class,
            ))
        return packages

    def load(self, package_info: ToolPackageInfo) -> Optional[BaseTool]:
        """加载内置工具"""
        if package_info.tool_class:
            try:
                return package_info.tool_class()
            except Exception as e:
                logger.warning(f"[BuiltinToolSource] 加载工具 {package_info.name} 失败: {e}")
        return None


# ============================================================================
# 本地插件来源
# ============================================================================


class LocalPluginSource(ToolSource):
    """本地插件来源 - ~/.jude_agent/plugins/"""

    def __init__(self, plugin_dir: Optional[Path] = None):
        self.plugin_dir = plugin_dir or self._get_plugin_dir()
        self._ensure_plugin_dir()

    def _get_plugin_dir(self) -> Path:
        """获取插件目录"""
        home = Path.home()
        if sys.platform == "win32":
            base = Path(os.environ.get("APPDATA", home / "AppData" / "Roaming"))
        else:
            base = home / ".local" / "share" if sys.platform == "darwin" else home / ".local"

        return base / "jude_agent" / "plugins"

    def _ensure_plugin_dir(self):
        """确保插件目录存在"""
        if not self.plugin_dir.exists():
            self.plugin_dir.mkdir(parents=True, exist_ok=True)
            logger.info(f"[LocalPluginSource] 创建插件目录: {self.plugin_dir}")

    def discover(self) -> List[ToolPackageInfo]:
        """发现本地插件"""
        packages = []

        if not self.plugin_dir.exists():
            return packages

        for item in self.plugin_dir.iterdir():
            if not item.is_dir() or item.name.startswith('_') or item.name.startswith('.'):
                continue

            # 检查是否有 __init__.py 或 tool.py
            init_file = item / "__init__.py"
            tool_file = item / "tool.py"

            if init_file.exists() or tool_file.exists():
                packages.append(ToolPackageInfo(
                    name=item.name,
                    source="local",
                    path=item,
                ))

        return packages

    def load(self, package_info: ToolPackageInfo) -> Optional[BaseTool]:
        """加载本地插件"""
        if not package_info.path:
            return None

        try:
            # 添加到 sys.path
            plugin_path = str(package_info.path)
            if plugin_path not in sys.path:
                sys.path.insert(0, plugin_path)

            # 尝试导入模块
            module_name = package_info.name

            # 优先尝试从 __init__.py 导入 Tool 类
            try:
                spec = importlib.util.spec_from_file_location(
                    module_name,
                    package_info.path / "__init__.py"
                )
                if spec and spec.loader:
                    module = importlib.util.module_from_spec(spec)
                    sys.modules[module_name] = module
                    spec.loader.exec_module(module)

                    # 查找 Tool 类
                    for attr_name in dir(module):
                        attr = getattr(module, attr_name)
                        if (isinstance(attr, type) and
                            issubclass(attr, BaseTool) and
                            attr is not BaseTool):
                            return attr()
            except (FileNotFoundError, AttributeError):
                pass

            # 尝试从 tool.py 导入
            tool_file = package_info.path / "tool.py"
            if tool_file.exists():
                spec = importlib.util.spec_from_file_location(
                    module_name,
                    tool_file
                )
                if spec and spec.loader:
                    module = importlib.util.module_from_spec(spec)
                    sys.modules[module_name] = module
                    spec.loader.exec_module(module)

                    for attr_name in dir(module):
                        attr = getattr(module, attr_name)
                        if (isinstance(attr, type) and
                            issubclass(attr, BaseTool) and
                            attr is not BaseTool):
                            return attr()

            logger.warning(f"[LocalPluginSource] 未在插件 {package_info.name} 中找到 Tool 类")
            return None

        except Exception as e:
            logger.warning(f"[LocalPluginSource] 加载插件 {package_info.name} 失败: {e}")
            return None


# ============================================================================
# pip 安装的工具来源
# ============================================================================


class PipToolSource(ToolSource):
    """pip 安装的工具来源 - 通过 entry_points 发现"""

    ENTRY_POINT_GROUP = "jude_agent.tools"

    def discover(self) -> List[ToolPackageInfo]:
        """通过 entry_points 发现工具"""
        packages = []

        try:
            from importlib.metadata import entry_points

            eps = entry_points()
            tool_eps = eps.get(self.ENTRY_POINT_GROUP, [])

            for ep in tool_eps:
                packages.append(ToolPackageInfo(
                    name=ep.name,
                    source="pip",
                ))

        except Exception as e:
            logger.debug(f"[PipToolSource] entry_points 查找失败: {e}")

        return packages

    def load(self, package_info: ToolPackageInfo) -> Optional[BaseTool]:
        """加载 pip 安装的工具"""
        try:
            from importlib.metadata import entry_points

            eps = entry_points()
            tool_eps = eps.get(self.ENTRY_POINT_GROUP, [])

            for ep in tool_eps:
                if ep.name == package_info.name:
                    # 加载 entry point
                    tool_class = ep.load()
                    return tool_class()

        except Exception as e:
            logger.warning(f"[PipToolSource] 加载工具 {package_info.name} 失败: {e}")

        return None


# ============================================================================
# MCP 工具来源
# ============================================================================


class MCPToolSource(ToolSource):
    """MCP 工具来源"""

    def __init__(self, mcp_config: Optional[Dict[str, Any]] = None):
        self.mcp_config = mcp_config or {}

    def discover(self) -> List[ToolPackageInfo]:
        """发现 MCP 工具"""
        packages = []

        for name, config in self.mcp_config.items():
            packages.append(ToolPackageInfo(
                name=f"mcp_{name}",
                description=config.get("description", f"MCP 服务器: {name}"),
                source="mcp",
            ))

        return packages

    def load(self, package_info: ToolPackageInfo) -> Optional[BaseTool]:
        """MCP 工具需要单独处理，这里返回 None"""
        # MCP 工具通过 MCPAdapter 单独处理
        return None


# ============================================================================
# 工具注册表
# ============================================================================


class PluginToolRegistry:
    """插件式工具注册表

    支持多来源工具发现和加载：
    - builtin: 内置工具
    - local: 本地插件 (~/.jude_agent/plugins/)
    - pip: pip 安装的工具 (entry_points)
    - mcp: MCP 服务器配置
    """

    def __init__(self):
        self._tools: Dict[str, BaseTool] = {}
        self._sources: Dict[str, ToolSource] = {}
        self._packages: Dict[str, ToolPackageInfo] = {}
        self._enabled_sources: List[str] = ["builtin"]

        # 初始化来源
        self._init_sources()

    def _init_sources(self):
        """初始化工具来源"""
        self._sources["builtin"] = BuiltinToolSource()
        self._sources["local"] = LocalPluginSource()
        self._sources["pip"] = PipToolSource()

    def enable_source(self, source: str):
        """启用工具来源"""
        if source not in self._enabled_sources:
            self._enabled_sources.append(source)

    def disable_source(self, source: str):
        """禁用工具来源"""
        if source in self._enabled_sources:
            self._enabled_sources.remove(source)

    def discover_all(self) -> List[ToolPackageInfo]:
        """发现所有来源的工具包"""
        all_packages = []

        for source_name in self._enabled_sources:
            if source_name in self._sources:
                try:
                    packages = self._sources[source_name].discover()
                    all_packages.extend(packages)

                    # 保存包信息
                    for pkg in packages:
                        self._packages[pkg.name] = pkg

                except Exception as e:
                    logger.warning(f"[PluginToolRegistry] 发现工具失败 ({source_name}): {e}")

        return all_packages

    def load_all(self, enabled_tools: Optional[List[str]] = None) -> ToolCollection:
        """加载所有启用的工具

        Args:
            enabled_tools: 启用的工具名称列表，None 表示全部加载
        """
        self._tools.clear()

        # 重新发现工具包
        self.discover_all()

        for name, package_info in self._packages.items():
            # 检查是否在启用列表中
            if enabled_tools and name not in enabled_tools:
                continue

            # 跳过 MCP（需要单独处理）
            if package_info.source == "mcp":
                continue

            # 加载工具
            tool = self._load_tool(package_info)
            if tool:
                self._tools[name] = tool
                logger.info(f"[PluginToolRegistry] 加载工具: {name} (来源: {package_info.source})")

        return ToolCollection(list(self._tools.values()))

    def _load_tool(self, package_info: ToolPackageInfo) -> Optional[BaseTool]:
        """加载单个工具"""
        source = self._sources.get(package_info.source)
        if source:
            try:
                return source.load(package_info)
            except Exception as e:
                logger.warning(f"[PluginToolRegistry] 加载工具 {package_info.name} 失败: {e}")
        return None

    def get_tool(self, name: str) -> Optional[BaseTool]:
        """获取工具"""
        return self._tools.get(name)

    def add_tool(self, tool: BaseTool):
        """添加工具"""
        self._tools[tool.name] = tool

    def remove_tool(self, name: str) -> bool:
        """移除工具"""
        if name in self._tools:
            del self._tools[name]
            return True
        return False

    @property
    def tools(self) -> Dict[str, BaseTool]:
        """获取所有工具"""
        return self._tools.copy()

    @property
    def packages(self) -> Dict[str, ToolPackageInfo]:
        """获取所有已发现的工具包"""
        return self._packages.copy()

    def get_collection(self) -> ToolCollection:
        """获取工具集合"""
        return ToolCollection(list(self._tools.values()))


# ============================================================================
# 全局注册表实例
# ============================================================================


_registry: Optional[PluginToolRegistry] = None


def get_plugin_registry() -> PluginToolRegistry:
    """获取全局插件注册表"""
    global _registry
    if _registry is None:
        _registry = PluginToolRegistry()
    return _registry


def reload_plugin_registry() -> PluginToolRegistry:
    """重新加载插件注册表"""
    global _registry
    _registry = PluginToolRegistry()
    return _registry
