"""Tests for brynq.memory_mapper — Phase 65 ephemeral/persistent memory."""

import json
import time
import pytest
from pathlib import Path


@pytest.fixture(autouse=True)
def _isolate_memory(tmp_path, monkeypatch):
    """Redirect memory storage to temp directory and clear ephemeral state."""
    mem_dir = tmp_path / "memory_map"
    mem_dir.mkdir()
    monkeypatch.setattr("brynq.memory_mapper.MEMORY_MAP_DIR", mem_dir)
    monkeypatch.setattr("brynq.memory_mapper.PERSISTENT_PATH", mem_dir / "persistent.json")
    monkeypatch.setattr("brynq.memory_mapper.SESSION_PATH", mem_dir / "session.json")
    # Clear ephemeral store
    import brynq.memory_mapper as mm
    mm._ephemeral.clear()


# ── store ─────────────────────────────────────────────────────────────────


class TestStore:
    def test_persistent_default(self):
        from brynq.memory_mapper import store, get
        store("k1", "value1")
        assert get("k1") == "value1"

    def test_ephemeral(self):
        from brynq.memory_mapper import store, get
        store("k2", 42, persistence="ephemeral")
        assert get("k2") == 42

    def test_session(self):
        from brynq.memory_mapper import store, get
        store("k3", [1, 2, 3], persistence="session")
        assert get("k3") == [1, 2, 3]

    def test_invalid_persistence(self):
        from brynq.memory_mapper import store
        with pytest.raises(ValueError, match="persistence"):
            store("k", "v", persistence="invalid")

    def test_empty_key(self):
        from brynq.memory_mapper import store
        with pytest.raises(ValueError, match="Key"):
            store("", "v")

    def test_negative_ttl(self):
        from brynq.memory_mapper import store
        with pytest.raises(ValueError, match="ttl"):
            store("k", "v", ttl_hours=-1)

    def test_with_scope(self):
        from brynq.memory_mapper import store, get
        store("k4", "scoped", scope="agent-1")
        assert get("k4", scope="agent-1") == "scoped"
        assert get("k4", scope="global") is None

    def test_returns_metadata(self):
        from brynq.memory_mapper import store
        result = store("k5", "v")
        assert result["key"] == "k5"
        assert result["persistence"] == "persistent"

    def test_stores_complex_value(self):
        from brynq.memory_mapper import store, get
        store("complex", {"nested": {"data": [1, 2]}})
        assert get("complex")["nested"]["data"] == [1, 2]


# ── get ───────────────────────────────────────────────────────────────────


class TestGet:
    def test_not_found(self):
        from brynq.memory_mapper import get
        assert get("nonexistent") is None

    def test_ttl_expired(self, monkeypatch):
        from brynq.memory_mapper import store, get
        store("exp1", "val", persistence="ephemeral", ttl_hours=0.001)
        # Fast-forward time — capture real time before patching
        future = time.time() + 100
        monkeypatch.setattr("brynq.memory_mapper.time", type("FakeTime", (), {"time": staticmethod(lambda: future)})())
        assert get("exp1") is None

    def test_ttl_not_expired(self):
        from brynq.memory_mapper import store, get
        store("exp2", "val", persistence="ephemeral", ttl_hours=24)
        assert get("exp2") == "val"

    def test_disk_ttl_expired(self, monkeypatch):
        from brynq.memory_mapper import store, get
        store("dexp", "val", persistence="persistent", ttl_hours=0.001)
        future = time.time() + 100
        monkeypatch.setattr("brynq.memory_mapper.time", type("FakeTime", (), {"time": staticmethod(lambda: future)})())
        assert get("dexp") is None

    def test_checks_all_levels(self):
        from brynq.memory_mapper import store, get
        store("multi", "eph", persistence="ephemeral")
        assert get("multi") == "eph"


# ── delete ────────────────────────────────────────────────────────────────


class TestDelete:
    def test_delete_persistent(self):
        from brynq.memory_mapper import store, delete, get
        store("d1", "v")
        assert delete("d1") is True
        assert get("d1") is None

    def test_delete_ephemeral(self):
        from brynq.memory_mapper import store, delete, get
        store("d2", "v", persistence="ephemeral")
        assert delete("d2") is True
        assert get("d2") is None

    def test_delete_nonexistent(self):
        from brynq.memory_mapper import delete
        assert delete("no_such") is False

    def test_delete_scoped(self):
        from brynq.memory_mapper import store, delete, get
        store("d3", "v", scope="agent-x")
        assert delete("d3", scope="agent-x") is True
        assert get("d3", scope="agent-x") is None


# ── list_keys ─────────────────────────────────────────────────────────────


class TestListKeys:
    def test_empty(self):
        from brynq.memory_mapper import list_keys
        assert list_keys() == []

    def test_lists_all(self):
        from brynq.memory_mapper import store, list_keys
        store("a", 1, persistence="ephemeral")
        store("b", 2, persistence="persistent")
        store("c", 3, persistence="session")
        keys = list_keys()
        assert len(keys) == 3

    def test_filter_by_scope(self):
        from brynq.memory_mapper import store, list_keys
        store("x", 1, scope="s1")
        store("y", 2, scope="s2")
        keys = list_keys(scope="s1")
        assert len(keys) == 1
        assert keys[0]["key"] == "x"

    def test_filter_by_persistence(self):
        from brynq.memory_mapper import store, list_keys
        store("p1", 1, persistence="ephemeral")
        store("p2", 2, persistence="persistent")
        keys = list_keys(persistence="ephemeral")
        assert len(keys) == 1


# ── cleanup_expired ───────────────────────────────────────────────────────


class TestCleanupExpired:
    def test_removes_expired(self, monkeypatch):
        from brynq.memory_mapper import store, cleanup_expired, get
        store("e1", "v", persistence="ephemeral", ttl_hours=0.001)
        store("e2", "v", persistence="persistent", ttl_hours=0.001)
        future = time.time() + 100
        monkeypatch.setattr("brynq.memory_mapper.time", type("FakeTime", (), {"time": staticmethod(lambda: future)})())
        result = cleanup_expired()
        assert result["ephemeral"] >= 1
        assert result["persistent"] >= 1

    def test_keeps_non_expired(self):
        from brynq.memory_mapper import store, cleanup_expired, get
        store("keep", "v", persistence="ephemeral", ttl_hours=24)
        cleanup_expired()
        assert get("keep") == "v"


# ── get_memory_map ────────────────────────────────────────────────────────


class TestGetMemoryMap:
    def test_empty(self):
        from brynq.memory_mapper import get_memory_map
        mm = get_memory_map()
        assert mm["total_entries"] == 0

    def test_with_entries(self):
        from brynq.memory_mapper import store, get_memory_map
        store("a", 1, persistence="ephemeral", scope="global")
        store("b", 2, persistence="persistent", scope="agent-1")
        mm = get_memory_map()
        assert mm["total_entries"] == 2
        assert "global" in mm["scopes"]
        assert "agent-1" in mm["scopes"]

    def test_scopes_have_persistence_counts(self):
        from brynq.memory_mapper import store, get_memory_map
        store("x", 1, persistence="persistent")
        store("y", 2, persistence="session")
        mm = get_memory_map()
        global_scope = mm["scopes"].get("global", {})
        assert "persistent" in global_scope or "session" in global_scope
