"""
Tests for Phase 52: Long-term Agent Memory with Semantic Organization.
"""

import json
import time
from datetime import datetime, timezone, timedelta

import pytest


@pytest.fixture(autouse=True)
def _isolate(tmp_path, monkeypatch):
    """Redirect all agent memory storage to tmp_path."""
    mem_dir = tmp_path / "agent_memory"
    mem_dir.mkdir(parents=True, exist_ok=True)

    import brynq.server as srv
    monkeypatch.setattr(srv, "BROKER_DIR", tmp_path)

    import brynq.agent_memory as am
    monkeypatch.setattr(am, "AGENT_MEMORY_DIR", mem_dir)


# ── Remember ─────────────────────────────────────────────────────────────

class TestRemember:
    def test_remember_basic(self):
        from brynq.agent_memory import remember
        m = remember("agent1", "name", "Alice")
        assert m["key"] == "name"
        assert m["value"] == "Alice"
        assert m["category"] == "general"
        assert m["importance"] == 5

    def test_remember_with_category(self):
        from brynq.agent_memory import remember
        m = remember("agent1", "lang", "python", category="preference")
        assert m["category"] == "preference"

    def test_remember_with_importance(self):
        from brynq.agent_memory import remember
        m = remember("agent1", "critical_info", "secret", importance=10)
        assert m["importance"] == 10

    def test_remember_clamps_importance_low(self):
        from brynq.agent_memory import remember
        m = remember("agent1", "k", "v", importance=-5)
        assert m["importance"] == 1

    def test_remember_clamps_importance_high(self):
        from brynq.agent_memory import remember
        m = remember("agent1", "k", "v", importance=99)
        assert m["importance"] == 10

    def test_remember_update_existing(self):
        from brynq.agent_memory import remember, recall
        remember("agent1", "color", "blue")
        remember("agent1", "color", "red")
        results = recall("agent1", key="color")
        assert len(results) == 1
        assert results[0]["value"] == "red"

    def test_remember_creates_timestamp(self):
        from brynq.agent_memory import remember
        m = remember("agent1", "k", "v")
        assert "created_at" in m
        datetime.fromisoformat(m["created_at"])

    def test_remember_different_agents_isolated(self):
        from brynq.agent_memory import remember, recall
        remember("agent1", "name", "Alice")
        remember("agent2", "name", "Bob")
        r1 = recall("agent1", key="name")
        r2 = recall("agent2", key="name")
        assert r1[0]["value"] == "Alice"
        assert r2[0]["value"] == "Bob"


# ── Recall ───────────────────────────────────────────────────────────────

class TestRecall:
    def test_recall_by_key(self):
        from brynq.agent_memory import remember, recall
        remember("a1", "food", "pizza")
        results = recall("a1", key="food")
        assert len(results) == 1
        assert results[0]["value"] == "pizza"

    def test_recall_by_category(self):
        from brynq.agent_memory import remember, recall
        remember("a1", "k1", "v1", category="work")
        remember("a1", "k2", "v2", category="personal")
        remember("a1", "k3", "v3", category="work")
        results = recall("a1", category="work")
        assert len(results) == 2

    def test_recall_by_query(self):
        from brynq.agent_memory import remember, recall
        remember("a1", "project", "building a REST API in python")
        remember("a1", "hobby", "playing guitar")
        results = recall("a1", query="python")
        assert len(results) == 1
        assert results[0]["key"] == "project"

    def test_recall_no_match(self):
        from brynq.agent_memory import remember, recall
        remember("a1", "k", "v")
        results = recall("a1", key="nonexistent")
        assert results == []

    def test_recall_empty_agent(self):
        from brynq.agent_memory import recall
        results = recall("unknown_agent")
        assert results == []

    def test_recall_updates_access_count(self):
        from brynq.agent_memory import remember, recall
        remember("a1", "k", "v")
        recall("a1", key="k")
        recall("a1", key="k")
        results = recall("a1", key="k")
        # After 3 recalls, access_count should be >= 2 (first recall was +1, second +1, third +1 from before save)
        assert results[0]["access_count"] >= 2


# ── Forget ───────────────────────────────────────────────────────────────

class TestForget:
    def test_forget_existing(self):
        from brynq.agent_memory import remember, forget, recall
        remember("a1", "temp", "data")
        assert forget("a1", "temp") is True
        assert recall("a1", key="temp") == []

    def test_forget_nonexistent(self):
        from brynq.agent_memory import forget
        assert forget("a1", "nope") is False


# ── Get Context ──────────────────────────────────────────────────────────

class TestGetContext:
    def test_get_context_returns_relevant(self):
        from brynq.agent_memory import remember, get_context
        remember("a1", "python_exp", "5 years of Python development", importance=8)
        remember("a1", "guitar", "plays acoustic guitar", importance=3)
        remember("a1", "api_design", "experienced with REST API design", importance=7)
        results = get_context("a1", "build a Python REST API")
        # Python and API memories should rank higher
        keys = [r["key"] for r in results]
        assert "python_exp" in keys
        assert "api_design" in keys

    def test_get_context_respects_limit(self):
        from brynq.agent_memory import remember, get_context
        for i in range(20):
            remember("a1", f"mem_{i}", f"memory number {i}")
        results = get_context("a1", "memory", limit=5)
        assert len(results) == 5

    def test_get_context_empty_agent(self):
        from brynq.agent_memory import get_context
        results = get_context("unknown", "anything")
        assert results == []


# ── Consolidate ──────────────────────────────────────────────────────────

class TestConsolidate:
    def test_consolidate_prunes_old_low_importance(self):
        from brynq.agent_memory import remember, consolidate, _read_memories, _save_memories
        remember("a1", "important", "keep me", importance=9)
        remember("a1", "trivial", "old junk", importance=1)

        # Manually backdate the trivial memory
        memories = _read_memories("a1")
        for m in memories:
            if m["key"] == "trivial":
                old_date = (datetime.now(timezone.utc) - timedelta(days=60)).isoformat()
                m["created_at"] = old_date
        _save_memories("a1", memories)

        result = consolidate("a1")
        assert result["pruned_count"] == 1
        assert result["remaining_count"] == 1

    def test_consolidate_keeps_recent_low_importance(self):
        from brynq.agent_memory import remember, consolidate
        remember("a1", "recent_low", "just added", importance=1)
        result = consolidate("a1")
        assert result["pruned_count"] == 0

    def test_consolidate_keeps_old_high_importance(self):
        from brynq.agent_memory import remember, consolidate, _read_memories, _save_memories
        remember("a1", "old_important", "critical data", importance=8)
        memories = _read_memories("a1")
        for m in memories:
            old_date = (datetime.now(timezone.utc) - timedelta(days=60)).isoformat()
            m["created_at"] = old_date
        _save_memories("a1", memories)
        result = consolidate("a1")
        assert result["pruned_count"] == 0


# ── Stats ────────────────────────────────────────────────────────────────

class TestStats:
    def test_stats_empty(self):
        from brynq.agent_memory import get_memory_stats
        stats = get_memory_stats("unknown")
        assert stats["total"] == 0
        assert stats["by_category"] == {}

    def test_stats_counts(self):
        from brynq.agent_memory import remember, get_memory_stats
        remember("a1", "k1", "v1", category="work")
        remember("a1", "k2", "v2", category="work")
        remember("a1", "k3", "v3", category="personal")
        stats = get_memory_stats("a1")
        assert stats["total"] == 3
        assert stats["by_category"]["work"] == 2
        assert stats["by_category"]["personal"] == 1

    def test_stats_oldest_newest(self):
        from brynq.agent_memory import remember, get_memory_stats
        remember("a1", "first", "v1")
        remember("a1", "second", "v2")
        stats = get_memory_stats("a1")
        assert stats["oldest"] is not None
        assert stats["newest"] is not None
        assert stats["oldest"] <= stats["newest"]
