"""Tests for ievo add command — new .md format."""

from __future__ import annotations

from pathlib import Path
from unittest.mock import AsyncMock, patch

import yaml

from ievo.commands.add import _create_placeholder_agent, add
from ievo.core.registry import Registry, RegistryEntry
from ievo.marketplace import parse_agent_frontmatter

# ── Deploy to .claude/agents/ ─────────────────────────────────


def test_add_deploys_md_to_claude_agents(tmp_project: Path) -> None:
    """Agent is installed as .claude/agents/<name>.md."""
    registry = Registry(
        {
            "test-agent": RegistryEntry(
                name="test-agent",
                version="0.1.0",
                description="Test",
                model="sonnet",
                dependencies=[],
                file="agents/test-agent.md",
            )
        }
    )

    with (
        patch("ievo.commands.add.require_project", return_value=tmp_project),
        patch("ievo.commands.add.Registry.load_cached", return_value=registry),
        patch.object(registry, "fetch", new_callable=AsyncMock),
    ):
        add(agents=["test-agent"])

    # Bundled source exists for test-agent? No, so it creates placeholder
    md_file = tmp_project / ".claude" / "agents" / "test-agent.md"
    assert md_file.exists()


def test_add_uses_bundled_source(tmp_project: Path) -> None:
    """When bundled file exists, copies from bundled."""
    registry = Registry(
        {
            "spec-writer": RegistryEntry(
                name="spec-writer",
                version="0.2.0",
                description="Spec",
                model="sonnet",
                dependencies=[],
                file="agents/spec-writer.md",
            )
        }
    )

    with (
        patch("ievo.commands.add.require_project", return_value=tmp_project),
        patch("ievo.commands.add.Registry.load_cached", return_value=registry),
        patch.object(registry, "fetch", new_callable=AsyncMock),
    ):
        add(agents=["spec-writer"])

    md_file = tmp_project / ".claude" / "agents" / "spec-writer.md"
    assert md_file.exists()
    # Should contain real bundled content (not placeholder)
    fm = parse_agent_frontmatter(md_file)
    assert fm["name"] == "spec-writer"
    assert "Placeholder" not in md_file.read_text()


def test_add_falls_back_to_github(tmp_project: Path) -> None:
    """When not bundled, calls download_agent_md."""
    registry = Registry(
        {
            "custom-agent": RegistryEntry(
                name="custom-agent",
                version="0.1.0",
                description="Custom",
                model="sonnet",
                dependencies=[],
            )
        }
    )

    with (
        patch("ievo.commands.add.require_project", return_value=tmp_project),
        patch("ievo.commands.add.Registry.load_cached", return_value=registry),
        patch.object(registry, "fetch", new_callable=AsyncMock),
        patch.object(
            registry, "download_agent_md", new_callable=AsyncMock, return_value=True
        ) as mock_dl,
    ):
        add(agents=["custom-agent"])

    mock_dl.assert_called_once()


def test_add_creates_placeholder_on_failure(tmp_project: Path) -> None:
    """When both bundled and GitHub fail, creates placeholder .md."""
    registry = Registry(
        {
            "unknown-agent": RegistryEntry(
                name="unknown-agent",
                version="0.1.0",
                description="Unknown",
                model="sonnet",
                dependencies=[],
            )
        }
    )

    with (
        patch("ievo.commands.add.require_project", return_value=tmp_project),
        patch("ievo.commands.add.Registry.load_cached", return_value=registry),
        patch.object(registry, "fetch", new_callable=AsyncMock),
        patch("ievo.commands.add.bundled_agent_path", return_value=None),
        patch.object(registry, "download_agent_md", new_callable=AsyncMock, return_value=False),
    ):
        add(agents=["unknown-agent"])

    md_file = tmp_project / ".claude" / "agents" / "unknown-agent.md"
    assert md_file.exists()
    assert "Placeholder" in md_file.read_text()


def test_add_placeholder_has_valid_frontmatter(tmp_path: Path) -> None:
    """Placeholder .md contains valid YAML frontmatter."""
    dest = tmp_path / "test.md"
    _create_placeholder_agent(dest, "test-agent")

    fm = parse_agent_frontmatter(dest)
    assert fm["name"] == "test-agent"
    assert fm["description"] == "Placeholder agent"
    assert fm["model"] == "sonnet"


# ── Manifest and deps ─────────────────────────────────────────


def test_add_records_in_manifest(tmp_project: Path) -> None:
    """Added agent is recorded in .ievo/manifest.yaml."""
    registry = Registry(
        {
            "test-agent": RegistryEntry(
                name="test-agent",
                version="0.2.0",
                description="Test",
                model="sonnet",
                dependencies=[],
            )
        }
    )

    with (
        patch("ievo.commands.add.require_project", return_value=tmp_project),
        patch("ievo.commands.add.Registry.load_cached", return_value=registry),
        patch.object(registry, "fetch", new_callable=AsyncMock),
    ):
        add(agents=["test-agent"])

    manifest = yaml.safe_load((tmp_project / ".ievo/manifest.yaml").read_text())
    assert manifest["agents"]["test-agent"] == "0.2.0"


def test_add_skips_already_installed(tmp_project_with_agent: Path) -> None:
    """Re-adding an installed agent is skipped."""
    registry = Registry()

    with (
        patch("ievo.commands.add.require_project", return_value=tmp_project_with_agent),
        patch("ievo.commands.add.Registry.load_cached", return_value=registry),
        patch.object(registry, "fetch", new_callable=AsyncMock),
        patch.object(
            registry, "download_agent_md", new_callable=AsyncMock, return_value=False
        ) as mock_dl,
    ):
        add(agents=["spec-writer"])

    mock_dl.assert_not_called()


def test_add_resolves_dependencies(tmp_project: Path) -> None:
    """Agent with dependencies installs deps first."""
    registry = Registry(
        {
            "spec-writer": RegistryEntry(
                name="spec-writer",
                version="0.1.0",
                description="Spec",
                model="sonnet",
                dependencies=[],
            ),
            "architect": RegistryEntry(
                name="architect",
                version="0.1.0",
                description="Arch",
                model="opus",
                dependencies=["spec-writer"],
            ),
        }
    )

    with (
        patch("ievo.commands.add.require_project", return_value=tmp_project),
        patch("ievo.commands.add.Registry.load_cached", return_value=registry),
        patch.object(registry, "fetch", new_callable=AsyncMock),
    ):
        add(agents=["architect"])

    manifest = yaml.safe_load((tmp_project / ".ievo/manifest.yaml").read_text())
    assert "spec-writer" in manifest["agents"]
    assert "architect" in manifest["agents"]


def test_add_nothing_to_install(tmp_project_with_agent: Path) -> None:
    """When all agents already installed, prints info message."""
    registry = Registry()

    with (
        patch("ievo.commands.add.require_project", return_value=tmp_project_with_agent),
        patch("ievo.commands.add.Registry.load_cached", return_value=registry),
        patch.object(registry, "fetch", new_callable=AsyncMock),
    ):
        add(agents=["spec-writer"])

    # Should not crash, just print info


# ── Deprecation warning ─────────────────────────────────────


def test_add_shows_deprecation_warning(tmp_project: Path) -> None:
    """Running add directly shows deprecation warning."""
    registry = Registry()

    with (
        patch("ievo.commands.add.require_project", return_value=tmp_project),
        patch("ievo.commands.add.Registry.load_cached", return_value=registry),
        patch.object(registry, "fetch", new_callable=AsyncMock),
        patch("ievo.utils.deprecation.warn") as mock_warn,
    ):
        add(agents=["test-agent"])

    mock_warn.assert_called_once()
    msg = mock_warn.call_args[0][0]
    assert "deprecated" in msg


def test_add_agents_internal_api_no_warning(tmp_project: Path) -> None:
    """Internal add_agents() does not print deprecation warning."""
    from ievo.commands.add import add_agents

    registry = Registry()

    with (
        patch("ievo.commands.add.require_project", return_value=tmp_project),
        patch("ievo.commands.add.Registry.load_cached", return_value=registry),
        patch.object(registry, "fetch", new_callable=AsyncMock),
        patch("ievo.utils.deprecation.warn") as mock_warn,
    ):
        add_agents(["test-agent"])

    mock_warn.assert_not_called()
