import pytest
from neo_cortex.models import MemoryRecord, RecalledMemory


def make_memory(id: str, sim: float) -> RecalledMemory:
    return RecalledMemory(
        record=MemoryRecord(
            id=id, session_id="s1",
            question="q", answer_preview="a", document="doc"
        ),
        similarity=sim,
    )


def test_rrf_combines_vector_and_fts():
    """RRF merges vector and FTS results, promoting docs found in both."""
    from neo_cortex.recall_fusion import reciprocal_rank_fusion

    vector = [make_memory("a", 0.9), make_memory("b", 0.7), make_memory("c", 0.5)]
    fts = [make_memory("b", 0.8), make_memory("d", 0.6), make_memory("a", 0.4)]

    result = reciprocal_rank_fusion(vector, fts, k=60, n=3)
    ids = [r.record.id for r in result]
    # "a" and "b" appear in both lists — should rank top 2
    assert "a" in ids[:2]
    assert "b" in ids[:2]
    assert len(result) <= 3


def test_rrf_both_lists_boosts_rank():
    """A memory in both lists gets higher RRF score than one in only one."""
    from neo_cortex.recall_fusion import reciprocal_rank_fusion

    vector = [make_memory("a", 0.9), make_memory("shared", 0.5)]
    fts = [make_memory("shared", 0.8), make_memory("b", 0.6)]

    result = reciprocal_rank_fusion(vector, fts, k=60, n=5)
    scores = {r.record.id: r.similarity for r in result}
    # "shared" appears in both → highest normalized score (1.0)
    assert scores["shared"] == 1.0


def test_rrf_empty_fts():
    """RRF with empty FTS returns vector results unchanged in order."""
    from neo_cortex.recall_fusion import reciprocal_rank_fusion

    vector = [make_memory("a", 0.9), make_memory("b", 0.7)]
    result = reciprocal_rank_fusion(vector, [], k=60, n=5)
    assert [r.record.id for r in result] == ["a", "b"]


def test_rrf_empty_vector():
    """RRF with empty vector returns FTS results."""
    from neo_cortex.recall_fusion import reciprocal_rank_fusion

    fts = [make_memory("x", 0.5), make_memory("y", 0.3)]
    result = reciprocal_rank_fusion([], fts, k=60, n=5)
    assert [r.record.id for r in result] == ["x", "y"]


def test_rrf_both_empty():
    """RRF with both empty returns empty."""
    from neo_cortex.recall_fusion import reciprocal_rank_fusion

    result = reciprocal_rank_fusion([], [], k=60, n=5)
    assert result == []


def test_rrf_normalized_scores():
    """RRF output similarities are normalized to [0, 1]."""
    from neo_cortex.recall_fusion import reciprocal_rank_fusion

    vector = [make_memory("a", 0.9), make_memory("b", 0.5)]
    fts = [make_memory("c", 0.7), make_memory("a", 0.3)]
    result = reciprocal_rank_fusion(vector, fts, k=60, n=5)
    for r in result:
        assert 0.0 <= r.similarity <= 1.0
    # First result should be normalized to 1.0
    assert result[0].similarity == 1.0


def test_rrf_respects_n_limit():
    """RRF returns at most n results."""
    from neo_cortex.recall_fusion import reciprocal_rank_fusion

    vector = [make_memory(f"v{i}", 0.9 - i * 0.1) for i in range(10)]
    result = reciprocal_rank_fusion(vector, [], k=60, n=3)
    assert len(result) == 3
