"""LLM向けツール定義 (JSON Schema) & ツール実行ハンドラ."""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

from .discovery import discover_skills
from .indexer import SkillIndex, build_index
from .types import SearchResult, SkillMeta

# ツール定義 (OpenAI function calling 形式)
_TOOL_DEFINITIONS = [
    {
        "type": "function",
        "function": {
            "name": "list_skills",
            "description": (
                "利用可能なスキルの一覧を取得します。"
                "各スキルの名前と概要説明が返されます。"
                "タスクに関連するスキルを見つけるために使用してください。"
            ),
            "parameters": {
                "type": "object",
                "properties": {},
                "required": [],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "read_skill",
            "description": (
                "指定されたスキルの SKILL.md 全文を読み込みます。"
                "スキルの詳細な使い方やワークフローを理解するために使用します。"
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    "skill_name": {
                        "type": "string",
                        "description": "読み込むスキルの名前 (list_skills で取得した名前を使用)",
                    },
                },
                "required": ["skill_name"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "search_skills",
            "description": (
                "スキルのドキュメント全体を全文検索します。"
                "BM25スコアリングで関連度の高い結果を返します。"
                "特定のトピックやキーワードに関する情報を素早く見つけるために使用します。"
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "検索クエリ文字列",
                    },
                    "top_k": {
                        "type": "integer",
                        "description": "返却する最大件数 (デフォルト: 5)",
                        "default": 5,
                    },
                    "skill_name": {
                        "type": "string",
                        "description": "特定スキルに限定する場合のスキル名 (省略時は全スキル対象)",
                    },
                },
                "required": ["query"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "read_resource",
            "description": (
                "スキル内のリソースファイルを読み込みます。"
                "SKILL.md 以外の補足ドキュメント、テンプレート、データファイル等のコンテンツを取得します。"
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    "skill_name": {
                        "type": "string",
                        "description": "リソースが属するスキルの名前",
                    },
                    "resource_name": {
                        "type": "string",
                        "description": "リソースファイル名 (例: 'docs/api/README.md')",
                    },
                },
                "required": ["skill_name", "resource_name"],
            },
        },
    },
]


class SkillSearch:
    """LLM フレームワーク非依存のスキル検索エンジン.

    skills/ ディレクトリを走査し、全文検索インデックスを構築。
    LLM に渡すためのツール定義と、ツール呼び出しの実行ハンドラを提供する。

    使い方:
        ```python
        from skill_search import SkillSearch

        ss = SkillSearch(skills_dirs=["./skills"])

        # ツール定義を LLM に渡す
        tools = ss.get_tool_definitions()

        # ツール呼び出しを実行
        result = ss.call_tool("search_skills", {"query": "figma api"})
        ```
    """

    def __init__(
        self,
        skills_dirs: list[str | Path],
        max_depth: int | None = 3,
        system_prompt: bool = True,
    ) -> None:
        """初期化.

        Args:
            skills_dirs: スキルが格納されたディレクトリのリスト.
            max_depth: SKILL.md の探索最大深度.
            system_prompt: True の場合、get_system_prompt() で利用可能スキル一覧を生成.
        """
        self._skills: dict[str, SkillMeta] = {}
        self._index: SkillIndex | None = None
        self._system_prompt_enabled = system_prompt

        # スキルの発見とインデックス構築
        all_skills: list[SkillMeta] = []
        for dir_path in skills_dirs:
            skills = discover_skills(dir_path, max_depth=max_depth)
            for skill in skills:
                if skill.name not in self._skills:
                    self._skills[skill.name] = skill
                    all_skills.append(skill)

        # 全文検索インデックスの構築
        if all_skills:
            self._index = build_index(all_skills)

        # ツールハンドラのマッピング
        self._handlers: dict[str, Any] = {
            "list_skills": self._handle_list_skills,
            "read_skill": self._handle_read_skill,
            "search_skills": self._handle_search_skills,
            "read_resource": self._handle_read_resource,
        }

    @property
    def skills(self) -> dict[str, SkillMeta]:
        """登録済みスキルの辞書."""
        return self._skills

    @property
    def index(self) -> SkillIndex | None:
        """検索インデックス."""
        return self._index

    def get_tool_definitions(self) -> list[dict[str, Any]]:
        """LLM に渡すツール定義を取得.

        OpenAI function calling 形式の JSON Schema を返す。
        Anthropic API にもそのまま使用可能。

        Returns:
            ツール定義のリスト.
        """
        return _TOOL_DEFINITIONS

    def get_system_prompt(self) -> str:
        """スキル一覧を含むシステムプロンプトを生成.

        Returns:
            LLM のシステムプロンプトに追加するテキスト.
        """
        if not self._skills:
            return ""

        lines = [
            "# Available Skills",
            "",
            "以下のスキルが利用可能です。タスクに関連するスキルを search_skills や read_skill で活用してください。",
            "",
        ]

        for name, skill in sorted(self._skills.items()):
            lines.append(f"- **{name}**: {skill.description}")

        lines.extend(
            [
                "",
                "## ツールの使い方",
                "",
                "1. `search_skills(query)` — キーワードでスキル内を全文検索",
                "2. `read_skill(skill_name)` — スキルの詳細な指示書を読む",
                "3. `read_resource(skill_name, resource_name)` — 補足リソースを読む",
                "4. `list_skills()` — 利用可能なスキル一覧を再確認",
            ]
        )

        return "\n".join(lines)

    def call_tool(self, tool_name: str, arguments: dict[str, Any] | None = None) -> str:
        """LLM から呼び出されたツールを実行.

        Args:
            tool_name: ツール名 (list_skills, read_skill, search_skills, read_resource).
            arguments: ツール引数の辞書.

        Returns:
            ツール実行結果の文字列.

        Raises:
            ValueError: 不明なツール名の場合.
        """
        if tool_name not in self._handlers:
            available = ", ".join(sorted(self._handlers.keys()))
            raise ValueError(f"Unknown tool: '{tool_name}'. Available: {available}")

        handler = self._handlers[tool_name]
        return handler(**(arguments or {}))

    def _handle_list_skills(self) -> str:
        """list_skills ツールのハンドラ."""
        if not self._skills:
            return json.dumps({"skills": []}, ensure_ascii=False)

        skills_list = []
        for name, skill in sorted(self._skills.items()):
            entry: dict[str, Any] = {
                "name": name,
                "description": skill.description,
            }
            if skill.resources:
                entry["resources"] = [r.name for r in skill.resources]
            skills_list.append(entry)

        return json.dumps({"skills": skills_list}, ensure_ascii=False, indent=2)

    def _handle_read_skill(self, skill_name: str) -> str:
        """read_skill ツールのハンドラ."""
        if skill_name not in self._skills:
            available = ", ".join(sorted(self._skills.keys())) or "none"
            return json.dumps(
                {"error": f"Skill '{skill_name}' not found. Available: {available}"},
                ensure_ascii=False,
            )

        skill = self._skills[skill_name]
        result = {
            "name": skill.name,
            "description": skill.description,
            "content": skill.content,
        }
        if skill.resources:
            result["resources"] = [r.name for r in skill.resources]

        return json.dumps(result, ensure_ascii=False, indent=2)

    def _handle_search_skills(
        self,
        query: str,
        top_k: int = 5,
        skill_name: str | None = None,
    ) -> str:
        """search_skills ツールのハンドラ."""
        if self._index is None:
            return json.dumps(
                {"results": [], "message": "No index available"}, ensure_ascii=False
            )

        results = self._index.search(query, top_k=top_k, skill_name=skill_name)
        return _format_search_results(results)

    def _handle_read_resource(self, skill_name: str, resource_name: str) -> str:
        """read_resource ツールのハンドラ."""
        if skill_name not in self._skills:
            return json.dumps(
                {"error": f"Skill '{skill_name}' not found."},
                ensure_ascii=False,
            )

        skill = self._skills[skill_name]

        # パストラバーサル防止: resource_name に ".." が含まれる場合は拒否
        if ".." in resource_name.split("/") or ".." in resource_name.split("\\"):
            return json.dumps(
                {"error": "Invalid resource name: path traversal detected."},
                ensure_ascii=False,
            )

        # リソースを名前で検索 (discovery 時に登録済みのもののみ許可)
        for resource in skill.resources:
            if resource.name == resource_name:
                # 追加安全検証: 読み取り対象がスキルディレクトリ内か確認
                skill_root = Path(skill.path).resolve()
                resource_resolved = Path(resource.path).resolve()
                if (
                    not str(resource_resolved).startswith(str(skill_root) + "/")
                    and resource_resolved != skill_root
                ):
                    return json.dumps(
                        {
                            "error": "Access denied: resource path is outside skill directory."
                        },
                        ensure_ascii=False,
                    )

                try:
                    content = Path(resource.path).read_text(encoding="utf-8")
                    return json.dumps(
                        {
                            "skill_name": skill_name,
                            "resource_name": resource_name,
                            "content": content,
                        },
                        ensure_ascii=False,
                        indent=2,
                    )
                except (OSError, UnicodeDecodeError) as e:
                    return json.dumps(
                        {"error": f"Failed to read resource: {e}"},
                        ensure_ascii=False,
                    )

        available = [r.name for r in skill.resources]
        return json.dumps(
            {
                "error": f"Resource '{resource_name}' not found in skill '{skill_name}'.",
                "available": available,
            },
            ensure_ascii=False,
        )


def _format_search_results(results: list[SearchResult]) -> str:
    """検索結果を JSON 文字列にフォーマット."""
    formatted = []
    for r in results:
        formatted.append(
            {
                "skill_name": r.skill_name,
                "file_path": r.file_path,
                "section": r.section,
                "score": round(r.score, 4),
                "snippet": r.snippet,
            }
        )
    return json.dumps({"results": formatted}, ensure_ascii=False, indent=2)
