"""Tests for brynq.entity_registry — Global Entity Registry (Phase 60)."""

import json
import pytest
from pathlib import Path


@pytest.fixture(autouse=True)
def _isolate_registry(tmp_path, monkeypatch):
    """Redirect registry storage to temp directory."""
    entities_dir = tmp_path / "registry" / "entities"
    entities_dir.mkdir(parents=True)
    links_file = tmp_path / "registry" / "links.jsonl"
    monkeypatch.setattr("brynq.entity_registry.REGISTRY_DIR", tmp_path / "registry")
    monkeypatch.setattr("brynq.entity_registry.ENTITIES_DIR", entities_dir)
    monkeypatch.setattr("brynq.entity_registry.LINKS_FILE", links_file)
    monkeypatch.setattr("brynq.entity_registry.BROKER_DIR", tmp_path)


# ── register_entity ──────────────────────────────────────────────────


class TestRegisterEntity:
    def test_returns_entity_dict(self):
        from brynq.entity_registry import register_entity
        entity = register_entity("agent", "a1", "Agent Alpha")
        assert entity["entity_id"] == "a1"
        assert entity["entity_type"] == "agent"
        assert entity["name"] == "Agent Alpha"

    def test_creates_file_on_disk(self):
        from brynq.entity_registry import register_entity, ENTITIES_DIR
        register_entity("task", "t1", "Task One")
        assert (ENTITIES_DIR / "t1.json").exists()

    def test_metadata_stored(self):
        from brynq.entity_registry import register_entity, get_entity
        register_entity("agent", "a2", "Agent Beta", metadata={"role": "lead"})
        entity = get_entity("a2")
        assert entity["metadata"]["role"] == "lead"

    def test_invalid_type_raises(self):
        from brynq.entity_registry import register_entity
        with pytest.raises(ValueError, match="Invalid entity_type"):
            register_entity("invalid_type", "x1", "Bad")

    def test_duplicate_id_raises(self):
        from brynq.entity_registry import register_entity
        register_entity("agent", "dup1", "First")
        with pytest.raises(ValueError, match="already exists"):
            register_entity("agent", "dup1", "Second")

    def test_all_valid_types(self):
        from brynq.entity_registry import register_entity, VALID_ENTITY_TYPES
        for i, etype in enumerate(sorted(VALID_ENTITY_TYPES)):
            entity = register_entity(etype, f"ent-{i}", f"Entity {i}")
            assert entity["entity_type"] == etype

    def test_timestamps_present(self):
        from brynq.entity_registry import register_entity
        entity = register_entity("agent", "ts1", "Timestamped")
        assert "created_at" in entity
        assert "updated_at" in entity


# ── get_entity / delete_entity / update_entity ───────────────────────


class TestGetEntity:
    def test_get_existing(self):
        from brynq.entity_registry import register_entity, get_entity
        register_entity("workspace", "ws1", "Workspace One")
        entity = get_entity("ws1")
        assert entity is not None
        assert entity["name"] == "Workspace One"

    def test_get_nonexistent_returns_none(self):
        from brynq.entity_registry import get_entity
        assert get_entity("nonexistent") is None


class TestDeleteEntity:
    def test_delete_existing(self):
        from brynq.entity_registry import register_entity, delete_entity, get_entity
        register_entity("agent", "del1", "Deletable")
        assert delete_entity("del1") is True
        assert get_entity("del1") is None

    def test_delete_nonexistent_returns_false(self):
        from brynq.entity_registry import delete_entity
        assert delete_entity("no-such-entity") is False

    def test_delete_removes_links(self):
        from brynq.entity_registry import (
            register_entity, link_entities, delete_entity, get_relationships,
        )
        register_entity("agent", "da1", "Agent")
        register_entity("task", "dt1", "Task")
        link_entities("da1", "dt1", "assigned_to")
        delete_entity("da1")
        # Links involving da1 should be gone
        assert get_relationships("da1") == []


class TestUpdateEntity:
    def test_update_name(self):
        from brynq.entity_registry import register_entity, update_entity
        register_entity("agent", "upd1", "Old Name")
        updated = update_entity("upd1", {"name": "New Name"})
        assert updated["name"] == "New Name"

    def test_update_nonexistent_returns_none(self):
        from brynq.entity_registry import update_entity
        assert update_entity("nope", {"name": "x"}) is None

    def test_cannot_overwrite_entity_id(self):
        from brynq.entity_registry import register_entity, update_entity
        register_entity("agent", "prot1", "Protected")
        updated = update_entity("prot1", {"entity_id": "hacked"})
        assert updated["entity_id"] == "prot1"

    def test_update_adds_new_field(self):
        from brynq.entity_registry import register_entity, update_entity
        register_entity("agent", "nf1", "New Field")
        updated = update_entity("nf1", {"custom_field": "value"})
        assert updated["custom_field"] == "value"


# ── list_entities ─────────────────────────────────────────────────────


class TestListEntities:
    def test_list_all(self):
        from brynq.entity_registry import register_entity, list_entities
        register_entity("agent", "la1", "Agent A")
        register_entity("task", "lt1", "Task T")
        entities = list_entities()
        assert len(entities) == 2

    def test_filter_by_type(self):
        from brynq.entity_registry import register_entity, list_entities
        register_entity("agent", "fa1", "Agent")
        register_entity("task", "ft1", "Task")
        register_entity("agent", "fa2", "Agent 2")
        agents = list_entities(entity_type="agent")
        assert all(e["entity_type"] == "agent" for e in agents)
        assert len(agents) == 2

    def test_search_by_query(self):
        from brynq.entity_registry import register_entity, list_entities
        register_entity("agent", "qa1", "Alpha Agent")
        register_entity("agent", "qa2", "Beta Agent")
        register_entity("task", "qt1", "Alpha Task")
        results = list_entities(query="Alpha")
        assert len(results) == 2

    def test_limit(self):
        from brynq.entity_registry import register_entity, list_entities
        for i in range(10):
            register_entity("agent", f"lim-{i}", f"Agent {i}")
        results = list_entities(limit=3)
        assert len(results) == 3

    def test_empty_registry(self):
        from brynq.entity_registry import list_entities
        assert list_entities() == []


# ── link_entities / unlink_entities ───────────────────────────────────


class TestLinkEntities:
    def test_create_link(self):
        from brynq.entity_registry import register_entity, link_entities
        register_entity("agent", "lk-a", "Agent")
        register_entity("task", "lk-t", "Task")
        link = link_entities("lk-a", "lk-t", "assigned_to")
        assert link["entity_a"] == "lk-a"
        assert link["entity_b"] == "lk-t"
        assert link["relationship_type"] == "assigned_to"

    def test_invalid_relationship_type_raises(self):
        from brynq.entity_registry import register_entity, link_entities
        register_entity("agent", "inv-a", "A")
        register_entity("task", "inv-t", "T")
        with pytest.raises(ValueError, match="Invalid relationship_type"):
            link_entities("inv-a", "inv-t", "loves")

    def test_link_nonexistent_entity_raises(self):
        from brynq.entity_registry import register_entity, link_entities
        register_entity("agent", "exist-a", "A")
        with pytest.raises(ValueError, match="not found"):
            link_entities("exist-a", "ghost", "owns")

    def test_duplicate_link_returns_existing(self):
        from brynq.entity_registry import register_entity, link_entities
        register_entity("agent", "dup-a", "A")
        register_entity("task", "dup-t", "T")
        link1 = link_entities("dup-a", "dup-t", "owns")
        link2 = link_entities("dup-a", "dup-t", "owns")
        assert link1["link_id"] == link2["link_id"]


class TestUnlinkEntities:
    def test_unlink(self):
        from brynq.entity_registry import (
            register_entity, link_entities, unlink_entities, get_relationships,
        )
        register_entity("agent", "ul-a", "A")
        register_entity("task", "ul-t", "T")
        link_entities("ul-a", "ul-t", "owns")
        assert unlink_entities("ul-a", "ul-t") is True
        assert get_relationships("ul-a") == []

    def test_unlink_nonexistent_returns_false(self):
        from brynq.entity_registry import unlink_entities
        assert unlink_entities("no-a", "no-b") is False


# ── get_relationships ─────────────────────────────────────────────────


class TestGetRelationships:
    def test_returns_all_links(self):
        from brynq.entity_registry import register_entity, link_entities, get_relationships
        register_entity("agent", "gr-a", "Agent")
        register_entity("task", "gr-t1", "Task 1")
        register_entity("task", "gr-t2", "Task 2")
        link_entities("gr-a", "gr-t1", "assigned_to")
        link_entities("gr-a", "gr-t2", "owns")
        rels = get_relationships("gr-a")
        assert len(rels) == 2

    def test_no_relationships(self):
        from brynq.entity_registry import register_entity, get_relationships
        register_entity("agent", "lone", "Lone Wolf")
        assert get_relationships("lone") == []


# ── get_entity_graph ──────────────────────────────────────────────────


class TestGetEntityGraph:
    def test_depth_1(self):
        from brynq.entity_registry import register_entity, link_entities, get_entity_graph
        register_entity("agent", "g-a", "Agent")
        register_entity("task", "g-t", "Task")
        register_entity("workspace", "g-w", "Workspace")
        link_entities("g-a", "g-t", "assigned_to")
        link_entities("g-t", "g-w", "member_of")
        graph = get_entity_graph("g-a", depth=1)
        assert "g-a" in graph["nodes"]
        assert "g-t" in graph["nodes"]
        # Depth=1 should not reach g-w
        assert "g-w" not in graph["nodes"]

    def test_depth_2(self):
        from brynq.entity_registry import register_entity, link_entities, get_entity_graph
        register_entity("agent", "g2-a", "Agent")
        register_entity("task", "g2-t", "Task")
        register_entity("workspace", "g2-w", "Workspace")
        link_entities("g2-a", "g2-t", "assigned_to")
        link_entities("g2-t", "g2-w", "member_of")
        graph = get_entity_graph("g2-a", depth=2)
        assert "g2-w" in graph["nodes"]

    def test_nonexistent_root(self):
        from brynq.entity_registry import get_entity_graph
        graph = get_entity_graph("ghost")
        assert graph["root"] is None
        assert graph["nodes"] == {}


# ── get_stats ─────────────────────────────────────────────────────────


class TestGetStats:
    def test_empty_stats(self):
        from brynq.entity_registry import get_stats
        stats = get_stats()
        assert stats["total_entities"] == 0
        assert stats["total_relationships"] == 0

    def test_counts(self):
        from brynq.entity_registry import register_entity, link_entities, get_stats
        register_entity("agent", "s-a1", "A1")
        register_entity("agent", "s-a2", "A2")
        register_entity("task", "s-t1", "T1")
        link_entities("s-a1", "s-t1", "owns")
        stats = get_stats()
        assert stats["total_entities"] == 3
        assert stats["total_relationships"] == 1
        assert stats["by_type"]["agent"] == 2
        assert stats["by_type"]["task"] == 1
