import sys
import tempfile
import unittest
from pathlib import Path


TOOLS_SCRIPTS_DIR = Path(__file__).resolve().parents[1]
if str(TOOLS_SCRIPTS_DIR) not in sys.path:
    sys.path.insert(0, str(TOOLS_SCRIPTS_DIR))

from symlink_test_utils import symlink_or_skip

import sync_microsoft_skills as sms


class SyncMicrosoftSkillsSecurityTests(unittest.TestCase):
    def test_sanitize_flat_name_rejects_path_traversal(self):
        sanitized = sms.sanitize_flat_name("../../.ssh", "fallback-name")
        self.assertEqual(sanitized, "fallback-name")

    def test_find_skills_ignores_symlinks_outside_clone(self):
        with tempfile.TemporaryDirectory() as temp_dir:
            root = Path(temp_dir)
            skills_dir = root / "skills"
            skills_dir.mkdir()

            safe_skill = root / ".github" / "skills" / "safe-skill"
            safe_skill.mkdir(parents=True)
            (safe_skill / "SKILL.md").write_text("---\nname: safe-skill\n---\n", encoding="utf-8")
            symlink_or_skip(self, safe_skill, skills_dir / "safe-skill", target_is_directory=True)

            outside = Path(tempfile.mkdtemp())
            try:
                (outside / "SKILL.md").write_text("---\nname: leaked\n---\n", encoding="utf-8")
                symlink_or_skip(self, outside, skills_dir / "escape", target_is_directory=True)

                entries = sms.find_skills_in_directory(root)
                relative_paths = {str(entry["relative_path"]) for entry in entries}

                self.assertEqual(relative_paths, {"safe-skill"})
            finally:
                for child in outside.iterdir():
                    child.unlink()
                outside.rmdir()

    def test_find_skills_ignores_symlinked_skill_markdown(self):
        with tempfile.TemporaryDirectory() as temp_dir:
            root = Path(temp_dir)
            skills_dir = root / "skills"
            skills_dir.mkdir()

            safe_skill = skills_dir / "safe-skill"
            safe_skill.mkdir()
            (safe_skill / "SKILL.md").write_text("---\nname: safe-skill\n---\n", encoding="utf-8")

            linked_skill = skills_dir / "linked-skill"
            linked_skill.mkdir()

            outside = Path(tempfile.mkdtemp())
            try:
                target = outside / "SKILL.md"
                target.write_text("---\nname: escaped\n---\n", encoding="utf-8")
                symlink_or_skip(self, target, linked_skill / "SKILL.md")

                entries = sms.find_skills_in_directory(root)
                relative_paths = {str(entry["relative_path"]) for entry in entries}

                self.assertEqual(relative_paths, {"safe-skill"})
            finally:
                target.unlink()
                outside.rmdir()

    def test_find_github_skills_ignores_symlinked_directories(self):
        with tempfile.TemporaryDirectory() as temp_dir:
            root = Path(temp_dir)
            github_skills = root / ".github" / "skills"
            github_skills.mkdir(parents=True)

            safe_skill = github_skills / "safe-skill"
            safe_skill.mkdir()
            (safe_skill / "SKILL.md").write_text("---\nname: safe-skill\n---\n", encoding="utf-8")

            outside = Path(tempfile.mkdtemp())
            try:
                escaped = outside / "escaped-skill"
                escaped.mkdir()
                (escaped / "SKILL.md").write_text("---\nname: escaped\n---\n", encoding="utf-8")
                symlink_or_skip(self, escaped, github_skills / "escape", target_is_directory=True)

                entries = sms.find_github_skills(root, set())
                relative_paths = {str(entry["relative_path"]) for entry in entries}

                self.assertEqual(relative_paths, {".github/skills/safe-skill"})
            finally:
                for child in escaped.iterdir():
                    child.unlink()
                escaped.rmdir()
                outside.rmdir()

    def test_find_github_skills_ignores_symlinked_skill_markdown(self):
        with tempfile.TemporaryDirectory() as temp_dir:
            root = Path(temp_dir)
            github_skills = root / ".github" / "skills"
            github_skills.mkdir(parents=True)

            safe_skill = github_skills / "safe-skill"
            safe_skill.mkdir()
            (safe_skill / "SKILL.md").write_text("---\nname: safe-skill\n---\n", encoding="utf-8")

            linked_skill = github_skills / "linked-skill"
            linked_skill.mkdir()

            outside = Path(tempfile.mkdtemp())
            try:
                target = outside / "SKILL.md"
                target.write_text("---\nname: escaped\n---\n", encoding="utf-8")
                symlink_or_skip(self, target, linked_skill / "SKILL.md")

                entries = sms.find_github_skills(root, set())
                relative_paths = {str(entry["relative_path"]) for entry in entries}

                self.assertEqual(relative_paths, {".github/skills/safe-skill"})
            finally:
                target.unlink()
                outside.rmdir()

    def test_find_plugin_skills_ignores_symlinked_skill_markdown(self):
        with tempfile.TemporaryDirectory() as temp_dir:
            root = Path(temp_dir)
            github_plugins = root / ".github" / "plugins"
            github_plugins.mkdir(parents=True)

            safe_plugin = github_plugins / "safe-plugin"
            safe_plugin.mkdir()
            (safe_plugin / "SKILL.md").write_text("---\nname: safe-plugin\n---\n", encoding="utf-8")

            linked_plugin = github_plugins / "linked-plugin"
            linked_plugin.mkdir()

            outside = Path(tempfile.mkdtemp())
            try:
                target = outside / "SKILL.md"
                target.write_text("---\nname: escaped\n---\n", encoding="utf-8")
                symlink_or_skip(self, target, linked_plugin / "SKILL.md")

                entries = sms.find_plugin_skills(root, set())
                relative_paths = {str(entry["relative_path"]) for entry in entries}

                self.assertEqual(relative_paths, {"plugins/safe-plugin"})
            finally:
                target.unlink()
                outside.rmdir()

    def test_copy_license_ignores_symlinked_license(self):
        with tempfile.TemporaryDirectory() as temp_dir:
            root = Path(temp_dir)
            source = root / "source"
            docs = root / "docs"
            source.mkdir()

            outside = root / "outside-license.txt"
            outside.write_text("secret license payload", encoding="utf-8")
            symlink_or_skip(self, outside, source / "LICENSE")

            previous_docs_dir = sms.DOCS_DIR
            sms.DOCS_DIR = docs
            try:
                sms.copy_license(source)
            finally:
                sms.DOCS_DIR = previous_docs_dir

            self.assertFalse((docs / "LICENSE-MICROSOFT").exists())

    def test_copy_license_copies_regular_license(self):
        with tempfile.TemporaryDirectory() as temp_dir:
            root = Path(temp_dir)
            source = root / "source"
            docs = root / "docs"
            source.mkdir()
            (source / "LICENSE").write_text("regular license", encoding="utf-8")

            previous_docs_dir = sms.DOCS_DIR
            sms.DOCS_DIR = docs
            try:
                sms.copy_license(source)
            finally:
                sms.DOCS_DIR = previous_docs_dir

            self.assertEqual((docs / "LICENSE-MICROSOFT").read_text(encoding="utf-8"), "regular license")


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