"""Tests for ievo.core.project.ProjectManifest and compare_agent_versions."""

from __future__ import annotations

from pathlib import Path

import yaml

from ievo.core.project import ProjectManifest, compare_agent_versions, compare_template_versions
from ievo.core.registry import Registry, RegistryEntry

# ── Helpers ──────────────────────────────────────────────────


def _make_registry(*agents: tuple[str, str]) -> Registry:
    """Create a Registry with the given (name, version) pairs."""
    entries = {
        name: RegistryEntry(
            name=name,
            version=version,
            description="",
            model="sonnet",
            dependencies=[],
        )
        for name, version in agents
    }
    return Registry(entries)


def test_load_manifest(tmp_project: Path) -> None:
    m = ProjectManifest.load(tmp_project)
    assert m.project == "test-project"
    assert m.version == "0.1.0"
    assert m.agents == {}


def test_load_with_defaults(tmp_path: Path) -> None:
    (tmp_path / ".ievo").mkdir()
    (tmp_path / ".ievo" / "manifest.yaml").write_text("project: minimal\n")
    m = ProjectManifest.load(tmp_path)
    assert m.project == "minimal"
    assert m.version == "0.1.0"
    assert m.created == ""
    assert m.agents == {}
    assert m.overrides == {}


def test_save_roundtrip(tmp_path: Path) -> None:
    m = ProjectManifest(project="roundtrip", version="1.0.0", created="2026-01-01")
    m.add_agent("coder", "0.2.0")
    m.save(tmp_path)

    loaded = ProjectManifest.load(tmp_path)
    assert loaded.project == "roundtrip"
    assert loaded.version == "1.0.0"
    assert loaded.agents == {"coder": "0.2.0"}


def test_save_includes_overrides(tmp_path: Path) -> None:
    m = ProjectManifest(project="test", overrides={"coder": {"model": {"primary": "opus"}}})
    m.save(tmp_path)

    data = yaml.safe_load((tmp_path / ".ievo" / "manifest.yaml").read_text())
    assert "overrides" in data
    assert data["overrides"]["coder"]["model"]["primary"] == "opus"


def test_save_omits_empty_overrides(tmp_path: Path) -> None:
    m = ProjectManifest(project="test")
    m.save(tmp_path)

    data = yaml.safe_load((tmp_path / ".ievo" / "manifest.yaml").read_text())
    assert "overrides" not in data


def test_has_agent() -> None:
    m = ProjectManifest(project="test", agents={"coder": "0.1.0"})
    assert m.has_agent("coder") is True
    assert m.has_agent("tester") is False


def test_add_agent() -> None:
    m = ProjectManifest(project="test")
    m.add_agent("spec-writer", "0.1.0")
    assert m.agents == {"spec-writer": "0.1.0"}


def test_remove_agent() -> None:
    m = ProjectManifest(
        project="test",
        agents={"coder": "0.1.0", "architect": "0.1.0"},
        overrides={"coder": {"model": {"primary": "opus"}}},
    )
    m.remove_agent("coder")
    assert "coder" not in m.agents
    assert "coder" not in m.overrides
    assert "architect" in m.agents


def test_remove_nonexistent_agent() -> None:
    m = ProjectManifest(project="test")
    m.remove_agent("nonexistent")  # should not raise
    assert m.agents == {}


# ── compare_agent_versions ────────────────────────────────────


def test_compare_agent_versions_one_outdated() -> None:
    """One installed agent whose version differs from registry."""
    manifest = ProjectManifest(project="test", agents={"coder": "0.1.0"})
    registry = _make_registry(("coder", "0.2.0"))
    result = compare_agent_versions(manifest, registry)
    assert result == {"coder": ("0.1.0", "0.2.0")}


def test_compare_agent_versions_all_up_to_date() -> None:
    """Versions match → empty dict."""
    manifest = ProjectManifest(project="test", agents={"coder": "0.2.0"})
    registry = _make_registry(("coder", "0.2.0"))
    result = compare_agent_versions(manifest, registry)
    assert result == {}


def test_compare_agent_versions_agent_not_in_registry() -> None:
    """Agent in manifest but absent from registry → skipped, no error."""
    manifest = ProjectManifest(project="test", agents={"coder": "0.1.0"})
    registry = _make_registry(("spec-writer", "0.2.0"))
    result = compare_agent_versions(manifest, registry)
    assert result == {}


def test_compare_agent_versions_empty_manifest() -> None:
    """Empty manifest agents dict → empty result."""
    manifest = ProjectManifest(project="test", agents={})
    registry = _make_registry(("coder", "0.2.0"))
    result = compare_agent_versions(manifest, registry)
    assert result == {}


def test_compare_agent_versions_multiple_outdated() -> None:
    """Multiple agents both outdated → both returned."""
    manifest = ProjectManifest(project="test", agents={"coder": "0.1.0", "architect": "0.1.0"})
    registry = _make_registry(("coder", "0.2.0"), ("architect", "0.2.0"))
    result = compare_agent_versions(manifest, registry)
    assert result == {
        "coder": ("0.1.0", "0.2.0"),
        "architect": ("0.1.0", "0.2.0"),
    }


def test_compare_agent_versions_mixed() -> None:
    """One outdated, one up-to-date → only outdated returned."""
    manifest = ProjectManifest(project="test", agents={"coder": "0.1.0", "architect": "0.2.0"})
    registry = _make_registry(("coder", "0.2.0"), ("architect", "0.2.0"))
    result = compare_agent_versions(manifest, registry)
    assert result == {"coder": ("0.1.0", "0.2.0")}


def test_compare_agent_versions_none_registry() -> None:
    """None registry → empty dict, no crash."""
    manifest = ProjectManifest(project="test", agents={"coder": "0.1.0"})
    result = compare_agent_versions(manifest, None)
    assert result == {}


def test_compare_agent_versions_none_manifest() -> None:
    """None manifest → empty dict, no crash."""
    registry = _make_registry(("coder", "0.2.0"))
    result = compare_agent_versions(None, registry)
    assert result == {}


# ── cli_version field on ProjectManifest ──────────────────────


def test_manifest_cli_version_default() -> None:
    """ProjectManifest default cli_version is empty string."""
    m = ProjectManifest(project="test")
    assert m.cli_version == ""


def test_manifest_load_with_cli_version(tmp_path: Path) -> None:
    """Load manifest YAML with cli_version field."""
    (tmp_path / ".ievo").mkdir()
    (tmp_path / ".ievo" / "manifest.yaml").write_text(
        "project: test\nversion: 0.1.0\ncli_version: '0.1.0'\n"
    )
    m = ProjectManifest.load(tmp_path)
    assert m.cli_version == "0.1.0"


def test_manifest_load_without_cli_version(tmp_path: Path) -> None:
    """Load manifest YAML without cli_version key → default to empty string (backwards compat)."""
    (tmp_path / ".ievo").mkdir()
    (tmp_path / ".ievo" / "manifest.yaml").write_text("project: test\nversion: 0.1.0\n")
    m = ProjectManifest.load(tmp_path)
    assert m.cli_version == ""


def test_manifest_save_includes_cli_version(tmp_path: Path) -> None:
    """save() writes cli_version key to YAML even when non-empty."""
    m = ProjectManifest(project="test", cli_version="0.2.0")
    m.save(tmp_path)
    data = yaml.safe_load((tmp_path / ".ievo" / "manifest.yaml").read_text())
    assert "cli_version" in data
    assert data["cli_version"] == "0.2.0"


def test_manifest_save_includes_empty_cli_version(tmp_path: Path) -> None:
    """save() writes cli_version key even when it is empty string."""
    m = ProjectManifest(project="test", cli_version="")
    m.save(tmp_path)
    data = yaml.safe_load((tmp_path / ".ievo" / "manifest.yaml").read_text())
    assert "cli_version" in data
    assert data["cli_version"] == "" or data["cli_version"] is None


def test_save_roundtrip_preserves_cli_version(tmp_path: Path) -> None:
    """Save with cli_version, load back, value is preserved."""
    m = ProjectManifest(project="test", cli_version="0.3.0")
    m.save(tmp_path)
    loaded = ProjectManifest.load(tmp_path)
    assert loaded.cli_version == "0.3.0"


# ── compare_template_versions ─────────────────────────────────


def test_compare_template_versions_outdated() -> None:
    """manifest.cli_version='0.1.0' vs cli_version='0.2.0' → True (outdated)."""
    manifest = ProjectManifest(project="test", cli_version="0.1.0")
    assert compare_template_versions(manifest, "0.2.0") is True


def test_compare_template_versions_up_to_date() -> None:
    """manifest.cli_version matches cli_version → False (up to date)."""
    manifest = ProjectManifest(project="test", cli_version="0.2.0")
    assert compare_template_versions(manifest, "0.2.0") is False


def test_compare_template_versions_empty_cli_version() -> None:
    """manifest.cli_version='' (old project) → False (no nagging)."""
    manifest = ProjectManifest(project="test", cli_version="")
    assert compare_template_versions(manifest, "0.1.0") is False


def test_compare_template_versions_none_manifest() -> None:
    """None manifest → False (no crash, no nagging)."""
    assert compare_template_versions(None, "0.1.0") is False
