#!/usr/bin/env python3
"""Tests for gen-skill-docs.py — TDD (RED → GREEN)"""

import importlib.util
import json
import sys
from pathlib import Path
from unittest.mock import patch

import pytest

# gen-skill-docs.py 는 하이픈 파일명이므로 importlib 으로 임포트
_SCRIPTS_DIR = Path(__file__).parent.parent
_MODULE_PATH = _SCRIPTS_DIR / "gen-skill-docs.py"
spec = importlib.util.spec_from_file_location("gen_skill_docs", _MODULE_PATH)
assert spec is not None and spec.loader is not None
gsd = importlib.util.module_from_spec(spec)
sys.modules["gen_skill_docs"] = gsd
spec.loader.exec_module(gsd)  # type: ignore[union-attr]

REQUIRED_SECTIONS = ["# ", "## Description", "## Trigger", "## Usage", "## Examples", "## Files"]

COMPLIANT_SKILL_MD = """\
# MySkill

## Description
Does something useful.

## Trigger
When user asks.

## Usage
Call it like this.

## Examples
Example 1.

## Files
- skill.py
"""


# ---------------------------------------------------------------------------
# 1. 빈 스킬 디렉토리 → 빈 결과
# ---------------------------------------------------------------------------
class TestEmptySkillsDir:
    def test_empty_dir_returns_zero_skills(self, tmp_path):
        result = gsd.verify_skills(skills_dir=str(tmp_path))
        assert result["skills_checked"] == 0
        assert result["compliant"] == 0
        assert result["non_compliant"] == 0
        assert result["details"] == []

    def test_empty_dir_has_correct_schema(self, tmp_path):
        result = gsd.verify_skills(skills_dir=str(tmp_path))
        assert set(result.keys()) == {"skills_checked", "compliant", "non_compliant", "details"}


# ---------------------------------------------------------------------------
# 2. 표준 SKILL.md 검증 PASS
# ---------------------------------------------------------------------------
class TestCompliantSkill:
    def test_compliant_skill_passes_verify(self, tmp_path):
        skill_dir = tmp_path / "myskill"
        skill_dir.mkdir()
        (skill_dir / "SKILL.md").write_text(COMPLIANT_SKILL_MD)

        result = gsd.verify_skills(skills_dir=str(tmp_path))
        assert result["compliant"] == 1
        assert result["non_compliant"] == 0

    def test_compliant_skill_detail_status(self, tmp_path):
        skill_dir = tmp_path / "myskill"
        skill_dir.mkdir()
        (skill_dir / "SKILL.md").write_text(COMPLIANT_SKILL_MD)

        result = gsd.verify_skills(skills_dir=str(tmp_path))
        detail = result["details"][0]
        assert detail["status"] == "compliant"
        assert detail["missing_sections"] == []


# ---------------------------------------------------------------------------
# 3. 누락 섹션 감지
# ---------------------------------------------------------------------------
class TestMissingSections:
    def test_missing_examples_detected(self, tmp_path):
        skill_dir = tmp_path / "myskill"
        skill_dir.mkdir()
        content = COMPLIANT_SKILL_MD.replace("## Examples\nExample 1.\n\n", "")
        (skill_dir / "SKILL.md").write_text(content)

        result = gsd.verify_skills(skills_dir=str(tmp_path))
        detail = result["details"][0]
        assert "Examples" in detail["missing_sections"]

    def test_missing_files_detected(self, tmp_path):
        skill_dir = tmp_path / "myskill"
        skill_dir.mkdir()
        content = COMPLIANT_SKILL_MD.replace("## Files\n- skill.py\n", "")
        (skill_dir / "SKILL.md").write_text(content)

        result = gsd.verify_skills(skills_dir=str(tmp_path))
        detail = result["details"][0]
        assert "Files" in detail["missing_sections"]

    def test_multiple_missing_sections(self, tmp_path):
        skill_dir = tmp_path / "myskill"
        skill_dir.mkdir()
        (skill_dir / "SKILL.md").write_text("# MySkill\n\n## Description\nOnly this.\n")

        result = gsd.verify_skills(skills_dir=str(tmp_path))
        detail = result["details"][0]
        missing = detail["missing_sections"]
        assert "Trigger" in missing
        assert "Usage" in missing
        assert "Examples" in missing
        assert "Files" in missing

    def test_no_skill_md_all_sections_missing(self, tmp_path):
        skill_dir = tmp_path / "myskill"
        skill_dir.mkdir()
        # SKILL.md 없음

        result = gsd.verify_skills(skills_dir=str(tmp_path))
        assert result["non_compliant"] == 1
        detail = result["details"][0]
        assert len(detail["missing_sections"]) > 0

    def test_non_compliant_count_correct(self, tmp_path):
        for name in ["skill_a", "skill_b"]:
            d = tmp_path / name
            d.mkdir()
            (d / "SKILL.md").write_text("# Title\n")

        result = gsd.verify_skills(skills_dir=str(tmp_path))
        assert result["non_compliant"] == 2
        assert result["compliant"] == 0


# ---------------------------------------------------------------------------
# 4. 새 SKILL.md 생성
# ---------------------------------------------------------------------------
class TestGenerateSkillMd:
    def test_generate_creates_skill_md(self, tmp_path):
        skill_dir = tmp_path / "newskill"
        skill_dir.mkdir()

        gsd.generate_skill_md(skill_dir=str(skill_dir), skill_name="newskill")

        assert (skill_dir / "SKILL.md").exists()

    def test_generated_skill_md_passes_verify(self, tmp_path):
        skill_dir = tmp_path / "newskill"
        skill_dir.mkdir()
        gsd.generate_skill_md(skill_dir=str(skill_dir), skill_name="newskill")

        result = gsd.verify_skills(skills_dir=str(tmp_path))
        assert result["compliant"] == 1

    def test_generate_includes_all_sections(self, tmp_path):
        skill_dir = tmp_path / "s"
        skill_dir.mkdir()
        gsd.generate_skill_md(skill_dir=str(skill_dir), skill_name="s")
        content = (skill_dir / "SKILL.md").read_text()
        for section in ["## Description", "## Trigger", "## Usage", "## Examples", "## Files"]:
            assert section in content

    def test_generate_fills_skill_name(self, tmp_path):
        skill_dir = tmp_path / "awesome"
        skill_dir.mkdir()
        gsd.generate_skill_md(skill_dir=str(skill_dir), skill_name="awesome")
        content = (skill_dir / "SKILL.md").read_text()
        assert "awesome" in content


# ---------------------------------------------------------------------------
# 5. dry-run 모드 (파일 변경 없음)
# ---------------------------------------------------------------------------
class TestDryRun:
    def test_dry_run_does_not_create_file(self, tmp_path):
        skill_dir = tmp_path / "dryskill"
        skill_dir.mkdir()

        gsd.generate_skill_md(skill_dir=str(skill_dir), skill_name="dryskill", dry_run=True)

        assert not (skill_dir / "SKILL.md").exists()

    def test_dry_run_returns_preview_content(self, tmp_path):
        skill_dir = tmp_path / "dryskill"
        skill_dir.mkdir()

        preview = gsd.generate_skill_md(skill_dir=str(skill_dir), skill_name="dryskill", dry_run=True)

        assert preview is not None
        assert "## Description" in preview

    def test_dry_run_does_not_overwrite_existing(self, tmp_path):
        skill_dir = tmp_path / "existing"
        skill_dir.mkdir()
        original = "# Original\n"
        (skill_dir / "SKILL.md").write_text(original)

        gsd.generate_skill_md(skill_dir=str(skill_dir), skill_name="existing", dry_run=True)

        assert (skill_dir / "SKILL.md").read_text() == original


# ---------------------------------------------------------------------------
# 6. 특정 스킬만 처리
# ---------------------------------------------------------------------------
class TestSpecificSkill:
    def test_verify_specific_skill_only(self, tmp_path):
        for name in ["alpha", "beta", "gamma"]:
            d = tmp_path / name
            d.mkdir()
            (d / "SKILL.md").write_text(COMPLIANT_SKILL_MD)

        result = gsd.verify_skills(skills_dir=str(tmp_path), skill_name="alpha")
        assert result["skills_checked"] == 1
        assert result["details"][0]["skill"] == "alpha"

    def test_generate_specific_skill_only(self, tmp_path):
        for name in ["alpha", "beta"]:
            (tmp_path / name).mkdir()

        gsd.generate_skill_md(skill_dir=str(tmp_path / "alpha"), skill_name="alpha")

        assert (tmp_path / "alpha" / "SKILL.md").exists()
        assert not (tmp_path / "beta" / "SKILL.md").exists()


# ---------------------------------------------------------------------------
# 7. 존재하지 않는 스킬 → graceful 에러
# ---------------------------------------------------------------------------
class TestNonExistentSkill:
    def test_verify_nonexistent_skills_dir_raises(self):
        with pytest.raises(gsd.SkillsError):
            gsd.verify_skills(skills_dir="/no/such/path/skills")

    def test_verify_specific_missing_skill_raises(self, tmp_path):
        with pytest.raises(gsd.SkillsError):
            gsd.verify_skills(skills_dir=str(tmp_path), skill_name="ghost")

    def test_generate_nonexistent_dir_raises(self):
        with pytest.raises(gsd.SkillsError):
            gsd.generate_skill_md(skill_dir="/no/such/skill", skill_name="ghost")


# ---------------------------------------------------------------------------
# 8. verify 결과 JSON 직렬화 가능
# ---------------------------------------------------------------------------
class TestJsonSerializable:
    def test_verify_result_is_json_serializable(self, tmp_path):
        skill_dir = tmp_path / "myskill"
        skill_dir.mkdir()
        (skill_dir / "SKILL.md").write_text(COMPLIANT_SKILL_MD)

        result = gsd.verify_skills(skills_dir=str(tmp_path))
        dumped = json.dumps(result)
        reloaded = json.loads(dumped)
        assert reloaded["skills_checked"] == result["skills_checked"]

    def test_verify_details_contain_required_keys(self, tmp_path):
        skill_dir = tmp_path / "myskill"
        skill_dir.mkdir()
        (skill_dir / "SKILL.md").write_text(COMPLIANT_SKILL_MD)

        result = gsd.verify_skills(skills_dir=str(tmp_path))
        detail = result["details"][0]
        assert "skill" in detail
        assert "missing_sections" in detail
        assert "status" in detail


# ---------------------------------------------------------------------------
# 9. generate_skills 전체 디렉토리 처리
# ---------------------------------------------------------------------------
class TestGenerateAllSkills:
    def test_generate_all_creates_missing_skill_mds(self, tmp_path):
        for name in ["a", "b", "c"]:
            (tmp_path / name).mkdir()

        gsd.generate_all_skills(skills_dir=str(tmp_path))

        for name in ["a", "b", "c"]:
            assert (tmp_path / name / "SKILL.md").exists()

    def test_generate_all_preserves_compliant_existing(self, tmp_path):
        skill_dir = tmp_path / "existing"
        skill_dir.mkdir()
        (skill_dir / "SKILL.md").write_text(COMPLIANT_SKILL_MD)

        gsd.generate_all_skills(skills_dir=str(tmp_path))

        content = (skill_dir / "SKILL.md").read_text()
        assert content == COMPLIANT_SKILL_MD


# ---------------------------------------------------------------------------
# 10. check_section 헬퍼
# ---------------------------------------------------------------------------
class TestCheckSection:
    def test_section_present(self):
        content = "# Title\n\n## Description\nHello\n"
        assert gsd.check_section(content, "Description") is True

    def test_section_absent(self):
        content = "# Title\n\n## Description\nHello\n"
        assert gsd.check_section(content, "Usage") is False

    def test_empty_content_all_absent(self):
        for section in ["Description", "Trigger", "Usage", "Examples", "Files"]:
            assert gsd.check_section("", section) is False
