"""Tests for ``skills.create_dir`` — config-driven skill creation directory.

When configured, agent-created skills (skill_manage action=create) land in
``skills.create_dir`` instead of the profile-local skills dir, the directory
is scanned for discovery like the local dir, and the instruction text that
names the creation path renders the configured directory.
"""

import json
from pathlib import Path

import pytest


@pytest.fixture
def isolated_home(tmp_path, monkeypatch):
    """Fresh HERMES_HOME with an empty local skills dir."""
    home = tmp_path / ".hermes"
    (home / "skills").mkdir(parents=True)
    monkeypatch.setenv("HERMES_HOME", str(home))

    import hermes_constants
    monkeypatch.setattr(hermes_constants, "_hermes_home_cache", None, raising=False)

    from agent import skill_utils as su
    su._external_dirs_cache_clear()

    import tools.skills_tool as skills_tool
    import tools.skill_manager_tool as smt
    monkeypatch.setattr(skills_tool, "SKILLS_DIR", home / "skills")
    monkeypatch.setattr(smt, "SKILLS_DIR", home / "skills")
    yield home
    su._external_dirs_cache_clear()


def _write_config(home: Path, body: str):
    (home / "config.yaml").write_text(body, encoding="utf-8")
    from agent import skill_utils as su
    su._raw_config_cache_clear()
    su._external_dirs_cache_clear()


def _skill_md(name: str) -> str:
    return (
        f"---\nname: {name}\n"
        f"description: Use when testing create dir routing. One-line behavior.\n"
        f"---\n\n# {name}\n\nBody.\n"
    )


class TestGetSkillCreateDir:
    def test_unset_returns_none(self, isolated_home):
        from agent.skill_utils import get_skill_create_dir
        _write_config(isolated_home, "skills:\n  external_dirs: []\n")
        assert get_skill_create_dir() is None

    def test_absolute_path(self, isolated_home, tmp_path):
        from agent.skill_utils import get_skill_create_dir
        brain = tmp_path / "brain-skills"
        _write_config(isolated_home, f"skills:\n  create_dir: {brain}\n")
        assert get_skill_create_dir() == brain.resolve()

    def test_relative_path_resolves_against_home(self, isolated_home):
        from agent.skill_utils import get_skill_create_dir
        _write_config(isolated_home, "skills:\n  create_dir: brain\n")
        assert get_skill_create_dir() == (isolated_home / "brain").resolve()

    def test_tilde_expansion(self, isolated_home):
        from agent.skill_utils import get_skill_create_dir
        _write_config(isolated_home, "skills:\n  create_dir: ~/brain-skills\n")
        assert get_skill_create_dir() == (Path.home() / "brain-skills").resolve()

    def test_local_skills_dir_treated_as_unset(self, isolated_home):
        from agent.skill_utils import get_skill_create_dir
        _write_config(
            isolated_home, f"skills:\n  create_dir: {isolated_home / 'skills'}\n"
        )
        assert get_skill_create_dir() is None

    def test_empty_string_treated_as_unset(self, isolated_home):
        from agent.skill_utils import get_skill_create_dir
        _write_config(isolated_home, "skills:\n  create_dir: ''\n")
        assert get_skill_create_dir() is None


class TestDisplaySkillCreateDir:
    def test_default_renders_local_skills_path(self, isolated_home):
        from agent.skill_utils import display_skill_create_dir
        _write_config(isolated_home, "skills: {}\n")
        assert display_skill_create_dir().endswith("/skills/")

    def test_configured_renders_configured_path(self, isolated_home, tmp_path):
        from agent.skill_utils import display_skill_create_dir
        brain = tmp_path / "opt-brain"
        _write_config(isolated_home, f"skills:\n  create_dir: {brain}\n")
        assert "opt-brain" in display_skill_create_dir()


def test_tool_schema_stays_stable_when_skill_creation_home_changes(isolated_home, tmp_path):
    from tools.skill_manager_tool import SKILL_MANAGE_SCHEMA
    from tools.registry import registry

    _write_config(isolated_home, f"skills:\n  create_dir: {tmp_path / 'first-brain'}\n")
    first = registry.get_definitions({"skill_manage"})[0]
    _write_config(isolated_home, f"skills:\n  create_dir: {tmp_path / 'second-brain'}\n")
    second = registry.get_definitions({"skill_manage"})[0]
    assert first == second
    assert first["function"]["description"] == SKILL_MANAGE_SCHEMA["description"]


class TestDiscovery:
    def test_create_dir_in_all_skills_dirs(self, isolated_home, tmp_path):
        from agent.skill_utils import get_all_skills_dirs
        brain = tmp_path / "brain-skills"
        brain.mkdir()
        _write_config(isolated_home, f"skills:\n  create_dir: {brain}\n")
        dirs = [d.resolve() for d in get_all_skills_dirs()]
        assert dirs[0] == (isolated_home / "skills").resolve()
        assert brain.resolve() in dirs

    def test_missing_create_dir_not_scanned(self, isolated_home, tmp_path):
        from agent.skill_utils import get_all_skills_dirs
        brain = tmp_path / "does-not-exist"
        _write_config(isolated_home, f"skills:\n  create_dir: {brain}\n")
        assert brain.resolve() not in [d.resolve() for d in get_all_skills_dirs()]

    def test_no_duplicate_when_also_in_external_dirs(self, isolated_home, tmp_path):
        from agent.skill_utils import get_all_skills_dirs
        brain = tmp_path / "brain-skills"
        brain.mkdir()
        _write_config(
            isolated_home,
            f"skills:\n  create_dir: {brain}\n  external_dirs:\n    - {brain}\n",
        )
        dirs = [d.resolve() for d in get_all_skills_dirs()]
        assert dirs.count(brain.resolve()) == 1


class TestCreateRouting:
    def test_create_lands_in_create_dir(self, isolated_home, tmp_path):
        from tools.skill_manager_tool import skill_manage
        brain = tmp_path / "brain-skills"
        _write_config(isolated_home, f"skills:\n  create_dir: {brain}\n")
        res = json.loads(skill_manage("", "", operations=[{
            "action": "create", "name": "routed-skill",
            "content": _skill_md("routed-skill"),
        }]))
        assert res.get("success"), res
        assert (brain / "routed-skill" / "SKILL.md").exists()
        assert not (isolated_home / "skills" / "routed-skill").exists()
        # Out-of-root creation reports an absolute path, not a relative_to
        # crash (single-op legacy shape surfaces the path field).
        res_flat = json.loads(skill_manage(
            "create", "routed-skill-flat", content=_skill_md("routed-skill-flat"),
        ))
        assert res_flat.get("success"), res_flat
        assert str(brain / "routed-skill-flat") == res_flat["path"]

    def test_create_with_category(self, isolated_home, tmp_path):
        from tools.skill_manager_tool import skill_manage
        brain = tmp_path / "brain-skills"
        _write_config(isolated_home, f"skills:\n  create_dir: {brain}\n")
        res = json.loads(skill_manage("", "", operations=[{
            "action": "create", "name": "cat-skill", "category": "devops",
            "content": _skill_md("cat-skill"),
        }]))
        assert res.get("success"), res
        assert (brain / "devops" / "cat-skill" / "SKILL.md").exists()

    def test_default_create_still_local(self, isolated_home):
        from tools.skill_manager_tool import skill_manage
        _write_config(isolated_home, "skills: {}\n")
        res = json.loads(skill_manage("", "", operations=[{
            "action": "create", "name": "local-skill",
            "content": _skill_md("local-skill"),
        }]))
        assert res.get("success"), res
        assert (isolated_home / "skills" / "local-skill" / "SKILL.md").exists()

    def test_created_skill_is_findable_and_patchable(self, isolated_home, tmp_path):
        from tools.skill_manager_tool import skill_manage, _find_skill
        brain = tmp_path / "brain-skills"
        _write_config(isolated_home, f"skills:\n  create_dir: {brain}\n")
        json.loads(skill_manage("", "", operations=[{
            "action": "create", "name": "patchable-skill",
            "content": _skill_md("patchable-skill"),
        }]))
        found = _find_skill("patchable-skill")
        assert found is not None
        res = json.loads(skill_manage("", "", operations=[{
            "action": "patch", "name": "patchable-skill",
            "old_string": "Body.", "new_string": "Patched.",
        }]))
        assert res.get("success"), res
        assert "Patched." in (brain / "patchable-skill" / "SKILL.md").read_text()
