"""Skill 加载器

支持多种来源的 Skill 发现和加载：
- builtin: 内置 Skill
- local: 本地 Skill (~/.jude_agent/skills/)
- pip: pip 安装的 Skill (entry_points)
"""

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 Dict, List, Optional, Type

from .base import BaseSkill, SkillCollection

logger = logging.getLogger("agent")


# ============================================================================
# Skill 包信息
# ============================================================================


@dataclass
class SkillPackageInfo:
    """Skill 包信息"""
    name: str
    version: str = "0.1.0"
    description: str = ""
    author: str = ""
    source: str = "builtin"  # builtin, local, pip
    path: Optional[Path] = None
    skill_class: Optional[Type[BaseSkill]] = None


class SkillSource(ABC):
    """Skill 来源抽象基类"""

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

    @abstractmethod
    def load(self, package_info: SkillPackageInfo) -> Optional[BaseSkill]:
        """加载 Skill 包中的 Skill"""
        pass


# ============================================================================
# 内置 Skill 来源
# ============================================================================


class BuiltinSkillSource(SkillSource):
    """内置 Skill 来源"""

    def __init__(self):
        self._skill_classes: Dict[str, Type[BaseSkill]] = {}
        self._register_builtin_skills()

    def _register_builtin_skills(self):
        """注册内置 Skill 类"""
        from .builtin import (
            HelpSkill,
            ReloadSkill,
            StatusSkill,
        )

        skill_classes = [
            HelpSkill,
            ReloadSkill,
            StatusSkill,
        ]

        for skill_class in skill_classes:
            self._skill_classes[skill_class.name] = skill_class

    def discover(self) -> List[SkillPackageInfo]:
        """发现内置 Skill"""
        packages = []
        for name, skill_class in self._skill_classes.items():
            packages.append(SkillPackageInfo(
                name=name,
                description=skill_class.description,
                source="builtin",
                skill_class=skill_class,
            ))
        return packages

    def load(self, package_info: SkillPackageInfo) -> Optional[BaseSkill]:
        """加载内置 Skill"""
        if package_info.skill_class:
            try:
                return package_info.skill_class()
            except Exception as e:
                logger.warning(f"[BuiltinSkillSource] 加载 Skill {package_info.name} 失败: {e}")
        return None


# ============================================================================
# 本地 Skill 来源
# ============================================================================


class LocalSkillSource(SkillSource):
    """本地 Skill 来源 - ~/.jude_agent/skills/"""

    def __init__(self, skill_dir: Optional[Path] = None):
        self.skill_dir = skill_dir or self._get_skill_dir()
        self._ensure_skill_dir()

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

        return base / "jude_agent" / "skills"

    def _ensure_skill_dir(self):
        """确保 Skill 目录存在"""
        if not self.skill_dir.exists():
            self.skill_dir.mkdir(parents=True, exist_ok=True)
            logger.info(f"[LocalSkillSource] 创建 Skill 目录: {self.skill_dir}")

    def discover(self) -> List[SkillPackageInfo]:
        """发现本地 Skill"""
        packages = []

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

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

            # 检查是否有 __init__.py 或 skill.py
            init_file = item / "__init__.py"
            skill_file = item / "skill.py"

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

        return packages

    def load(self, package_info: SkillPackageInfo) -> Optional[BaseSkill]:
        """加载本地 Skill"""
        if not package_info.path:
            return None

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

            module_name = package_info.name

            # 优先尝试从 __init__.py 导入 Skill 类
            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)

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

            # 尝试从 skill.py 导入
            skill_file = package_info.path / "skill.py"
            if skill_file.exists():
                spec = importlib.util.spec_from_file_location(
                    module_name,
                    skill_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, BaseSkill) and
                            attr is not BaseSkill):
                            return attr()

            logger.warning(f"[LocalSkillSource] 未在 Skill {package_info.name} 中找到 Skill 类")
            return None

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


# ============================================================================
# pip 安装的 Skill 来源
# ============================================================================


class PipSkillSource(SkillSource):
    """pip 安装的 Skill 来源 - 通过 entry_points 发现"""

    ENTRY_POINT_GROUP = "jude_agent.skills"

    def discover(self) -> List[SkillPackageInfo]:
        """通过 entry_points 发现 Skill"""
        packages = []

        try:
            from importlib.metadata import entry_points

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

            for ep in skill_eps:
                packages.append(SkillPackageInfo(
                    name=ep.name,
                    source="pip",
                ))

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

        return packages

    def load(self, package_info: SkillPackageInfo) -> Optional[BaseSkill]:
        """加载 pip 安装的 Skill"""
        try:
            from importlib.metadata import entry_points

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

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

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

        return None


# ============================================================================
# Skill 加载器
# ============================================================================


class SkillLoader:
    """Skill 加载器

    支持多来源 Skill 发现和加载：
    - builtin: 内置 Skill
    - local: 本地 Skill (~/.jude_agent/skills/)
    - pip: pip 安装的 Skill (entry_points)
    """

    def __init__(self):
        self._sources: Dict[str, SkillSource] = {}
        self._packages: Dict[str, SkillPackageInfo] = {}
        self._enabled_sources: List[str] = ["builtin"]

        # 初始化来源
        self._init_sources()

    def _init_sources(self):
        """初始化 Skill 来源"""
        self._sources["builtin"] = BuiltinSkillSource()
        self._sources["local"] = LocalSkillSource()
        self._sources["pip"] = PipSkillSource()

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

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

    def discover_all(self) -> List[SkillPackageInfo]:
        """发现所有来源的 Skill 包"""
        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"[SkillLoader] 发现 Skill 失败 ({source_name}): {e}")

        return all_packages

    def load_all(self, enabled_skills: Optional[List[str]] = None) -> SkillCollection:
        """加载所有启用的 Skill

        Args:
            enabled_skills: 启用的 Skill 名称列表，None 表示全部加载
        """
        # 重新发现 Skill 包
        self.discover_all()

        skills = []

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

            # 加载 Skill
            skill = self._load_skill(package_info)
            if skill:
                skills.append(skill)
                logger.debug(f"[SkillLoader] 加载 Skill: {name} (来源: {package_info.source})")

        return SkillCollection(skills)

    def _load_skill(self, package_info: SkillPackageInfo) -> Optional[BaseSkill]:
        """加载单个 Skill"""
        source = self._sources.get(package_info.source)
        if source:
            try:
                return source.load(package_info)
            except Exception as e:
                logger.warning(f"[SkillLoader] 加载 Skill {package_info.name} 失败: {e}")
        return None

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


# ============================================================================
# 全局加载器实例
# ============================================================================


_loader: Optional[SkillLoader] = None


def get_skill_loader() -> SkillLoader:
    """获取全局 Skill 加载器"""
    global _loader
    if _loader is None:
        _loader = SkillLoader()
    return _loader


def reload_skill_loader() -> SkillLoader:
    """重新加载 Skill 加载器"""
    global _loader
    _loader = SkillLoader()
    return _loader
