# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# Licensed under the MIT License

"""Tests for MemoryTree working-tier context injection (Sprint 5)."""

from unittest.mock import MagicMock, patch

import pytest

from familiar.core.memory_tree import MemoryTree


@pytest.fixture
def tree(tmp_path):
    return MemoryTree(db_path=tmp_path / "inject_test.db")


@pytest.fixture
def prompt_builder(tree):
    """Create a PromptBuilder with mocked agent."""
    from familiar.core.prompt_builder import PromptBuilder

    agent = MagicMock()
    agent.provider = MagicMock()
    agent.config = MagicMock()
    agent.config.context = MagicMock()
    agent.config.context.max_memory_tree_tokens = 600
    pb = PromptBuilder.__new__(PromptBuilder)
    pb._agent = agent
    return pb


class TestMemoryTreeInjection:
    def test_inject_empty_tree(self, prompt_builder, tree):
        with patch("familiar.core.memory_tree.get_memory_tree", return_value=tree):
            result = prompt_builder.inject_memory_tree_context("base", "default")
        assert result == "base"

    def test_inject_with_working_memories(self, prompt_builder, tree):
        tree.store(title="User Name", body="George", tier="working")
        tree.store(title="Favorite Color", body="Blue", tier="working")
        with patch("familiar.core.memory_tree.get_memory_tree", return_value=tree):
            result = prompt_builder.inject_memory_tree_context("base", "default")
        assert "<memory_tree_working>" in result
        assert "User Name" in result
        assert "George" in result
        assert "Favorite Color" in result

    def test_inject_skips_non_working(self, prompt_builder, tree):
        tree.store(title="Archive Item", body="old", tier="archival")
        tree.store(title="Long Item", body="searchable", tier="long")
        with patch("familiar.core.memory_tree.get_memory_tree", return_value=tree):
            result = prompt_builder.inject_memory_tree_context("base", "default")
        assert result == "base"

    def test_inject_respects_token_budget(self, prompt_builder, tree):
        # Store many large entries
        for i in range(50):
            tree.store(
                title=f"Fact {i}",
                body="x" * 200,
                tier="working",
            )
        with patch("familiar.core.memory_tree.get_memory_tree", return_value=tree):
            result = prompt_builder.inject_memory_tree_context("base", "default")
        # Should not include all 50 (budget is 600 tokens * 4 chars = 2400 chars)
        assert result.count("Fact") < 50

    def test_inject_filters_by_user(self, prompt_builder, tree):
        tree.store(
            title="User A Fact",
            body="belongs to A",
            tier="working",
            user_id="user-a",
        )
        tree.store(
            title="User B Fact",
            body="belongs to B",
            tier="working",
            user_id="user-b",
        )
        with patch("familiar.core.memory_tree.get_memory_tree", return_value=tree):
            result = prompt_builder.inject_memory_tree_context("base", "user-a")
        assert "User A Fact" in result
        assert "User B Fact" not in result

    def test_inject_ordered_by_importance(self, prompt_builder, tree):
        tree.store(title="Low", body="low", tier="working", importance=0.1)
        tree.store(title="High", body="high", tier="working", importance=0.9)
        with patch("familiar.core.memory_tree.get_memory_tree", return_value=tree):
            result = prompt_builder.inject_memory_tree_context("base", "default")
        # High importance should come first
        high_pos = result.index("High")
        low_pos = result.index("Low")
        assert high_pos < low_pos

    def test_inject_skips_invalidated(self, prompt_builder, tree):
        entry = tree.store(title="Stale", body="old info", tier="working")
        tree.invalidate(entry.memory_id)
        with patch("familiar.core.memory_tree.get_memory_tree", return_value=tree):
            result = prompt_builder.inject_memory_tree_context("base", "default")
        assert result == "base"

    def test_inject_skips_local_provider(self, tree):
        """Ollama provider should skip injection."""
        from familiar.core.prompt_builder import PromptBuilder

        try:
            from familiar.core.providers import OllamaProvider
        except ImportError:
            pytest.skip("OllamaProvider not available")

        agent = MagicMock(spec_set=["provider", "config"])
        agent.provider = MagicMock(spec=OllamaProvider)
        agent.config = MagicMock()
        pb = PromptBuilder.__new__(PromptBuilder)
        pb._agent = agent

        tree.store(title="Test", body="data", tier="working")
        with patch("familiar.core.memory_tree.get_memory_tree", return_value=tree):
            result = pb.inject_memory_tree_context("base", "default")
        assert result == "base"

    def test_inject_graceful_on_error(self, prompt_builder):
        with patch(
            "familiar.core.memory_tree.get_memory_tree",
            side_effect=Exception("db error"),
        ):
            result = prompt_builder.inject_memory_tree_context("base", "default")
        assert result == "base"
