"""Tests for ConceptGraph — SQLite-backed concept co-occurrence graph."""

import sqlite3

import pytest

from neo_cortex.graph import ConceptGraph


def _make_conn(tmp_path, name="graph.db"):
    return sqlite3.connect(str(tmp_path / name), check_same_thread=False)


@pytest.fixture
def graph(tmp_path):
    return ConceptGraph(_make_conn(tmp_path))


@pytest.fixture
def graph_with_data(tmp_path):
    """Graph with: encoding-BOM-stdin (mem1), encoding-BOM-utf8 (mem2), deploy-systemd (mem3)."""
    g = ConceptGraph(_make_conn(tmp_path))
    g.add_memory("mem1", ["encoding", "BOM", "stdin"])
    g.add_memory("mem2", ["encoding", "BOM", "utf8"])
    g.add_memory("mem3", ["deploy", "systemd"])
    return g


class TestAddMemory:
    def test_creates_nodes(self, graph):
        graph.add_memory("mem1", ["encoding", "BOM", "stdin"])
        assert graph.has_node("encoding")
        assert graph.has_node("BOM")
        assert graph.has_node("stdin")

    def test_creates_cooccurrence_edges(self, graph):
        graph.add_memory("mem1", ["encoding", "BOM", "stdin"])
        assert graph.has_edge("encoding", "BOM")
        assert graph.has_edge("encoding", "stdin")
        assert graph.has_edge("BOM", "stdin")

    def test_cooccurrence_weight_increments(self, graph):
        graph.add_memory("mem1", ["encoding", "BOM"])
        graph.add_memory("mem2", ["encoding", "BOM"])
        assert graph.edge_weight("encoding", "BOM") == 2

    def test_mentions_increment(self, graph):
        graph.add_memory("mem1", ["encoding"])
        graph.add_memory("mem2", ["encoding"])
        assert graph.mentions("encoding") == 2

    def test_memory_links(self, graph):
        graph.add_memory("mem1", ["encoding"])
        graph.add_memory("mem2", ["encoding"])
        assert set(graph.memories_for("encoding")) == {"mem1", "mem2"}

    def test_empty_concepts_noop(self, graph):
        graph.add_memory("mem1", [])
        assert graph.node_count() == 0

    def test_single_concept_no_edges(self, graph):
        graph.add_memory("mem1", ["encoding"])
        assert graph.has_node("encoding")
        assert graph.node_count() == 1


class TestRemoveMemory:
    def test_decrements_edge_weight(self, graph):
        graph.add_memory("mem1", ["encoding", "BOM"])
        graph.add_memory("mem2", ["encoding", "BOM"])
        graph.remove_memory("mem1", ["encoding", "BOM"])
        assert graph.edge_weight("encoding", "BOM") == 1
        assert graph.mentions("encoding") == 1

    def test_removes_zero_weight_edges(self, graph):
        graph.add_memory("mem1", ["encoding", "BOM"])
        graph.remove_memory("mem1", ["encoding", "BOM"])
        assert not graph.has_edge("encoding", "BOM")


class TestSpreadingActivation:
    def test_direct_concept_activation(self, graph_with_data):
        activated = graph_with_data.spreading_activation(["encoding"])
        assert activated["encoding"] == 1.0

    def test_neighbor_activation_decays(self, graph_with_data):
        activated = graph_with_data.spreading_activation(["encoding"])
        assert "BOM" in activated
        assert activated["BOM"] < 1.0

    def test_two_hop_activation(self, graph_with_data):
        """encoding → BOM → utf8 (2 hops, decay²)."""
        activated = graph_with_data.spreading_activation(["encoding"], hops=2)
        assert "utf8" in activated
        assert activated["utf8"] < activated["BOM"]

    def test_empty_concepts(self, graph_with_data):
        assert graph_with_data.spreading_activation([]) == {}

    def test_unknown_concept(self, graph_with_data):
        assert graph_with_data.spreading_activation(["nonexistent"]) == {}

    def test_get_related_memories(self, graph_with_data):
        memories = graph_with_data.get_related_memories(["encoding"], n=5)
        assert len(memories) >= 1
        assert all(isinstance(m, str) for m in memories)
        assert "mem1" in memories or "mem2" in memories

    def test_disconnected_not_activated(self, graph_with_data):
        """deploy/systemd should NOT activate from encoding (disconnected)."""
        activated = graph_with_data.spreading_activation(["encoding"], hops=2)
        assert "deploy" not in activated
        assert "systemd" not in activated


class TestGraphPersistence:
    def test_save_and_load(self, tmp_path):
        db = str(tmp_path / "graph.db")
        conn1 = sqlite3.connect(db, check_same_thread=False)
        g1 = ConceptGraph(conn1)
        g1.add_memory("mem1", ["encoding", "BOM"])
        g1.save()
        conn1.close()
        conn2 = sqlite3.connect(db, check_same_thread=False)
        g2 = ConceptGraph(conn2)
        assert g2.has_node("encoding")
        assert g2.has_edge("encoding", "BOM")

    def test_empty_graph_on_new_db(self, tmp_path):
        g = ConceptGraph(_make_conn(tmp_path, "empty.db"))
        assert g.node_count() == 0

    def test_auto_save_on_add(self, tmp_path):
        db = str(tmp_path / "graph.db")
        conn1 = sqlite3.connect(db, check_same_thread=False)
        g = ConceptGraph(conn1)
        g.add_memory("mem1", ["encoding"])
        conn1.close()
        conn2 = sqlite3.connect(db, check_same_thread=False)
        g2 = ConceptGraph(conn2)
        assert g2.has_node("encoding")
