"""Unit tests for ot_forge.install_skills() — skill stub installation."""

from __future__ import annotations

from pathlib import Path
from unittest.mock import patch

import pytest
import yaml


@pytest.fixture
def fake_env(tmp_path: Path):
    """Set up a fake environment with skills.md, skills dir, and mock config."""
    skills_dir = tmp_path / "skills"
    skills_dir.mkdir()
    (skills_dir / "my-skill.md").write_text(
        "---\nname: my-skill\ndescription: My test skill\n---\n\nBody."
    )
    (skills_dir / "other-skill.md").write_text(
        "---\nname: other-skill\ndescription: Another skill\n---\n\nOther body."
    )

    tools_yaml = tmp_path / "skills.md"
    tools_yaml.write_text(
        yaml.dump({
            "tools": {
                "claude": {"stub_path": ".claude/skills/{name}/SKILL.md"},
                "codex": {"stub_path": ".codex/skills/{name}/SKILL.md"},
                "opencode": {"stub_path": ".opencode/skills/{name}/SKILL.md"},
            }
        })
    )

    stub_tmpl = tmp_path / "skill_stub.md.j2"
    stub_tmpl.write_text(
        "---\nname: {{ name }}\ndescription: {{ description }}\n---\n\n{{ body }}\n"
    )

    project_dir = tmp_path / "project"
    project_dir.mkdir()

    return {
        "tmp": tmp_path,
        "skills_dir": skills_dir,
        "tools_yaml": tools_yaml,
        "stub_tmpl": stub_tmpl,
        "project_dir": project_dir,
    }


def _patch_env(env: dict):
    """Create a context manager that patches key functions."""
    from contextlib import ExitStack

    def patcher():
        stack = ExitStack()
        stack.enter_context(
            patch("ottools.ot_forge._get_tools_config", return_value={
                "claude": {"stub_path": ".claude/skills/{name}/SKILL.md"},
                "codex": {"stub_path": ".codex/skills/{name}/SKILL.md"},
                "opencode": {"stub_path": ".opencode/skills/{name}/SKILL.md"},
                "pi": {"stub_path": ".pi/skills/{name}/SKILL.md"},
            })
        )
        stack.enter_context(
            patch("ottools.ot_forge._list_bundled_skills", return_value=["my-skill", "other-skill"])
        )
        stack.enter_context(
            patch("ottools.ot_forge._get_skill_description", side_effect=lambda n: f"Desc for {n}")
        )
        stack.enter_context(
            patch("ottools.ot_forge._get_skill_stub_template",
                  return_value="---\nname: {{ name }}\ndescription: {{ description }}\n---\n\n{{ body }}\n")
        )
        stack.enter_context(
            patch("ottools.ot_forge._get_skill_body", side_effect=lambda name: f"Full content of {name}")
        )
        stack.enter_context(
            patch("ottools.ot_forge.get_effective_cwd", return_value=env["project_dir"])
        )
        return stack

    return patcher()


# =============================================================================
# Install Tests
# =============================================================================


@pytest.mark.unit
@pytest.mark.tools
def test_install_skill_claude(fake_env: dict, tmp_path: Path) -> None:
    """install_skills(install=...) installs stub for Claude Code."""
    from ottools.ot_forge import install_skills

    with _patch_env(fake_env):
        result = install_skills(install="my-skill", tool="claude")

    assert "installed" in result or "updated" in result
    stub_file = fake_env["project_dir"] / ".claude" / "skills" / "my-skill" / "SKILL.md"
    assert stub_file.exists(), f"Stub file not created at {stub_file}"
    content = stub_file.read_text()
    assert "my-skill" in content
    assert "Full content of my-skill" in content
    assert "ot.skills" not in content


@pytest.mark.unit
@pytest.mark.tools
def test_install_skill_codex(fake_env: dict) -> None:
    """install_skills(install=..., tool='codex') installs stub for Codex."""
    from ottools.ot_forge import install_skills

    with _patch_env(fake_env):
        result = install_skills(install="my-skill", tool="codex")

    assert "installed" in result or "updated" in result
    stub_file = fake_env["project_dir"] / ".codex" / "skills" / "my-skill" / "SKILL.md"
    assert stub_file.exists(), f"Codex stub not created at {stub_file}"


@pytest.mark.unit
@pytest.mark.tools
def test_install_skill_all(fake_env: dict) -> None:
    """install_skills(install='all') installs stubs for all bundled skills."""
    from ottools.ot_forge import install_skills

    with _patch_env(fake_env):
        result = install_skills(install="all", tool="claude")

    assert "my-skill" in result
    assert "other-skill" in result
    project = fake_env["project_dir"]
    assert (project / ".claude" / "skills" / "my-skill" / "SKILL.md").exists()
    assert (project / ".claude" / "skills" / "other-skill" / "SKILL.md").exists()


@pytest.mark.unit
@pytest.mark.tools
def test_install_skill_overwrites(fake_env: dict) -> None:
    """Installing an already-existing stub reports 'updated'."""
    from ottools.ot_forge import install_skills

    with _patch_env(fake_env):
        install_skills(install="my-skill", tool="claude")
        result = install_skills(install="my-skill", tool="claude")

    assert "updated" in result


@pytest.mark.unit
@pytest.mark.tools
def test_install_skill_default(fake_env: dict) -> None:
    """install_skills() with no args installs all bundled skills."""
    from ottools.ot_forge import install_skills

    with _patch_env(fake_env):
        result = install_skills()

    assert "my-skill" in result
    assert "other-skill" in result
    project = fake_env["project_dir"]
    assert (project / ".claude" / "skills" / "my-skill" / "SKILL.md").exists()
    assert (project / ".claude" / "skills" / "other-skill" / "SKILL.md").exists()


@pytest.mark.unit
@pytest.mark.tools
def test_install_skill_exclude(fake_env: dict) -> None:
    """install_skills(exclude=[...]) installs all skills except excluded ones."""
    from ottools.ot_forge import install_skills

    with _patch_env(fake_env):
        result = install_skills(exclude=["other-skill"])

    project = fake_env["project_dir"]
    assert (project / ".claude" / "skills" / "my-skill" / "SKILL.md").exists()
    assert not (project / ".claude" / "skills" / "other-skill" / "SKILL.md").exists()
    assert "my-skill" in result
    assert "other-skill" not in result


@pytest.mark.unit
@pytest.mark.tools
def test_install_skill_unknown(fake_env: dict) -> None:
    """install_skills(install='unknown') returns error listing available skills."""
    from ottools.ot_forge import install_skills

    with _patch_env(fake_env):
        result = install_skills(install="nonexistent-skill")

    assert "Error" in result or "Unknown" in result
    assert "my-skill" in result


@pytest.mark.unit
@pytest.mark.tools
def test_install_skill_pi(fake_env: dict) -> None:
    """install_skills(install=..., tool='pi') installs stub for Pi coding agent."""
    from ottools.ot_forge import install_skills

    with _patch_env(fake_env):
        result = install_skills(install="my-skill", tool="pi")

    assert "installed" in result or "updated" in result
    stub_file = fake_env["project_dir"] / ".pi" / "skills" / "my-skill" / "SKILL.md"
    assert stub_file.exists(), f"Pi stub not created at {stub_file}"
    content = stub_file.read_text()
    assert "my-skill" in content


@pytest.mark.unit
@pytest.mark.tools
def test_install_skill_unsupported_tool(fake_env: dict) -> None:
    """install_skills(install=..., tool='unknown-tool') returns error."""
    from ottools.ot_forge import install_skills

    with _patch_env(fake_env):
        result = install_skills(install="my-skill", tool="unknown-tool")

    assert "Error" in result or "Unsupported" in result
    assert "claude" in result


# =============================================================================
# Config Tests
# =============================================================================


@pytest.mark.unit
@pytest.mark.tools
def test_get_tools_config_loads_yaml() -> None:
    """_get_tools_config() reads skills.md from global_templates."""
    from ottools.ot_forge import _get_tools_config

    config = _get_tools_config()

    assert "claude" in config
    assert "codex" in config
    assert "opencode" in config
    assert "pi" in config
    assert "stub_path" in config["claude"]
    assert "{name}" in config["claude"]["stub_path"]
    assert config["pi"]["stub_path"] == ".pi/skills/{name}/SKILL.md"
