"""Skill System — 技能注册、加载、匹配、执行

三层自由度：
- 高自由（原则层）：只有名称+描述，Agent 自由发挥
- 中自由（模板层）：有 prompt template，Agent 按模板填
- 低自由（脚本层）：有脚本/工具，Agent 调用执行
"""

from __future__ import annotations

import json
import logging
import re
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Tuple

logger = logging.getLogger("moziplus-v2.skill")


class SkillFreedom(str, Enum):
    HIGH = "high"       # 原则层
    MEDIUM = "medium"   # 模板层
    LOW = "low"         # 脚本层


@dataclass
class Skill:
    """技能描述"""
    id: str
    name: str
    description: str
    freedom: str = SkillFreedom.HIGH.value
    prompt_template: Optional[str] = None
    script_path: Optional[str] = None
    tags: List[str] = field(default_factory=list)
    input_schema: Optional[Dict[str, Any]] = None
    output_schema: Optional[Dict[str, Any]] = None
    enabled: bool = True
    created_at: Optional[str] = None
    updated_at: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        return {
            "id": self.id,
            "name": self.name,
            "description": self.description,
            "freedom": self.freedom,
            "prompt_template": self.prompt_template,
            "script_path": self.script_path,
            "tags": self.tags,
            "enabled": self.enabled,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> Skill:
        return cls(**{k: v for k, v in data.items()
                      if k in cls.__dataclass_fields__})


class SkillRegistry:
    """技能注册表"""

    def __init__(self, skills_dir: Optional[Path] = None):
        self._skills: Dict[str, Skill] = {}
        self.skills_dir = skills_dir

        if skills_dir and skills_dir.exists():
            self._load_from_dir(skills_dir)

    def _load_from_dir(self, dir_path: Path) -> None:
        """从目录加载 skill 定义（每个 .json 一个）"""
        for f in dir_path.glob("*.json"):
            try:
                data = json.loads(f.read_text())
                skill = Skill.from_dict(data)
                self._skills[skill.id] = skill
            except Exception:
                logger.warning("Failed to load skill from %s", f)

    def register(self, skill: Skill) -> str:
        self._skills[skill.id] = skill
        return skill.id

    def unregister(self, skill_id: str) -> bool:
        if skill_id in self._skills:
            del self._skills[skill_id]
            return True
        return False

    def get(self, skill_id: str) -> Optional[Skill]:
        return self._skills.get(skill_id)

    def list_skills(
        self,
        freedom: Optional[str] = None,
        tags: Optional[List[str]] = None,
        enabled_only: bool = True,
    ) -> List[Skill]:
        results = list(self._skills.values())

        if enabled_only:
            results = [s for s in results if s.enabled]

        if freedom:
            results = [s for s in results if s.freedom == freedom]

        if tags:
            results = [s for s in results
                       if any(t in s.tags for t in tags)]

        return results

    def match(
        self,
        query: str,
        task_type: Optional[str] = None,
        limit: int = 5,
    ) -> List[Tuple[Skill, float]]:
        """匹配技能

        Returns:
            [(Skill, relevance_score), ...]
        """
        q_lower = query.lower()
        scored = []

        for skill in self._skills.values():
            if not skill.enabled:
                continue
            score = 0.0

            # 名称匹配（权重最高）
            if q_lower in skill.name.lower():
                score += 3.0

            # 描述匹配
            if q_lower in skill.description.lower():
                score += 2.0

            # 标签匹配
            if task_type and task_type.lower() in skill.tags:
                score += 1.5

            for tag in skill.tags:
                if tag.lower() in q_lower:
                    score += 1.0

            if score > 0:
                scored.append((skill, score))

        scored.sort(key=lambda x: x[1], reverse=True)
        return scored[:limit]

    def count(self) -> int:
        return len(self._skills)


class SkillExecutor:
    """技能执行器"""

    def __init__(
        self,
        registry: Optional[SkillRegistry] = None,
        allow_scripts: bool = False,
    ):
        self.registry = registry or SkillRegistry()
        self.allow_scripts = allow_scripts
        self._execution_log: List[Dict[str, Any]] = []

    def build_prompt(
        self,
        skill_id: str,
        variables: Optional[Dict[str, str]] = None,
    ) -> Optional[str]:
        """构建 skill prompt"""
        skill = self.registry.get(skill_id)
        if not skill:
            return None

        if skill.freedom == SkillFreedom.HIGH.value:
            return f"## Skill: {skill.name}\n{skill.description}\n\nPrinciple: Apply the above guidelines."

        if skill.freedom == SkillFreedom.MEDIUM.value:
            template = skill.prompt_template or skill.description
            if variables:
                for key, value in variables.items():
                    template = template.replace(f"{{{{{key}}}}}", value)
            return template

        if skill.freedom == SkillFreedom.LOW.value:
            parts = [f"## Skill: {skill.name}"]
            parts.append(skill.description)
            if skill.script_path:
                parts.append(f"Execute: `{skill.script_path}`")
            return "\n".join(parts)

        return skill.description

    def execute(
        self,
        skill_id: str,
        context: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """执行技能

        Returns:
            {"status": "success"|"error", "prompt": str, "skill": str}
        """
        skill = self.registry.get(skill_id)
        if not skill:
            return {"status": "error", "error": f"Skill not found: {skill_id}"}

        if not skill.enabled:
            return {"status": "error", "error": f"Skill disabled: {skill_id}"}

        prompt = self.build_prompt(skill_id, context)

        # 脚本执行需要显式允许
        if skill.freedom == SkillFreedom.LOW.value and skill.script_path:
            if not self.allow_scripts:
                return {
                    "status": "error",
                    "error": "Script execution not allowed",
                    "prompt": prompt,
                }

        result = {
            "status": "success",
            "prompt": prompt,
            "skill": skill_id,
            "freedom": skill.freedom,
        }

        self._execution_log.append({
            "skill_id": skill_id,
            "timestamp": datetime.utcnow().isoformat(),
            "status": result["status"],
        })

        return result

    @property
    def execution_log(self) -> List[Dict[str, Any]]:
        return list(self._execution_log)
