import inspect
import re
from pathlib import Path
from typing import Any

from pydantic_ai import Agent, RunContext
from pydantic_ai_skills import (
    CallableSkillScriptExecutor,
    SkillsDirectory,
    SkillsToolset,
)

from app.agent.deps import Deps

from .scripts import uv_script_executor

if "auto_reload" not in inspect.signature(SkillsToolset.__init__).parameters:
    _original_init = SkillsToolset.__init__
    _original_get_instructions = SkillsToolset.get_instructions

    def _patched_init(
        self: SkillsToolset, *args: Any, auto_reload: bool = False, **kwargs: Any
    ) -> None:
        _original_init(self, *args, **kwargs)
        self._auto_reload = auto_reload

    def _reload(self: SkillsToolset) -> None:
        new_skills: dict[str, Any] = {}
        for skill_dir in self._skill_directories:
            for skill in skill_dir.get_skills().values():
                if skill.name not in new_skills:
                    new_skills[skill.name] = skill
        self._skills = new_skills

    async def _patched_get_instructions(
        self: SkillsToolset, *args: Any, **kwargs: Any
    ) -> str | None:
        if getattr(self, "_auto_reload", False):
            _reload(self)
        return await _original_get_instructions(self, *args, **kwargs)

    SkillsToolset.__init__ = _patched_init
    SkillsToolset.reload = _reload  # type: ignore[attr-defined]
    SkillsToolset.get_instructions = _patched_get_instructions


def keep_only_docstring_summary(toolset: SkillsToolset) -> SkillsToolset:
    for tool in toolset.tools.values():
        if tool.description:
            if match := re.search(r"<summary>(.*?)</summary>", tool.description, flags=re.DOTALL):
                tool.description = match.group(1).strip()
    return toolset


def build_skills_toolset(
    global_skills_dir: Path,
    user_skills_dir: Path,
    *,
    executor: CallableSkillScriptExecutor | None = None,
) -> SkillsToolset:
    if executor is None:
        executor = CallableSkillScriptExecutor(func=uv_script_executor)
    user_skills_dir.mkdir(parents=True, exist_ok=True)

    skills_toolset = SkillsToolset(
        directories=[
            SkillsDirectory(path=global_skills_dir, script_executor=executor),
            SkillsDirectory(path=user_skills_dir, script_executor=executor),
        ],
        auto_reload=True,
        exclude_tools=["list_skills"],
    )
    return keep_only_docstring_summary(skills_toolset)


def configure_skill_usage_toolset(
    agent: Agent[Deps, str],
    skills_dir: Path,
) -> Agent[Deps, str]:
    executor = CallableSkillScriptExecutor(func=uv_script_executor)
    toolsets_by_user: dict[Path, SkillsToolset] = {}

    def resolve_toolset(ctx: RunContext[Deps]) -> SkillsToolset:
        user_skills_dir = ctx.deps.skills_dir
        skills_toolset = toolsets_by_user.get(user_skills_dir)
        if skills_toolset is None:
            skills_toolset = build_skills_toolset(
                skills_dir,
                user_skills_dir,
                executor=executor,
            )
            toolsets_by_user[user_skills_dir] = skills_toolset
        return skills_toolset

    @agent.toolset
    def skills_toolset(ctx: RunContext[Deps]) -> SkillsToolset:
        return resolve_toolset(ctx)

    @agent.instructions
    async def _execute_instructions(ctx: RunContext[Deps]) -> str | None:
        skills_toolset = resolve_toolset(ctx)
        base = await skills_toolset.get_instructions(ctx)
        if base is None:
            return None
        return f"## Skills\n\n{base}"

    return agent
