"""Tests for KnowledgeGraph — entity-relation graph queries."""

from pathlib import Path
from memotrail.core.graph import KnowledgeGraph
from memotrail.storage.sqlite_store import SQLiteStore


def _make_graph(tmp_path: Path) -> tuple[KnowledgeGraph, SQLiteStore]:
    """Create a fresh KnowledgeGraph with a temp SQLite DB."""
    store = SQLiteStore(db_path=tmp_path / "test.db")
    store.connect()
    graph = KnowledgeGraph(store)
    return graph, store


class TestAddEntity:
    def test_add_new_entity(self, tmp_path):
        graph, store = _make_graph(tmp_path)
        session = store.create_session(project="proj1")
        eid = graph.add_entity("React", "library", session.id)
        assert eid.startswith("ent_")

    def test_duplicate_entity_increments_count(self, tmp_path):
        graph, store = _make_graph(tmp_path)
        session = store.create_session(project="proj1")
        eid1 = graph.add_entity("React", "library", session.id)
        eid2 = graph.add_entity("react", "library", session.id)
        assert eid1 == eid2

        entity = graph.get_entity("react")
        assert entity["mention_count"] == 2

    def test_entity_with_description(self, tmp_path):
        graph, store = _make_graph(tmp_path)
        session = store.create_session(project="proj1")
        graph.add_entity("PostgreSQL", "service", session.id, description="Main database")
        entity = graph.get_entity("postgresql")
        assert entity["description"] == "Main database"


class TestAddRelation:
    def test_add_relation(self, tmp_path):
        graph, store = _make_graph(tmp_path)
        session = store.create_session(project="proj1")
        eid1 = graph.add_entity("FastAPI", "framework", session.id)
        eid2 = graph.add_entity("Starlette", "library", session.id)
        rid = graph.add_relation(eid1, eid2, "uses", session.id)
        assert rid.startswith("rel_")

    def test_duplicate_relation_skipped(self, tmp_path):
        graph, store = _make_graph(tmp_path)
        session = store.create_session(project="proj1")
        eid1 = graph.add_entity("A", "library", session.id)
        eid2 = graph.add_entity("B", "library", session.id)
        rid1 = graph.add_relation(eid1, eid2, "uses", session.id)
        rid2 = graph.add_relation(eid1, eid2, "uses", session.id)
        assert rid1 == rid2

    def test_relation_with_context(self, tmp_path):
        graph, store = _make_graph(tmp_path)
        session = store.create_session(project="proj1")
        eid1 = graph.add_entity("App", "service", session.id)
        eid2 = graph.add_entity("Redis", "service", session.id)
        graph.add_relation(eid1, eid2, "uses", session.id, context="for caching")

        relations = graph.get_entity_relations(eid1)
        assert len(relations) == 1
        assert relations[0]["context"] == "for caching"


class TestGetEntity:
    def test_get_nonexistent(self, tmp_path):
        graph, _ = _make_graph(tmp_path)
        assert graph.get_entity("nonexistent") is None

    def test_get_entity_with_relations(self, tmp_path):
        graph, store = _make_graph(tmp_path)
        session = store.create_session(project="proj1")
        eid1 = graph.add_entity("FastAPI", "framework", session.id)
        eid2 = graph.add_entity("Starlette", "library", session.id)
        graph.add_relation(eid1, eid2, "uses", session.id)

        entity = graph.get_entity("fastapi")
        assert entity is not None
        assert entity["name"] == "FastAPI"
        assert len(entity["relations"]) == 1

    def test_get_entity_case_insensitive(self, tmp_path):
        graph, store = _make_graph(tmp_path)
        session = store.create_session(project="proj1")
        graph.add_entity("React", "library", session.id)
        assert graph.get_entity("REACT") is not None
        assert graph.get_entity("react") is not None


class TestGetEntitiesByType:
    def test_filter_by_type(self, tmp_path):
        graph, store = _make_graph(tmp_path)
        session = store.create_session(project="proj1")
        graph.add_entity("React", "library", session.id)
        graph.add_entity("Vue", "library", session.id)
        graph.add_entity("PostgreSQL", "service", session.id)

        libraries = graph.get_entities_by_type("library")
        assert len(libraries) == 2

        services = graph.get_entities_by_type("service")
        assert len(services) == 1

    def test_empty_type(self, tmp_path):
        graph, _ = _make_graph(tmp_path)
        assert graph.get_entities_by_type("nonexistent") == []

    def test_ordered_by_mention_count(self, tmp_path):
        graph, store = _make_graph(tmp_path)
        session = store.create_session(project="proj1")
        graph.add_entity("React", "library", session.id)
        graph.add_entity("Vue", "library", session.id)
        graph.add_entity("React", "library", session.id)  # mention again

        libraries = graph.get_entities_by_type("library")
        assert libraries[0]["name"] == "React"
        assert libraries[0]["mention_count"] == 2


class TestGetRelatedEntities:
    def test_bfs_single_hop(self, tmp_path):
        graph, store = _make_graph(tmp_path)
        session = store.create_session(project="proj1")
        eid1 = graph.add_entity("A", "library", session.id)
        eid2 = graph.add_entity("B", "library", session.id)
        graph.add_relation(eid1, eid2, "uses", session.id)

        related = graph.get_related_entities("A", max_depth=1)
        assert len(related) == 1
        assert related[0]["name"] == "B"
        assert related[0]["depth"] == 1

    def test_bfs_two_hops(self, tmp_path):
        graph, store = _make_graph(tmp_path)
        session = store.create_session(project="proj1")
        eid1 = graph.add_entity("A", "library", session.id)
        eid2 = graph.add_entity("B", "library", session.id)
        eid3 = graph.add_entity("C", "library", session.id)
        graph.add_relation(eid1, eid2, "uses", session.id)
        graph.add_relation(eid2, eid3, "uses", session.id)

        related = graph.get_related_entities("A", max_depth=2)
        assert len(related) == 2
        names = [r["name"] for r in related]
        assert "B" in names
        assert "C" in names

    def test_bfs_no_cycles(self, tmp_path):
        graph, store = _make_graph(tmp_path)
        session = store.create_session(project="proj1")
        eid1 = graph.add_entity("A", "library", session.id)
        eid2 = graph.add_entity("B", "library", session.id)
        graph.add_relation(eid1, eid2, "uses", session.id)
        graph.add_relation(eid2, eid1, "uses", session.id)

        related = graph.get_related_entities("A", max_depth=3)
        assert len(related) == 1  # Only B, no revisit of A

    def test_nonexistent_entity(self, tmp_path):
        graph, _ = _make_graph(tmp_path)
        assert graph.get_related_entities("nonexistent") == []

    def test_respects_max_depth(self, tmp_path):
        graph, store = _make_graph(tmp_path)
        session = store.create_session(project="proj1")
        eid1 = graph.add_entity("A", "library", session.id)
        eid2 = graph.add_entity("B", "library", session.id)
        eid3 = graph.add_entity("C", "library", session.id)
        graph.add_relation(eid1, eid2, "uses", session.id)
        graph.add_relation(eid2, eid3, "uses", session.id)

        related = graph.get_related_entities("A", max_depth=1)
        assert len(related) == 1
        assert related[0]["name"] == "B"


class TestQueryGraph:
    def test_query_libraries(self, tmp_path):
        graph, store = _make_graph(tmp_path)
        session = store.create_session(project="proj1")
        graph.add_entity("React", "library", session.id)
        graph.add_entity("lodash", "library", session.id)

        result = graph.query_graph("What libraries are used?")
        assert "React" in result
        assert "lodash" in result

    def test_query_services(self, tmp_path):
        graph, store = _make_graph(tmp_path)
        session = store.create_session(project="proj1")
        graph.add_entity("PostgreSQL", "service", session.id)

        result = graph.query_graph("What databases are used?")
        assert "PostgreSQL" in result

    def test_query_empty_graph(self, tmp_path):
        graph, _ = _make_graph(tmp_path)
        result = graph.query_graph("What libraries are used?")
        assert "No" in result or "no" in result

    def test_query_overview(self, tmp_path):
        graph, store = _make_graph(tmp_path)
        session = store.create_session(project="proj1")
        graph.add_entity("React", "library", session.id)
        graph.add_entity("PostgreSQL", "service", session.id)

        result = graph.query_graph("Show me everything")
        assert "Knowledge Graph Overview" in result

    def test_query_frameworks(self, tmp_path):
        graph, store = _make_graph(tmp_path)
        session = store.create_session(project="proj1")
        graph.add_entity("Django", "framework", session.id)

        result = graph.query_graph("What frameworks?")
        assert "Django" in result
