"""
Tests for MemoryDecay (M.2)

Tests: evaluate(), archive_memory(), deprioritize_memory(), restore_from_archive().
"""

import pytest
from datetime import UTC, datetime, timedelta
from unittest.mock import MagicMock

from app.services.memory.memory_decay import MemoryDecay


def create_mock_memory(
    last_accessed_at=None,
    importance_level: str = "medium",
    decay_score: float = 1.0,
    is_archived: bool = False,
    archive_at=None,
):
    """Create a mock UserMemory for testing."""
    memory = MagicMock()
    memory.last_accessed_at = last_accessed_at
    memory.importance_level = importance_level
    memory.decay_score = decay_score
    memory.is_archived = is_archived
    memory.archive_at = archive_at
    return memory


class TestMemoryDecayEvaluate:
    """Test evaluate() method."""

    def test_evaluate_fresh_memory_keeps_active(self):
        """Fresh memory should be kept active."""
        decay = MemoryDecay()
        recent = datetime.now(UTC) - timedelta(hours=1)
        memory = create_mock_memory(last_accessed_at=recent)

        result = decay.evaluate(memory)

        assert result["action"] == "keep_active"
        assert result["should_archive"] is False
        assert result["should_deprioritize"] is False
        assert result["decay_score"] > 0.5

    def test_evaluate_old_low_importance_archives(self):
        """Old low-importance memory should be archived."""
        decay = MemoryDecay()
        old = datetime.now(UTC) - timedelta(days=100)
        memory = create_mock_memory(last_accessed_at=old, importance_level="low")

        result = decay.evaluate(memory)

        assert result["action"] == "archive"
        assert result["should_archive"] is True
        assert result["should_deprioritize"] is True
        assert result["decay_score"] < 0.2

    def test_evaluate_old_high_importance_deprioritizes(self):
        """Old high-importance memory may be deprioritized but not archived."""
        decay = MemoryDecay()
        # ~45 days for high: exp(-45/90) ≈ 0.6, still > 0.5
        old = datetime.now(UTC) - timedelta(days=45)
        memory = create_mock_memory(last_accessed_at=old, importance_level="high")

        result = decay.evaluate(memory)

        assert result["should_archive"] is False
        assert result["should_deprioritize"] is False
        assert 0.5 < result["decay_score"] < 0.7

    def test_evaluate_boundary_deprioritize(self):
        """Memory at ~42 days medium importance should be deprioritized but not archived."""
        decay = MemoryDecay()
        # ~42 days for medium: exp(-42/30) ≈ 0.25 < 0.5, > 0.2
        old = datetime.now(UTC) - timedelta(days=42)
        memory = create_mock_memory(last_accessed_at=old, importance_level="medium")

        result = decay.evaluate(memory)

        assert result["action"] == "deprioritize"
        assert result["should_deprioritize"] is True
        assert result["should_archive"] is False

    def test_evaluate_returns_all_keys(self):
        """evaluate() returns decay_score, should_archive, should_deprioritize, action."""
        decay = MemoryDecay()
        memory = create_mock_memory(last_accessed_at=datetime.now(UTC))

        result = decay.evaluate(memory)

        assert "decay_score" in result
        assert "should_archive" in result
        assert "should_deprioritize" in result
        assert "action" in result
        assert result["action"] in ("keep_active", "deprioritize", "archive")


class TestMemoryDecayArchiveMemory:
    """Test archive_memory() method."""

    def test_archive_sets_is_archived_true(self):
        """archive_memory() sets is_archived = True."""
        decay = MemoryDecay()
        memory = create_mock_memory(is_archived=False)

        result = decay.archive_memory(memory)

        assert result.is_archived is True

    def test_archive_sets_low_decay_score(self):
        """archive_memory() resets decay_score to 0.1."""
        decay = MemoryDecay()
        memory = create_mock_memory(decay_score=0.8)

        result = decay.archive_memory(memory)

        assert result.decay_score == 0.1

    def test_archive_sets_archive_at_timestamp(self):
        """archive_memory() sets archive_at to current time."""
        decay = MemoryDecay()
        memory = create_mock_memory(archive_at=None)

        before = datetime.now(UTC)
        result = decay.archive_memory(memory)
        after = datetime.now(UTC)

        assert result.archive_at is not None
        assert before <= result.archive_at <= after

    def test_archive_preserves_other_fields(self):
        """archive_memory() does not modify other fields."""
        decay = MemoryDecay()
        memory = create_mock_memory(
            last_accessed_at=datetime.now(UTC),
            importance_level="high",
            decay_score=0.5,
        )

        result = decay.archive_memory(memory)

        assert result.last_accessed_at == memory.last_accessed_at
        assert result.importance_level == "high"


class TestMemoryDecayDeprioritizeMemory:
    """Test deprioritize_memory() method."""

    def test_deprioritize_updates_decay_score(self):
        """deprioritize_memory() recalculates decay_score."""
        decay = MemoryDecay()
        # Old memory will have low decay score
        old = datetime.now(UTC) - timedelta(days=60)
        memory = create_mock_memory(
            last_accessed_at=old, importance_level="medium", decay_score=0.9
        )

        result = decay.deprioritize_memory(memory)

        assert result.decay_score < 0.5  # Should be recalculated low

    def test_deprioritize_does_not_archive(self):
        """deprioritize_memory() does NOT set is_archived."""
        decay = MemoryDecay()
        memory = create_mock_memory(is_archived=False)

        result = decay.deprioritize_memory(memory)

        assert result.is_archived is False


class TestMemoryDecayRestoreFromArchive:
    """Test restore_from_archive() method."""

    def test_restore_clears_is_archived(self):
        """restore_from_archive() sets is_archived = False."""
        decay = MemoryDecay()
        memory = create_mock_memory(is_archived=True)

        result = decay.restore_from_archive(memory)

        assert result.is_archived is False

    def test_restore_sets_decay_score_high(self):
        """restore_from_archive() sets decay_score to 0.8."""
        decay = MemoryDecay()
        memory = create_mock_memory(decay_score=0.1)

        result = decay.restore_from_archive(memory)

        assert result.decay_score == 0.8

    def test_restore_updates_last_accessed(self):
        """restore_from_archive() updates last_accessed_at to now."""
        decay = MemoryDecay()
        old_time = datetime.now(UTC) - timedelta(days=30)
        memory = create_mock_memory(
            last_accessed_at=old_time, is_archived=True, archive_at=old_time
        )

        before = datetime.now(UTC)
        result = decay.restore_from_archive(memory)
        after = datetime.now(UTC)

        assert before <= result.last_accessed_at <= after

    def test_restore_clears_archive_at(self):
        """restore_from_archive() sets archive_at to None."""
        decay = MemoryDecay()
        memory = create_mock_memory(is_archived=True, archive_at=datetime.now(UTC))

        result = decay.restore_from_archive(memory)

        assert result.archive_at is None


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
