"""Tests for ratatoskr.local_agents.

Use ``$RATATOSKR_LOCAL_AGENTS`` env-var override + pytest tmp_path to
isolate from the operator's real ``~/.config/ratatoskr/local_agents.json``.
"""

from __future__ import annotations

import json
from pathlib import Path

import pytest

from ratatoskr.local_agents import (
    LocalAgentEntry,
    _local_agents_path,
    add_local_agent,
    load_local_agents,
    make_description,
    remove_local_agent,
    update_local_agent,
)


@pytest.fixture
def local_path(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
    """Point $RATATOSKR_LOCAL_AGENTS at a fresh tmp file for the test."""
    path = tmp_path / "local_agents.json"
    monkeypatch.setenv("RATATOSKR_LOCAL_AGENTS", str(path))
    return path


def _entry(
    agent_id: str = "ratatoskr:wizard",
    agent_name: str = "wizard",
    model: str = "qwen3.6-35-a3b",
    description: str = "(tier 3) test agent",
    defined_at: str = "2026-05-25T00:00:00+00:00",
) -> LocalAgentEntry:
    return LocalAgentEntry(
        agent_id=agent_id,
        agent_name=agent_name,
        model=model,
        description=description,
        defined_at=defined_at,
    )


class TestPathResolution:
    def test_env_override(self, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.setenv("RATATOSKR_LOCAL_AGENTS", "/tmp/custom-agents.json")
        assert _local_agents_path() == Path("/tmp/custom-agents.json")

    def test_xdg_config_home(self, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.delenv("RATATOSKR_LOCAL_AGENTS", raising=False)
        monkeypatch.setenv("XDG_CONFIG_HOME", "/tmp/xdg-config")
        assert (
            _local_agents_path()
            == Path("/tmp/xdg-config/ratatoskr/local_agents.json")
        )

    def test_default_home(self, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.delenv("RATATOSKR_LOCAL_AGENTS", raising=False)
        monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)
        path = _local_agents_path()
        assert path == Path.home() / ".config" / "ratatoskr" / "local_agents.json"


class TestLoadEmpty:
    def test_missing_file_returns_empty(self, local_path: Path) -> None:
        assert not local_path.exists()
        assert load_local_agents() == []

    def test_corrupt_json_returns_empty(self, local_path: Path) -> None:
        local_path.parent.mkdir(parents=True, exist_ok=True)
        local_path.write_text("not json at all")
        assert load_local_agents() == []

    def test_wrong_schema_version_returns_empty(self, local_path: Path) -> None:
        local_path.parent.mkdir(parents=True, exist_ok=True)
        local_path.write_text(json.dumps({"version": 999, "agents": []}))
        assert load_local_agents() == []

    def test_missing_version_key_returns_empty(self, local_path: Path) -> None:
        local_path.parent.mkdir(parents=True, exist_ok=True)
        local_path.write_text(json.dumps({"agents": []}))
        assert load_local_agents() == []

    def test_malformed_row_skipped(self, local_path: Path) -> None:
        local_path.parent.mkdir(parents=True, exist_ok=True)
        local_path.write_text(
            json.dumps(
                {
                    "version": 1,
                    "agents": [
                        {"agent_id": "incomplete"},  # missing required fields
                        {
                            "agent_id": "ratatoskr:good",
                            "agent_name": "good",
                            "model": "m",
                            "description": "d",
                            "defined_at": "t",
                        },
                    ],
                }
            )
        )
        entries = load_local_agents()
        assert len(entries) == 1
        assert entries[0].agent_id == "ratatoskr:good"


class TestAdd:
    def test_add_one(self, local_path: Path) -> None:
        add_local_agent(_entry())
        entries = load_local_agents()
        assert len(entries) == 1
        assert entries[0].agent_id == "ratatoskr:wizard"

    def test_add_two_different(self, local_path: Path) -> None:
        add_local_agent(_entry(agent_id="ratatoskr:a", agent_name="a"))
        add_local_agent(_entry(agent_id="ratatoskr:b", agent_name="b"))
        ids = {e.agent_id for e in load_local_agents()}
        assert ids == {"ratatoskr:a", "ratatoskr:b"}

    def test_add_replaces_same_id(self, local_path: Path) -> None:
        add_local_agent(_entry(model="old-model"))
        add_local_agent(_entry(model="new-model"))
        entries = load_local_agents()
        assert len(entries) == 1
        assert entries[0].model == "new-model"

    def test_creates_parent_dirs(
        self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        nested = tmp_path / "deep" / "nested" / "path" / "agents.json"
        monkeypatch.setenv("RATATOSKR_LOCAL_AGENTS", str(nested))
        add_local_agent(_entry())
        assert nested.exists()


class TestUpdate:
    def test_update_changes_existing(self, local_path: Path) -> None:
        add_local_agent(_entry(model="v1"))
        update_local_agent(_entry(model="v2"))
        entries = load_local_agents()
        assert len(entries) == 1
        assert entries[0].model == "v2"


class TestRemove:
    def test_remove_existing(self, local_path: Path) -> None:
        add_local_agent(_entry())
        remove_local_agent("ratatoskr:wizard")
        assert load_local_agents() == []

    def test_remove_missing_is_noop(self, local_path: Path) -> None:
        add_local_agent(_entry())
        remove_local_agent("ratatoskr:doesnotexist")
        assert len(load_local_agents()) == 1


class TestMakeDescription:
    def test_first_nonempty_line(self) -> None:
        prompt = "\n\n# IDENTITY\nYou are a test agent..."
        desc = make_description(prompt)
        assert desc.startswith("(tier 3) IDENTITY")

    def test_strips_heading_markers(self) -> None:
        prompt = "# A nice heading\nMore prompt..."
        desc = make_description(prompt)
        assert "(tier 3) A nice heading" == desc

    def test_truncates_long(self) -> None:
        prompt = "x" * 200
        desc = make_description(prompt)
        # 80 char cap including the prefix
        assert len(desc) == 81  # 80 + ellipsis char
        assert desc.endswith("…")

    def test_empty_prompt_fallback(self) -> None:
        desc = make_description("")
        assert desc == "(tier 3) custom system prompt"

    def test_whitespace_only_fallback(self) -> None:
        desc = make_description("   \n\n  ")
        assert desc == "(tier 3) custom system prompt"
