"""
ServerSkillsMiddleware - Skills 工具化实现
========================================

本模块提供兼容 LangChain/DeepAgents 标准的技能中间件实现。

核心设计原则：Skills 工具化
--------------------------
1. ServerSkillsMiddleware：提供 skill 工具（不注入系统提示词）
2. skill 工具：在工具描述中列出所有可用技能（动态描述）
3. 工具被调用时返回完整 SKILL.md 内容

本模块为 langchain_agentx 内部实现，不依赖 langchain_agentx 以外的代码。
"""
from pathlib import Path
from typing import List, Callable, Awaitable
import time

from langchain.agents.middleware.types import ModelRequest, ModelResponse
from langgraph.runtime import Runtime
from langchain_core.tools import tool

from .base_skills_middleware import (
    BaseSkillsMiddleware,
    SkillsState,
    SkillsStateUpdate,
)
from .server_skill_loader import list_skills, SkillMetadata

import logging

logger = logging.getLogger(__name__)


# 系统提示词模板（用于渐进披露 + 强制规则）
SKILLS_SYSTEM_PROMPT_TEMPLATE = """
## 技能系统

以下技能可通过 `skill` 工具使用：

{skills_list}

**使用规则（强制）**：
- 处理任何任务前，先快速浏览上述技能列表，判断是否有**明显匹配当前问题**的技能；
- 如有匹配技能，必须先调用一次 `skill(skill_name="<技能名>")` 获取该技能文档，然后按文档的流程与输出结构完成本轮任务；
- 如确无合适技能，本轮可以不调用 `skill`，按通用代码分析规则作答；
- 每轮起步只选定一个"主技能"；除非 SKILL.md 明确建议，否则避免在同一轮无目的地调用多个不同技能。
"""


class ServerSkillsMiddleware(BaseSkillsMiddleware):
    """
    服务端技能中间件 - Skills 工具化实现

    继承自 BaseSkillsMiddleware，加载 skill/server_skill/ 目录下的通用技能。

    核心设计：Skills 工具化 + 渐进披露
    ------------------------------------
    1. 系统提示词：简洁地列出技能名称和描述（渐进披露第一步）
    2. skill 工具：工具描述中强调"强制性"并列出技能
    3. 工具被调用：返回完整 SKILL.md（渐进披露第二步）
    4. 直接读取本地文件，不使用虚拟文件系统

    职责：
    -----
    1. 加载 SKILL.md 格式的技能元数据
    2. 注入简洁的技能列表到系统提示词（名称 + 描述）
    3. 创建 skill 工具（工具描述中强调强制性）
    4. 工具被调用时返回完整 SKILL.md 内容
    """

    def __init__(
        self,
        *,
        enabled_skills: List[str] | None = None,
    ):
        """
        初始化 ServerSkillsMiddleware

        Args:
            enabled_skills: 技能白名单（None=全部启用，[]= 禁用全部）
        """
        # 调用基类初始化
        super().__init__(
            enabled_skills=enabled_skills,
        )

        # ⭐ 必须在 __init__ 中创建 skill 工具：框架在构建 agent 时从 middleware.tools 收集工具并绑定到模型，
        # 若在 before_agent 中才创建，构建时 self.tools 为空，skill 不会被加入，导致 LLM 无法调用。
        skills = list_skills(self.skills_dir)
        if self.enabled_skills is not None:
            skills = [s for s in skills if s["name"] in self.enabled_skills]
        self._skills_metadata = skills
        self.tools = [self._create_skill_tool(skills)] if skills else []
        logger.info(
            "ServerSkillsMiddleware: 初始化完成，skill 工具已创建（skills=%d）",
            len(skills),
        )

    def _get_skills_dir(self) -> Path:
        """获取通用技能目录"""
        current_file = Path(__file__).resolve()
        project_root = current_file.parent.parent.parent
        return project_root / "skill" / "server_skill"

    def _format_skills_list(self, skills: List[SkillMetadata]) -> str:
        """格式化技能列表（用于系统提示词）

        Args:
            skills: 技能元数据列表

        Returns:
            格式化的技能列表字符串
        """
        if not skills:
            return "（暂无可用技能）"

        lines = []
        for skill in skills:
            lines.append(f"- **{skill['name']}**: {skill['description']}")

        return "\n".join(lines)

    def _create_skill_tool(self, skills_metadata: List[SkillMetadata]):
        """创建 skill 工具（动态描述）

        Args:
            skills_metadata: 技能元数据列表

        Returns:
            skill 工具
        """
        skills_dir = self.skills_dir

        # ========== 1. 动态生成工具描述 ==========
        description_parts = [
            "在主对话流程内执行一项技能。",
            "",
            "当用户要求你执行任务时，先检查是否存在匹配的可用技能。技能具备专属能力与领域专业知识。",
            "",
            "调用方式：",
            "- 传入技能名称使用本工具",
            "- 示例：skill_name=\"architecture_analysis\"",
            "",
            "重要规则：",
            "- 若存在匹配用户请求的技能，此为**强制性要求**：在生成任务相关的其他响应前，必须先调用对应技能工具",
            "- 严禁仅口头提及技能，却不实际执行本工具调用",
            "- 若已加载某个技能（工具返回了技能文档），请直接按照文档指导执行，无需重复调用",
            "",
        ]

        description = "\n".join(description_parts)

        # ========== 2. 定义工具函数 ==========
        @tool
        def skill(skill_name: str) -> str:
            """执行一项技能（此 docstring 会被 description 参数覆盖）

            Args:
                skill_name: 技能名称（如 "architecture_analysis"）

            Returns:
                技能的完整指导文档（Markdown 格式）
            """
            start_ts = time.perf_counter()
            skill_path = skills_dir / skill_name / "SKILL.md"

            try:
                if not skill_path.exists():
                    raise ValueError(
                        f"未找到技能文档：skill_name='{skill_name}', "
                        f"期望路径='{skill_path}'"
                    )

                # 读取 SKILL.md
                content = skill_path.read_text(encoding="utf-8")
                duration_ms = (time.perf_counter() - start_ts) * 1000

                # ========== 3. 格式化返回 ==========
                skill_dir = skills_dir / skill_name
                output = f"""## Skill: {skill_name}

**Base directory**: {skill_dir}

{content}"""

                logger.info(
                    "skill tool executed: name='%s', path='%s', size=%d chars, duration=%.2f ms",
                    skill_name,
                    str(skill_path),
                    len(content),
                    duration_ms,
                )
                return output

            except Exception as e:
                duration_ms = (time.perf_counter() - start_ts) * 1000
                logger.error(
                    "skill tool error: name='%s', path='%s', duration=%.2f ms, error=%s",
                    skill_name,
                    str(skill_path),
                    duration_ms,
                    str(e),
                )
                raise ValueError(f"加载技能失败：{e}") from e

        # ========== 4. 设置动态描述 ==========
        skill.description = description
        return skill

    def before_agent(
        self,
        state: SkillsState,
        runtime: Runtime
    ) -> SkillsStateUpdate | None:
        """在 Agent 执行前将技能元数据写入 state，供 wrap_model_call 注入系统提示词。

        skill 工具已在 __init__ 中创建并加入 self.tools，此处仅返回 SkillsStateUpdate。
        """

        return SkillsStateUpdate(skills_metadata=self._skills_metadata)

    def wrap_model_call(
        self,
        request: ModelRequest,
        handler: Callable[[ModelRequest], ModelResponse],
    ) -> ModelResponse:
        """每次 model 调用时注入简洁的技能列表

        Args:
            request: Model 请求
            handler: 原始处理器

        Returns:
            Model 响应
        """
        # 获取技能元数据（优先从 state，否则用 __init__ 时缓存的 _skills_metadata）
        skills_metadata = getattr(request, 'state', {}).get("skills_metadata", None)
        if skills_metadata is None:
            skills_metadata = getattr(self, '_skills_metadata', [])

        # 格式化技能列表
        skills_list = self._format_skills_list(skills_metadata)

        # 生成技能section（简洁版）
        skills_section = SKILLS_SYSTEM_PROMPT_TEMPLATE.format(
            skills_list=skills_list
        )

        # 注入到系统提示
        system_prompt = getattr(request, 'system_prompt', '')
        if system_prompt:
            system_prompt = system_prompt + "\n\n" + skills_section
        else:
            system_prompt = skills_section

        # 创建新的请求
        try:
            return handler(request.override(system_prompt=system_prompt))
        except AttributeError:
            request.system_prompt = system_prompt
            return handler(request)

    async def awrap_model_call(
        self,
        request: ModelRequest,
        handler: Callable[[ModelRequest], Awaitable[ModelResponse]],
    ) -> ModelResponse:
        """异步版本的 wrap_model_call

        Args:
            request: Model 请求
            handler: 异步处理器

        Returns:
            Model 响应
        """
        # 获取技能元数据（优先从 state，否则用 __init__ 时缓存的 _skills_metadata）
        skills_metadata = getattr(request, 'state', {}).get("skills_metadata", None)
        if skills_metadata is None:
            skills_metadata = getattr(self, '_skills_metadata', [])

        # 格式化技能列表
        skills_list = self._format_skills_list(skills_metadata)

        # 生成技能section（简洁版）
        skills_section = SKILLS_SYSTEM_PROMPT_TEMPLATE.format(
            skills_list=skills_list
        )

        # 注入到系统提示
        system_prompt = getattr(request, 'system_prompt', '')
        if system_prompt:
            system_prompt = system_prompt + "\n\n" + skills_section
        else:
            system_prompt = skills_section

        # 创建新的请求
        try:
            return await handler(request.override(system_prompt=system_prompt))
        except AttributeError:
            request.system_prompt = system_prompt
            return await handler(request)
