from __future__ import annotations

import tempfile
import unittest
from pathlib import Path

from src.bundled_skills import (
    find_bundled_skill,
    format_skills_for_system_prompt,
    get_bundled_skills,
    load_directory_skills,
    load_project_skills,
)
from src.agent_runtime import LocalCodingAgent
from src.agent_types import AgentRuntimeConfig, ModelConfig


class BundledSkillsTests(unittest.TestCase):
    def test_directory_skills_are_loaded_with_metadata(self) -> None:
        skills = {skill.name: skill for skill in get_bundled_skills()}
        self.assertIn('verify', skills)
        self.assertEqual(skills['verify'].source, 'directory')
        self.assertIn('bash', skills['verify'].allowed_tools)

    def test_directory_skill_aliases_are_resolved(self) -> None:
        skill = find_bundled_skill('config-help')
        self.assertIsNotNone(skill)
        assert skill is not None
        self.assertEqual(skill.name, 'update-config')
        self.assertEqual(skill.source, 'directory')

    def test_directory_skill_prompt_appends_invocation_args(self) -> None:
        skill = find_bundled_skill('verify')
        self.assertIsNotNone(skill)
        assert skill is not None
        prompt = skill.get_prompt(None, 'focus: tests')  # type: ignore[arg-type]
        self.assertIn('Verify that the recent code changes work correctly.', prompt)
        self.assertIn('## Invocation Arguments', prompt)
        self.assertIn('focus: tests', prompt)

    def test_custom_directory_skill_loader(self) -> None:
        with tempfile.TemporaryDirectory() as tmp_dir:
            root = Path(tmp_dir)
            skill_dir = root / 'sample'
            skill_dir.mkdir()
            (skill_dir / 'SKILL.md').write_text(
                (
                    '---\n'
                    'name: sample\n'
                    'description: Sample directory skill.\n'
                    'aliases: s1, s2\n'
                    'allowed_tools: read_file, write_file\n'
                    '---\n'
                    'Use sample instructions.\n'
                ),
                encoding='utf-8',
            )
            skills = load_directory_skills(root)
        self.assertEqual(len(skills), 1)
        self.assertEqual(skills[0].name, 'sample')
        self.assertEqual(skills[0].aliases, ('s1', 's2'))
        self.assertEqual(skills[0].allowed_tools, ('read_file', 'write_file'))

    def test_skills_prompt_includes_directory_skill(self) -> None:
        rendered = format_skills_for_system_prompt()
        self.assertIn('verify', rendered)
        self.assertIn('update-config', rendered)

    def test_skills_prompt_respects_enabled_skill_filter(self) -> None:
        rendered = format_skills_for_system_prompt(
            enabled_skill_names=('verify', 'product-data')
        )
        self.assertIn('verify', rendered)
        self.assertIn('product-data', rendered)
        self.assertNotIn('online-mining', rendered)

    def test_project_skills_are_loaded_from_workspace_skills_directory(self) -> None:
        with tempfile.TemporaryDirectory() as tmp_dir:
            workspace = Path(tmp_dir)
            skill_dir = workspace / 'skills' / 'project-skill'
            skill_dir.mkdir(parents=True)
            (skill_dir / 'SKILL.md').write_text(
                (
                    '---\n'
                    'name: project-skill\n'
                    'description: Project-maintained skill.\n'
                    'aliases: local-skill\n'
                    'allowed_tools: read_file\n'
                    '---\n'
                    'Project skill body.\n'
                ),
                encoding='utf-8',
            )
            skills = get_bundled_skills(workspace)
            resolved = find_bundled_skill('local-skill', cwd=workspace)
        by_name = {skill.name: skill for skill in skills}
        self.assertIn('project-skill', by_name)
        self.assertEqual(by_name['project-skill'].source, 'project')
        self.assertIsNotNone(resolved)
        assert resolved is not None
        self.assertEqual(resolved.name, 'project-skill')

    def test_project_skills_override_bundled_directory_skills(self) -> None:
        with tempfile.TemporaryDirectory() as tmp_dir:
            workspace = Path(tmp_dir)
            skill_dir = workspace / 'skills' / 'verify'
            skill_dir.mkdir(parents=True)
            (skill_dir / 'SKILL.md').write_text(
                (
                    '---\n'
                    'name: verify\n'
                    'description: Project override for verify.\n'
                    '---\n'
                    'Project verify body.\n'
                ),
                encoding='utf-8',
            )
            skill = find_bundled_skill('verify', cwd=workspace)
        self.assertIsNotNone(skill)
        assert skill is not None
        self.assertEqual(skill.source, 'project')
        self.assertIn('Project verify body.', skill.get_prompt(None, ''))  # type: ignore[arg-type]

    def test_load_project_skills_returns_empty_without_skills_directory(self) -> None:
        with tempfile.TemporaryDirectory() as tmp_dir:
            self.assertEqual(load_project_skills(Path(tmp_dir)), ())

    def test_agent_skill_tool_executes_project_skill_from_cwd(self) -> None:
        with tempfile.TemporaryDirectory() as tmp_dir:
            workspace = Path(tmp_dir)
            skill_dir = workspace / 'skills' / 'project-run'
            skill_dir.mkdir(parents=True)
            (skill_dir / 'SKILL.md').write_text(
                (
                    '---\n'
                    'name: project-run\n'
                    'description: Project runnable skill.\n'
                    '---\n'
                    'Run project skill body.\n'
                ),
                encoding='utf-8',
            )
            agent = LocalCodingAgent(
                model_config=ModelConfig(model='test'),
                runtime_config=AgentRuntimeConfig(cwd=workspace),
            )
            result = agent._execute_skill({'skill': 'project-run', 'args': 'demo'})
        self.assertTrue(result.ok)
        self.assertIn('Run project skill body.', result.content)
        self.assertEqual(result.metadata.get('source'), 'project')
        self.assertIn('SKILL.md', str(result.metadata.get('skill_path')))


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