"""Tests for the SKILL.md + fliiq.yaml skill loader."""

import os

import pytest
import yaml

from fliiq.runtime.skills.base import SkillBase, _parse_fliiq_yaml, _parse_skill_md
from fliiq.runtime.skills.loader import discover_skills


def _make_skill(tmp_path: str, name: str, return_value: str, description: str = "Test skill") -> str:
    """Create a minimal skill directory with SKILL.md + fliiq.yaml + main.py."""
    skill_dir = os.path.join(tmp_path, name)
    os.makedirs(skill_dir)

    # SKILL.md
    with open(os.path.join(skill_dir, "SKILL.md"), "w") as f:
        f.write(f"---\nname: {name}\ndescription: \"{description}\"\n---\n\nInstructions here.\n")

    # fliiq.yaml
    config = {
        "input_schema": {
            "type": "object",
            "properties": {
                "value": {"type": "string", "description": "A test value"},
            },
            "required": [],
        },
        "output_schema": {
            "type": "object",
            "properties": {
                "result": {"type": "string"},
            },
        },
    }
    with open(os.path.join(skill_dir, "fliiq.yaml"), "w") as f:
        yaml.dump(config, f)

    # main.py
    with open(os.path.join(skill_dir, "main.py"), "w") as f:
        f.write(f"async def handler(params):\n    return {{'result': '{return_value}'}}\n")

    return skill_dir


def test_parse_skill_md(tmp_path):
    """SKILL.md frontmatter is parsed correctly."""
    skill_dir = _make_skill(str(tmp_path), "test_skill", "hello")
    result = _parse_skill_md(str(tmp_path / "test_skill"))
    assert result["name"] == "test_skill"
    assert result["description"] == "Test skill"
    assert "Instructions here." in result["body"]


def test_parse_skill_md_missing_frontmatter(tmp_path):
    """SKILL.md without frontmatter raises error."""
    skill_dir = os.path.join(str(tmp_path), "bad_skill")
    os.makedirs(skill_dir)
    with open(os.path.join(skill_dir, "SKILL.md"), "w") as f:
        f.write("No frontmatter here.\n")

    with pytest.raises(ValueError, match="missing YAML frontmatter"):
        _parse_skill_md(skill_dir)


def test_parse_fliiq_yaml(tmp_path):
    """fliiq.yaml is parsed correctly."""
    skill_dir = _make_skill(str(tmp_path), "yaml_skill", "test")
    result = _parse_fliiq_yaml(str(tmp_path / "yaml_skill"))
    assert result["input_schema"]["type"] == "object"
    assert "value" in result["input_schema"]["properties"]


def test_parse_fliiq_yaml_missing(tmp_path):
    """Missing fliiq.yaml returns default empty schema."""
    os.makedirs(str(tmp_path / "no_yaml"))
    result = _parse_fliiq_yaml(str(tmp_path / "no_yaml"))
    assert result["input_schema"]["type"] == "object"
    assert result["input_schema"]["properties"] == {}


def test_skill_base_loads(tmp_path):
    """SkillBase loads from SKILL.md + fliiq.yaml + main.py."""
    skill_dir = _make_skill(str(tmp_path), "loadable", "loaded")
    skill = SkillBase(skill_dir)
    assert skill.name == "loadable"
    assert skill.description == "Test skill"


def test_skill_schema(tmp_path):
    """schema() returns valid tool definition."""
    skill_dir = _make_skill(str(tmp_path), "schema_test", "val")
    skill = SkillBase(skill_dir)
    schema = skill.schema()
    assert schema["name"] == "schema_test"
    assert schema["parameters"]["type"] == "object"
    assert "value" in schema["parameters"]["properties"]


async def test_skill_execute(tmp_path):
    """execute() runs handler and returns result."""
    skill_dir = _make_skill(str(tmp_path), "exec_test", "executed")
    skill = SkillBase(skill_dir)
    result = await skill.execute({})
    assert result == {"result": "executed"}


async def test_no_module_cache_collision(tmp_path):
    """Two skills with handler in main.py don't collide."""
    dir_a = _make_skill(str(tmp_path), "skill_a", "from_a")
    dir_b = _make_skill(str(tmp_path), "skill_b", "from_b")

    a = SkillBase(dir_a)
    b = SkillBase(dir_b)

    assert await a.execute({}) == {"result": "from_a"}
    assert await b.execute({}) == {"result": "from_b"}


def test_discover_skills(tmp_path):
    """discover_skills() finds skills by SKILL.md."""
    core_dir = tmp_path / "skills" / "core"
    _make_skill(str(core_dir), "tool_a", "a")
    _make_skill(str(core_dir), "tool_b", "b")

    skills = discover_skills(skills_dir=core_dir)
    assert "tool_a" in skills
    assert "tool_b" in skills
    assert len(skills) == 2


def test_discover_skills_ignores_bad(tmp_path):
    """discover_skills() skips broken skills and continues."""
    core_dir = tmp_path / "skills" / "core"
    _make_skill(str(core_dir), "good_skill", "good")

    # Create a broken skill (SKILL.md without frontmatter)
    bad_dir = core_dir / "bad_skill"
    os.makedirs(str(bad_dir))
    with open(str(bad_dir / "SKILL.md"), "w") as f:
        f.write("No frontmatter.\n")
    with open(str(bad_dir / "main.py"), "w") as f:
        f.write("def handler(p): pass\n")

    skills = discover_skills(skills_dir=core_dir)
    assert "good_skill" in skills
    assert len(skills) == 1


def test_real_time_skill():
    """Smoke test: real get_current_time skill loads with new format."""
    from fliiq.runtime.package_data import bundled_skills_dir

    skill_dir = str(bundled_skills_dir() / "time")
    skill = SkillBase(skill_dir)
    assert skill.name == "get_current_time"
    assert "timezone" in skill.schema()["parameters"]["properties"]
