# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Licensed under the MIT License - see LICENSE file for details

"""Tests for Pipeline 1: LLM-powered memory consolidation."""

from familiar.core.memory_consolidation import _naive_fallback, create_llm_summarizer


class _FakeMemory:
    """Minimal stand-in for episodic_memory.Memory."""

    def __init__(self, content: str):
        self.content = content


class _FakeProvider:
    """Mock LLM provider with a configurable response."""

    def __init__(self, text: str = "Summarized result.", fail: bool = False):
        self._text = text
        self._fail = fail
        self.calls = []

    def chat(self, messages, tools, system, max_tokens):
        self.calls.append({"messages": messages, "system": system})
        if self._fail:
            raise RuntimeError("provider error")

        class Resp:
            text = self._text

        return Resp()


# ── Test: no provider → naive fallback ──────────────────────

def test_no_provider_returns_naive_fallback():
    fn = create_llm_summarizer(provider=None)
    memories = [_FakeMemory("fact A"), _FakeMemory("fact B")]
    result = fn(memories)
    assert result == "fact A | fact B"


def test_naive_fallback_truncates():
    memories = [_FakeMemory(f"m{i}") for i in range(6)]
    result = _naive_fallback(memories)
    assert "and 3 more" in result


# ── Test: with mock provider → LLM call made ────────────────

def test_llm_summarizer_calls_provider():
    provider = _FakeProvider(text="Combined summary.")
    fn = create_llm_summarizer(provider=provider)
    memories = [_FakeMemory("alpha"), _FakeMemory("beta"), _FakeMemory("gamma")]
    result = fn(memories)
    assert result == "Combined summary."
    assert len(provider.calls) == 1
    assert "Consolidate" in provider.calls[0]["messages"][0]["content"]


def test_llm_summarizer_caps_input():
    provider = _FakeProvider(text="Short summary.")
    fn = create_llm_summarizer(provider=provider, max_memories=3)
    memories = [_FakeMemory(f"mem-{i}") for i in range(10)]
    result = fn(memories)
    # Should only include first 3 memories in the prompt
    msg_content = provider.calls[0]["messages"][0]["content"]
    assert "mem-0" in msg_content
    assert "mem-2" in msg_content
    assert "mem-3" not in msg_content
    assert result == "Short summary."


# ── Test: provider failure → fallback, no crash ──────────────

def test_provider_failure_falls_back():
    provider = _FakeProvider(fail=True)
    fn = create_llm_summarizer(provider=provider)
    memories = [_FakeMemory("x"), _FakeMemory("y")]
    result = fn(memories)
    # Should fall back to naive join
    assert result == "x | y"


def test_empty_memories():
    provider = _FakeProvider(text="something")
    fn = create_llm_summarizer(provider=provider)
    result = fn([])
    assert result == ""
    assert len(provider.calls) == 0


# ── Test: end-to-end with MemorySystem.consolidate() ─────────

def test_consolidate_with_llm_summarizer():
    """Integration: consolidate() uses LLM summarizer and produces SUMMARY memories."""
    from familiar.core.episodic_memory import MemorySystem, MemoryType

    ms = MemorySystem(max_memories=100, consolidation_threshold=0.9)

    # Store several memories with same type/tags so they group together
    for i in range(5):
        ms.store_conversation(
            messages=[
                {"role": "user", "content": f"question {i}"},
                {"role": "assistant", "content": f"answer {i}"},
            ],
            conversation_id=f"conv-{i}",
            importance=0.1,  # Very low importance → weak → eligible for consolidation
        )

    # Force-decay all memories so they fall below threshold
    for mem in ms._index.get_all():
        mem.current_strength = 0.1

    provider = _FakeProvider(text="Consolidated conversation summary.")
    summarizer = create_llm_summarizer(provider=provider)

    count = ms.consolidate(threshold=0.9, summarize_fn=summarizer)
    assert count > 0

    # Check that SUMMARY type memory was created
    summaries = ms._index.get_by_type(MemoryType.SUMMARY)
    assert len(summaries) >= 1
    assert summaries[0].content == "Consolidated conversation summary."
