"""Unit tests for the #140 skill_validator module."""
from __future__ import annotations

from pathlib import Path

import pytest

from active_claude_github_mcp.skill_validator import (
    REQUIRED_FRONTMATTER_KEYS,
    REQUIRED_TOPICS,
    ValidationError,
    main,
    parse_frontmatter,
    validate_skill,
)


_GOOD_FRONTMATTER = (
    "---\n"
    "name: test-skill\n"
    "description: A skill for tests.\n"
    "---\n"
    "\n"
    "# Body\n"
)


def _make_skill(
    tmp_path: Path,
    *,
    frontmatter: str = _GOOD_FRONTMATTER,
    topics: tuple[str, ...] | None = None,
    omit_skill_md: bool = False,
) -> Path:
    skill_dir = tmp_path / "skill"
    skill_dir.mkdir()
    if not omit_skill_md:
        (skill_dir / "SKILL.md").write_text(frontmatter)
    if topics is None:
        topics = REQUIRED_TOPICS
    for t in topics:
        (skill_dir / t).write_text(f"# {t}\n")
    return skill_dir


class TestParseFrontmatter:
    def test_extracts_keys(self) -> None:
        keys = parse_frontmatter(_GOOD_FRONTMATTER)
        assert keys["name"] == "test-skill"
        assert keys["description"] == "A skill for tests."

    def test_skips_lines_without_colon(self) -> None:
        # Blank lines and bare text inside frontmatter are tolerated.
        body = (
            "---\n"
            "name: x\n"
            "\n"
            "just-a-comment\n"
            "description: y\n"
            "---\n"
        )
        keys = parse_frontmatter(body)
        assert keys == {"name": "x", "description": "y"}

    def test_missing_opening_delimiter_raises(self) -> None:
        with pytest.raises(ValidationError, match="must start with"):
            parse_frontmatter("name: test\n")

    def test_unterminated_frontmatter_raises(self) -> None:
        with pytest.raises(ValidationError, match="not terminated"):
            parse_frontmatter("---\nname: test\n")

    def test_value_with_colon_preserves_remainder(self) -> None:
        # ``description: a: b`` should yield ``"a: b"`` as the value
        # (partition on first colon).
        body = "---\nname: x\ndescription: tag: ok\n---\n"
        keys = parse_frontmatter(body)
        assert keys["description"] == "tag: ok"


class TestValidateSkill:
    def test_happy_path(self, tmp_path: Path) -> None:
        skill_dir = _make_skill(tmp_path)
        validate_skill(skill_dir)  # does not raise

    def test_missing_skill_md_raises(self, tmp_path: Path) -> None:
        skill_dir = _make_skill(tmp_path, omit_skill_md=True)
        with pytest.raises(ValidationError, match="SKILL.md not found"):
            validate_skill(skill_dir)

    def test_missing_required_key_raises(self, tmp_path: Path) -> None:
        bad = "---\nname: only-name\n---\n"
        skill_dir = _make_skill(tmp_path, frontmatter=bad)
        with pytest.raises(ValidationError, match="missing required key: 'description'"):
            validate_skill(skill_dir)

    def test_empty_required_key_raises(self, tmp_path: Path) -> None:
        bad = "---\nname: x\ndescription:\n---\n"
        skill_dir = _make_skill(tmp_path, frontmatter=bad)
        with pytest.raises(ValidationError, match="key 'description' is empty"):
            validate_skill(skill_dir)

    def test_missing_topic_file_raises(self, tmp_path: Path) -> None:
        topics = tuple(t for t in REQUIRED_TOPICS if t != "tools.md")
        skill_dir = _make_skill(tmp_path, topics=topics)
        with pytest.raises(ValidationError, match="topic file missing: tools.md"):
            validate_skill(skill_dir)


class TestMain:
    def test_returns_zero_on_valid(self, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
        skill_dir = _make_skill(tmp_path)
        rc = main(["--skill-dir", str(skill_dir)])
        assert rc == 0
        out = capsys.readouterr().out
        assert "OK:" in out

    def test_returns_one_on_invalid(self, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
        skill_dir = _make_skill(tmp_path, omit_skill_md=True)
        rc = main(["--skill-dir", str(skill_dir)])
        assert rc == 1
        err = capsys.readouterr().err
        assert "::error::" in err
        assert "SKILL.md not found" in err


def test_required_frontmatter_keys_minimum() -> None:
    # Sanity: name + description are the keys Claude Code documents
    # as required (#140's CI-contract reference). If this set ever
    # shrinks, the validator no longer enforces the contract.
    assert "name" in REQUIRED_FRONTMATTER_KEYS
    assert "description" in REQUIRED_FRONTMATTER_KEYS
