"""describe_skill_invocation() — recovering what the user typed from a /skill turn.

A /skill invocation expands into a message that embeds the whole skill body. Any
surface that summarizes a user turn from its raw content (session titles, sidebar
previews, the /rewind picker) otherwise shows the SKILL's prose as if the user had
written it.

Every case here builds the scaffolding with the real message builders rather than
a hand-written literal, so the description can't silently drift from the format it
parses.
"""

import pytest

import agent.skill_bundles as skill_bundles
import agent.skill_commands as skill_commands
import tools.skills_tool as skills_tool
from agent.skill_commands import (
    SKILL_EXCERPT_JOINT,
    SKILL_SCAFFOLD_SQL_LIKE,
    describe_skill_invocation,
)

SKILL_BODY = "Kick off a task in a fresh isolated git worktree instead of the current checkout."


def _write_skill(skills_dir, name, body=SKILL_BODY):
    skill_dir = skills_dir / name
    skill_dir.mkdir(parents=True, exist_ok=True)
    (skill_dir / "SKILL.md").write_text(
        f"---\nname: {name}\ndescription: Description for {name}\n---\n\n# {name}\n\n{body}\n"
    )
    return skill_dir


def _write_bundle(bundles_dir, slug, skills):
    bundles_dir.mkdir(parents=True, exist_ok=True)
    lines = [f"name: {slug}", "skills:"]
    lines.extend(f"  - {skill}" for skill in skills)
    (bundles_dir / f"{slug}.yaml").write_text("\n".join(lines) + "\n")


@pytest.fixture()
def skills(tmp_path, monkeypatch):
    """Install a 'work' skill and a 'demo' bundle, with caches reset."""
    skills_dir = tmp_path / "skills"
    bundles_dir = tmp_path / "skill-bundles"
    _write_skill(skills_dir, "work")
    _write_skill(skills_dir, "clean", body="Polish your own diff by hand.")
    _write_bundle(bundles_dir, "demo", ["work", "clean"])

    monkeypatch.setattr(skills_tool, "SKILLS_DIR", skills_dir)
    monkeypatch.setenv("HERMES_BUNDLES_DIR", str(bundles_dir))
    monkeypatch.setattr(skill_commands, "_skill_commands", {})
    monkeypatch.setattr(skill_commands, "_skill_commands_platform", None)
    monkeypatch.setattr(skill_bundles, "_bundles_cache", {})
    monkeypatch.setattr(skill_bundles, "_bundles_cache_mtime", None)
    skill_commands.scan_skill_commands()
    skill_bundles.scan_bundles()
    return skills_dir


class TestDescribeSkillInvocation:

    def test_ignores_non_string_content(self):
        assert describe_skill_invocation(None) is None
        assert describe_skill_invocation([{"type": "text", "text": "hi"}]) is None

    def test_recovers_the_typed_instruction(self, skills):
        message = skill_commands.build_skill_invocation_message(
            "/work", user_instruction="fix the title leak"
        )
        assert describe_skill_invocation(message) == "/work — fix the title leak"





    def test_bundle_carries_its_typed_keys(self, skills):
        result = skill_bundles.build_bundle_invocation_message(
            "/demo", user_instruction="fix the title leak"
        )
        assert result is not None
        message, _, _ = result
        described = describe_skill_invocation(message)
        assert described.endswith("— fix the title leak")
        assert "worktree" not in described



class TestExcerptedScaffolding:
    """Preview queries hand over a head+tail excerpt, not the whole message."""

    def _excerpt(self, message, window=400):
        """Mirror what _preview_raw_select() hands to _shape_preview()."""
        flat = message.replace("\n", " ").replace("\r", " ")
        if len(message) <= window * 2:
            return flat[: window * 2]
        return flat[:window] + SKILL_EXCERPT_JOINT + flat[-window:]

    def test_excerpt_still_recovers_the_instruction(self, skills):
        message = skill_commands.build_skill_invocation_message(
            "/work", user_instruction="fix the title leak"
        )
        described = describe_skill_invocation(self._excerpt(message))
        assert described == "/work — fix the title leak"

    def test_description_never_runs_across_the_joint(self, skills):
        # A long body pushes the head window into the middle of the skill text;
        # the instruction is only present on the tail side.
        skill_md = skills / "work" / "SKILL.md"
        skill_md.write_text(skill_md.read_text().replace(SKILL_BODY, "filler line.\n" * 200))
        skill_commands._skill_commands = {}
        skill_commands._skill_commands_platform = None
        skill_commands.scan_skill_commands()
        message = skill_commands.build_skill_invocation_message(
            "/work", user_instruction="fix the title leak"
        )
        described = describe_skill_invocation(self._excerpt(message))
        assert SKILL_EXCERPT_JOINT not in described
        assert "filler line" not in described


class TestSqlLikePattern:
    def test_matches_the_prefix_the_builders_emit(self, skills):
        message = skill_commands.build_skill_invocation_message("/work")
        assert message.startswith(SKILL_SCAFFOLD_SQL_LIKE.rstrip("%"))

    def test_carries_no_like_wildcards_needing_escape(self):
        # The pattern is interpolated into SQL without an ESCAPE clause, so the
        # literal part must not contain '%' or '_'.
        assert "%" not in SKILL_SCAFFOLD_SQL_LIKE[:-1]
        assert "_" not in SKILL_SCAFFOLD_SQL_LIKE[:-1]
