"""
Tests for Memory Store

Tests cover:
- Memory storage and retrieval
- TTL expiration
- Access counting
- Pattern-based search
"""

import pytest
import asyncio
from datetime import datetime, timezone, timedelta

import sys
sys.path.insert(0, str(__file__).rsplit('/tests/', 1)[0])

from brain.memory_store import MemoryStore, Memory


class TestMemory:
    """Tests for Memory dataclass"""

    def test_memory_creation(self):
        """Memory should be created with correct values"""
        memory = Memory(
            key="test:key",
            value={"data": "value"},
            agent_id="tutor-agent"
        )

        assert memory.key == "test:key"
        assert memory.value["data"] == "value"
        assert memory.agent_id == "tutor-agent"
        assert memory.access_count == 0
        assert memory.expires_at is None

    def test_memory_with_expiration(self):
        """Memory should track expiration"""
        expires = datetime.now(timezone.utc) + timedelta(days=30)

        memory = Memory(
            key="temp:data",
            value="temporary",
            agent_id="agent",
            expires_at=expires
        )

        assert memory.expires_at == expires
        assert memory.is_expired() is False

    def test_memory_expired(self):
        """Should detect expired memory"""
        expires = datetime.now(timezone.utc) - timedelta(hours=1)

        memory = Memory(
            key="old:data",
            value="expired",
            agent_id="agent",
            expires_at=expires
        )

        assert memory.is_expired() is True

    def test_memory_serialization(self):
        """Memory should serialize correctly"""
        memory = Memory(
            key="test",
            value={"nested": {"data": [1, 2, 3]}},
            agent_id="test-agent",
            metadata={"source": "unit_test"}
        )

        data = memory.to_dict()
        restored = Memory.from_dict(data)

        assert restored.key == memory.key
        assert restored.value == memory.value
        assert restored.agent_id == memory.agent_id
        assert restored.metadata == memory.metadata


class TestMemoryStore:
    """Tests for MemoryStore"""

    @pytest.fixture
    def store(self):
        """Create a memory store without persistence"""
        return MemoryStore(
            redis_client=None,
            db_pool=None,
            namespace="test"
        )

    @pytest.mark.asyncio
    async def test_remember_and_recall(self, store):
        """Should store and retrieve values"""
        await store.remember(
            key="math:formula",
            value={"name": "pythagorean", "formula": "a² + b² = c²"},
            agent_id="tutor-agent"
        )

        value = await store.recall("math:formula")

        assert value is not None
        assert value["name"] == "pythagorean"

    @pytest.mark.asyncio
    async def test_recall_nonexistent(self, store):
        """Should return None for nonexistent key"""
        value = await store.recall("nonexistent:key")
        assert value is None

    @pytest.mark.asyncio
    async def test_get_memory(self, store):
        """Should retrieve full Memory object"""
        await store.remember(
            key="test:memory",
            value="test value",
            agent_id="test-agent",
            metadata={"category": "test"}
        )

        memory = await store.get_memory("test:memory")

        assert memory is not None
        assert memory.key == "test:memory"
        assert memory.agent_id == "test-agent"
        assert memory.access_count >= 1

    @pytest.mark.asyncio
    async def test_forget(self, store):
        """Should delete memory"""
        await store.remember(
            key="temporary",
            value="will be deleted",
            agent_id="agent"
        )

        result = await store.forget("temporary")
        assert result is True

        value = await store.recall("temporary")
        assert value is None

    @pytest.mark.asyncio
    async def test_search_pattern(self, store):
        """Should search by pattern"""
        await store.remember("eval:math:1", {"score": 80}, "grader")
        await store.remember("eval:math:2", {"score": 90}, "grader")
        await store.remember("eval:english:1", {"score": 85}, "grader")

        math_results = await store.search("eval:math:*")

        assert len(math_results) == 2

    @pytest.mark.asyncio
    async def test_get_by_agent(self, store):
        """Should filter by agent ID"""
        await store.remember("data:1", "value1", "agent-1")
        await store.remember("data:2", "value2", "agent-2")
        await store.remember("data:3", "value3", "agent-1")

        agent1_memories = await store.get_by_agent("agent-1")

        assert len(agent1_memories) == 2

    @pytest.mark.asyncio
    async def test_get_recent(self, store):
        """Should get recently created memories"""
        await store.remember("new:1", "value1", "agent")
        await store.remember("new:2", "value2", "agent")

        recent = await store.get_recent(hours=1)

        assert len(recent) == 2

    @pytest.mark.asyncio
    async def test_access_count_increment(self, store):
        """Should increment access count on recall"""
        await store.remember("counting", "value", "agent")

        # Access multiple times
        await store.recall("counting")
        await store.recall("counting")
        await store.recall("counting")

        memory = await store.get_memory("counting")
        assert memory.access_count >= 3

    @pytest.mark.asyncio
    async def test_cleanup_expired(self, store):
        """Should clean up expired memories"""
        # Create an expired memory manually
        expired_memory = Memory(
            key="expired",
            value="old data",
            agent_id="agent",
            expires_at=datetime.now(timezone.utc) - timedelta(hours=1)
        )
        store._local_cache["expired"] = expired_memory

        count = await store.cleanup_expired()

        assert count == 1
        assert "expired" not in store._local_cache
