import numpy as np
import pytest

from masfactory.adapters.context.types import ContextBlock, ContextQuery
from masfactory.adapters.memory import HistoryMemory, VectorMemory
from masfactory.components.agents.agent import Agent


def test_history_memory_get_messages_semantics():
    mem = HistoryMemory(top_k=2, memory_size=3)
    mem.insert("user", "u1")
    mem.insert("assistant", "a1")
    mem.insert("user", "u2")
    mem.insert("assistant", "a2")  # evicts u1

    all_msgs = mem.get_messages(top_k=0)
    assert [m["content"] for m in all_msgs] == ["a1", "u2", "a2"]

    default_msgs = mem.get_messages()
    assert [m["content"] for m in default_msgs] == ["u2", "a2"]

    last_one = mem.get_messages(top_k=1)
    assert [m["content"] for m in last_one] == ["a2"]


def test_vector_memory_get_blocks_ranking_and_eviction():
    def embed(text: str) -> np.ndarray:
        # Small deterministic embedding: [len, count(a), count(b)]
        return np.array([len(text), text.count("a"), text.count("b")], dtype=float)

    mem = VectorMemory(embedding_function=embed, query_threshold=0.0, memory_size=1)
    mem.insert("k1", "apple")
    mem.insert("k2", "banana")  # evicts k1

    blocks = mem.get_blocks(ContextQuery(query_text="banana"), top_k=0)
    assert len(blocks) == 1
    assert blocks[0].text == "banana"
    assert blocks[0].metadata.get("key") == "k2"


def test_agent_exposes_active_providers_as_tools():
    class _ActiveProvider:
        context_label = "RAG"
        active = True
        passive = False
        supports_active = True
        supports_passive = True

        def get_blocks(self, query: ContextQuery, *, top_k: int = 8) -> list[ContextBlock]:
            return [ContextBlock(text=f"hit:{query.query_text}", score=1.0)]

    agent = Agent(
        name="a",
        instructions="do",
        model=object(),
        retrievers=[_ActiveProvider()],
    )

    agent.observe({"input": "hello"})
    assert agent._tool_adapter is not None

    sources = agent._tool_adapter.call("list_context_sources", {})  # type: ignore[union-attr]
    assert sources["sources"][0]["name"] == "RAG"

    out = agent._tool_adapter.call(  # type: ignore[union-attr]
        "retrieve_context",
        {"source": "RAG", "query": "q", "top_k": 1},
    )
    assert out["provider"] == "RAG"
    assert out["blocks"][0]["text"] == "hit:q"


def test_agent_disambiguates_duplicate_active_context_labels():
    class _MCPProvider:
        context_label = "MCP"
        active = True
        passive = False
        supports_active = True
        supports_passive = True

        def __init__(self, n: int):
            self._n = n

        def get_blocks(self, query: ContextQuery, *, top_k: int = 8) -> list[ContextBlock]:
            return [ContextBlock(text=f"{self._n}:{query.query_text}", score=1.0)]

    agent = Agent(
        name="a",
        instructions="do",
        model=object(),
        retrievers=[_MCPProvider(1), _MCPProvider(2)],
    )
    agent.observe({"input": "hello"})

    sources = agent._tool_adapter.call("list_context_sources", {})  # type: ignore[union-attr]
    names = [s["name"] for s in sources["sources"]]
    assert names == ["MCP#1", "MCP#2"]

    out1 = agent._tool_adapter.call(  # type: ignore[union-attr]
        "retrieve_context",
        {"source": "MCP#1", "query": "q", "top_k": 1},
    )
    out2 = agent._tool_adapter.call(  # type: ignore[union-attr]
        "retrieve_context",
        {"source": "MCP#2", "query": "q", "top_k": 1},
    )
    assert out1["blocks"][0]["text"] == "1:q"
    assert out2["blocks"][0]["text"] == "2:q"

    with pytest.raises(ValueError):
        agent._tool_adapter.call(  # type: ignore[union-attr]
            "retrieve_context",
            {"source": "MCP", "query": "q", "top_k": 1},
        )
