"""
Smoke tests for skill tools functional behavior.

Tests: Tool invocation (instructions, references, scripts), script execution
with shebang/extension detection, metrics tracking, LocalSkills full pipeline,
multiple loaders, and knowledge base search tool registration.
"""

import json
import tempfile
from pathlib import Path

from upsonic.skills.skill import Skill
from upsonic.skills.loader import InlineSkills, LocalSkills, BuiltinSkills
from upsonic.skills.skills import Skills


def _make_skill(name="test-skill", description="A test skill"):
    return Skill(
        name=name, description=description,
        instructions=f"Instructions for {name}",
        source_path="", scripts=["run.sh"], references=["ref.txt"],
    )


def _make_skills(*names):
    return Skills(loaders=[InlineSkills(
        [_make_skill(n, f"Description of {n}") for n in names]
    )])


def _make_local_skill_dir(base, name, *, shebang=True):
    """Create a real skill directory on disk for LocalSkills testing."""
    skill_dir = Path(base) / name
    skill_dir.mkdir(parents=True)
    (skill_dir / "SKILL.md").write_text(
        f"---\nname: {name}\ndescription: Skill {name}\n---\nInstructions for {name}"
    )
    scripts_dir = skill_dir / "scripts"
    scripts_dir.mkdir()
    if shebang:
        (scripts_dir / "hello.py").write_text("#!/usr/bin/env python3\nprint('hello from skill')")
    else:
        (scripts_dir / "hello.py").write_text("print('hello from skill')")
    (scripts_dir / "greet.sh").write_text("#!/bin/bash\necho 'hi'")
    refs_dir = skill_dir / "references"
    refs_dir.mkdir()
    (refs_dir / "guide.txt").write_text("Reference guide content for testing.")
    return skill_dir


# ---------------------------------------------------------------------------
# Tool Invocation
# ---------------------------------------------------------------------------

class TestSkillTools:
    def test_get_skill_instructions_tool(self):
        skills = _make_skills("instr-tool")
        tools = skills.get_tools()
        instr_fn = tools[0]
        result = instr_fn(skill_name="instr-tool")
        parsed = json.loads(result)
        assert parsed["skill_name"] == "instr-tool"
        assert "instructions" in parsed

    def test_get_skill_instructions_not_found(self):
        skills = _make_skills("exists")
        tools = skills.get_tools()
        instr_fn = tools[0]
        result = instr_fn(skill_name="nonexistent")
        parsed = json.loads(result)
        assert "error" in parsed

    def test_get_skill_reference_tool(self):
        with tempfile.TemporaryDirectory() as d:
            _make_local_skill_dir(d, "ref-skill")
            skills = Skills(loaders=[LocalSkills(d)])
            tools = skills.get_tools()
            ref_fn = tools[1]
            result = ref_fn(skill_name="ref-skill", reference_path="guide.txt")
            parsed = json.loads(result)
            assert "content" in parsed or "error" not in parsed

    def test_get_skill_script_tool_read(self):
        with tempfile.TemporaryDirectory() as d:
            _make_local_skill_dir(d, "script-skill")
            skills = Skills(loaders=[LocalSkills(d)])
            tools = skills.get_tools()
            script_fn = tools[2]
            result = script_fn(skill_name="script-skill", script_path="hello.py", execute=False)
            parsed = json.loads(result)
            assert "content" in parsed or "script_content" in parsed

    def test_metrics_increment_on_tool_use(self):
        skills = _make_skills("metric-track")
        tools = skills.get_tools()
        instr_fn = tools[0]
        metrics_before = skills.get_metrics()["metric-track"].load_count
        instr_fn(skill_name="metric-track")
        metrics_after = skills.get_metrics()["metric-track"].load_count
        assert metrics_after == metrics_before + 1


# ---------------------------------------------------------------------------
# Script Execution (shebang vs extension detection)
# ---------------------------------------------------------------------------

class TestScriptExecution:
    def test_execute_script_with_shebang(self):
        with tempfile.TemporaryDirectory() as d:
            _make_local_skill_dir(d, "shebang-skill", shebang=True)
            skills = Skills(loaders=[LocalSkills(d)])
            tools = skills.get_tools()
            script_fn = tools[2]
            result = script_fn(skill_name="shebang-skill", script_path="hello.py", execute=True)
            parsed = json.loads(result)
            assert parsed.get("returncode", parsed.get("exit_code", -1)) == 0
            assert "hello" in parsed.get("stdout", parsed.get("output", "")).lower()

    def test_execute_script_without_shebang(self):
        """Python scripts without shebang should still execute via extension detection."""
        with tempfile.TemporaryDirectory() as d:
            _make_local_skill_dir(d, "noshebang-skill", shebang=False)
            skills = Skills(loaders=[LocalSkills(d)])
            tools = skills.get_tools()
            script_fn = tools[2]
            result = script_fn(skill_name="noshebang-skill", script_path="hello.py", execute=True)
            parsed = json.loads(result)
            assert parsed.get("returncode", parsed.get("exit_code", -1)) == 0
            assert "hello" in parsed.get("stdout", parsed.get("output", "")).lower()

    def test_execute_bash_script(self):
        with tempfile.TemporaryDirectory() as d:
            _make_local_skill_dir(d, "bash-skill")
            skills = Skills(loaders=[LocalSkills(d)])
            tools = skills.get_tools()
            script_fn = tools[2]
            result = script_fn(skill_name="bash-skill", script_path="greet.sh", execute=True)
            parsed = json.loads(result)
            assert parsed.get("returncode", parsed.get("exit_code", -1)) == 0

    def test_read_reference_content(self):
        with tempfile.TemporaryDirectory() as d:
            _make_local_skill_dir(d, "read-ref")
            skills = Skills(loaders=[LocalSkills(d)])
            tools = skills.get_tools()
            ref_fn = tools[1]
            result = ref_fn(skill_name="read-ref", reference_path="guide.txt")
            parsed = json.loads(result)
            content = parsed.get("content", "")
            assert "Reference guide content" in content


# ---------------------------------------------------------------------------
# LocalSkills Full Pipeline
# ---------------------------------------------------------------------------

class TestLocalSkillsFullPipeline:
    def test_local_skills_to_tools(self):
        with tempfile.TemporaryDirectory() as d:
            _make_local_skill_dir(d, "local-pipe")
            skills = Skills(loaders=[LocalSkills(d)])
            assert "local-pipe" in skills
            tools = skills.get_tools()
            assert len(tools) >= 3

    def test_local_skills_system_prompt(self):
        with tempfile.TemporaryDirectory() as d:
            _make_local_skill_dir(d, "prompt-local")
            skills = Skills(loaders=[LocalSkills(d)])
            section = skills.get_system_prompt_section()
            assert "prompt-local" in section

    def test_local_skills_with_agent(self):
        from upsonic import Agent

        with tempfile.TemporaryDirectory() as d:
            _make_local_skill_dir(d, "agent-local")
            skills = Skills(loaders=[LocalSkills(d)])
            agent = Agent("openai/gpt-4o", skills=skills)
            assert agent.skills is not None
            assert "agent-local" in agent.skills
            metrics = agent.get_skill_metrics()
            assert "agent-local" in metrics


# ---------------------------------------------------------------------------
# Multiple Loaders Combined
# ---------------------------------------------------------------------------

class TestMultipleLoaders:
    def test_inline_plus_builtin(self):
        combined = Skills(loaders=[
            InlineSkills([_make_skill("custom-skill")]),
            BuiltinSkills(skills=["code-review"]),
        ])
        assert "custom-skill" in combined
        assert "code-review" in combined

    def test_inline_plus_local(self):
        with tempfile.TemporaryDirectory() as d:
            _make_local_skill_dir(d, "local-one")
            combined = Skills(loaders=[
                InlineSkills([_make_skill("inline-one")]),
                LocalSkills(d),
            ])
            assert "inline-one" in combined
            assert "local-one" in combined


# ---------------------------------------------------------------------------
# Knowledge Base Search Tool Registration
# ---------------------------------------------------------------------------

class TestKnowledgeBaseToolRegistration:
    def test_skills_without_kb_have_four_tools(self):
        skills = _make_skills("no-kb")
        tools = skills.get_tools()
        assert len(tools) == 4

    def test_skills_always_have_four_tools(self):
        """Skills always produce exactly 4 tools (no KB search tool)."""
        skills = _make_skills("no-kb-test")
        tools = skills.get_tools()
        assert len(tools) == 4
