"""Routing coverage test — every shipped skill must appear in the routing model.

Ensures that when a new skill is added, it can't silently fall out of
the CLAUDE.md intent families or the concierge routing tables.
"""

import os
import re
import sys
import unittest

PLUGIN_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Skills that are invoked directly (not through conversational routing)
DIRECT_INVOKE_ONLY = {
    "course-concierge",  # IS the router, not routed to
}


def _get_primary_skill_dirs():
    """Return set of skill directory names."""
    skills_dir = os.path.join(PLUGIN_ROOT, "skills")
    all_dirs = set()
    for name in os.listdir(skills_dir):
        if os.path.isdir(os.path.join(skills_dir, name)):
            all_dirs.add(name)
    return all_dirs


def _extract_skill_refs_from_file(filepath):
    """Extract backtick-quoted skill names from a markdown file."""
    with open(filepath, "r", encoding="utf-8") as f:
        content = f.read()
    # Match `skill-name` patterns (backtick-quoted, lowercase with hyphens)
    refs = set(re.findall(r'`([a-z][-a-z]+)`', content))
    return refs


class TestRoutingCoverage(unittest.TestCase):
    """Every primary skill must be referenced in the routing model."""

    def test_all_skills_in_claude_md(self):
        """Every primary skill appears in CLAUDE.md intent families or routing."""
        claude_md = os.path.join(PLUGIN_ROOT, "CLAUDE.md")
        refs = _extract_skill_refs_from_file(claude_md)
        primary = _get_primary_skill_dirs() - DIRECT_INVOKE_ONLY

        missing = primary - refs
        self.assertEqual(
            missing, set(),
            f"Skills missing from CLAUDE.md routing: {sorted(missing)}"
        )

    def test_all_skills_in_concierge(self):
        """Every primary skill appears in concierge intent families or mappings."""
        concierge = os.path.join(PLUGIN_ROOT, "skills", "course-concierge", "SKILL.md")
        refs = _extract_skill_refs_from_file(concierge)
        primary = _get_primary_skill_dirs() - DIRECT_INVOKE_ONLY

        missing = primary - refs
        self.assertEqual(
            missing, set(),
            f"Skills missing from concierge routing: {sorted(missing)}"
        )

    def test_no_phantom_skills_in_routing(self):
        """Routing shouldn't reference skill names that don't exist as directories."""
        claude_md = os.path.join(PLUGIN_ROOT, "CLAUDE.md")
        concierge = os.path.join(PLUGIN_ROOT, "skills", "course-concierge", "SKILL.md")

        all_refs = _extract_skill_refs_from_file(claude_md) | _extract_skill_refs_from_file(concierge)
        all_dirs = _get_primary_skill_dirs()

        # Filter to only things that look like skill names (not scope labels, etc.)
        # Skill names have hyphens and are >3 chars
        skill_like_refs = {r for r in all_refs if "-" in r and len(r) > 3}

        phantoms = skill_like_refs - all_dirs
        # Allow known non-skill backtick refs
        known_non_skills = {
            "course-config.json", "course-memory.md", "build-progress.json",
            "push-to-canvas", "post-write-verify", "staging-manager",
            "progress-tracker", "backup-manager", "edit-content",
            "fix-accessibility", "fix-headings", "generate-alt-text",
            "python-dotenv", "remediation-brief.json", "cross-course",
        }
        phantoms -= known_non_skills

        self.assertEqual(
            phantoms, set(),
            f"Routing references skills that don't exist: {sorted(phantoms)}"
        )

    def test_skill_count_matches_expectation(self):
        """Guard against silent skill additions or deletions."""
        primary = _get_primary_skill_dirs()
        # Current count: 28 skills (compatibility aliases were removed).
        self.assertEqual(
            len(primary), 28,
            f"Expected 28 skills, found {len(primary)}: "
            f"{sorted(primary)}"
        )
        self.assertIn("alignment", primary)
        self.assertIn("assurance-audit", primary)

    def test_alignment_trigger_phrases_route_to_alignment(self):
        """Alignment review language must be documented in the routing contract."""
        claude_md = os.path.join(PLUGIN_ROOT, "CLAUDE.md")
        with open(claude_md, "r", encoding="utf-8") as f:
            content = f.read().lower()

        self.assertIn("`alignment`", content)
        for phrase in (
            "is my course aligned",
            "check clo coverage",
            "do my assessments map to objectives",
            "alignment audit",
        ):
            self.assertIn(phrase, content)

    def test_template_trigger_phrases_route(self):
        """Canvas template trigger phrases must be documented in the routing contract."""
        claude_md = os.path.join(PLUGIN_ROOT, "CLAUDE.md")
        with open(claude_md, "r", encoding="utf-8") as f:
            content = f.read().lower()

        for phrase in (
            "use sdo jd template",
            "sdo-jdo template",
            "use asu online qm template",
            "qm template",
            "standards/canvas-templates/sdo-jdo-online-template.md",
            "standards/canvas-templates/asu-online-qm-template.md",
        ):
            self.assertIn(phrase, content)

    def test_skill_md_files_carry_template_triggers(self):
        """Canvas template trigger phrases must be documented in routing-aware skills."""
        for skill in ("course-mockup", "course-build", "course-config"):
            skill_md = os.path.join(PLUGIN_ROOT, "skills", skill, "SKILL.md")
            with open(skill_md, "r", encoding="utf-8") as f:
                content = f.read().lower()

            self.assertIn("sdo jd template", content)
            self.assertIn("asu online qm template", content)

    def test_body_write_skills_document_staging_preview(self):
        """Canvas body-write skills must document the staging-server preview gate."""
        for skill in (
            "assignment-generator",
            "discussion-generator",
            "update-module",
            "course-build",
            "syllabus-generator",
            "rubric-creator",
        ):
            skill_md = os.path.join(PLUGIN_ROOT, "skills", skill, "SKILL.md")
            with open(skill_md, "r", encoding="utf-8") as f:
                content = f.read().lower()

            self.assertIn("ensure_staging_server", content)
            self.assertIn("127.0.0.1", content)


if __name__ == "__main__":
    unittest.main()
