"""
Unit tests for the Redis short-term memory sliding window.

Uses fakeredis to avoid requiring a real Redis connection.
All tests verify tenant+agent+user namespacing and RPUSH/LTRIM correctness.
"""

from __future__ import annotations

import json

import fakeredis.aioredis
import pytest

from orchestrator.memory.short_term import append_message, get_recent_messages


@pytest.fixture
async def redis():
    """Return a fakeredis async client for testing."""
    client = fakeredis.aioredis.FakeRedis()
    yield client
    await client.aclose()


TENANT = "tenant-abc"
AGENT = "agent-xyz"
USER = "user-123"


async def test_get_recent_messages_empty(redis):
    """get_recent_messages on empty key returns empty list."""
    result = await get_recent_messages(redis, TENANT, AGENT, USER)
    assert result == []


async def test_append_and_get_single_message(redis):
    """append_message stores a message; get_recent_messages retrieves it."""
    await append_message(redis, TENANT, AGENT, USER, role="user", content="Hello!")
    result = await get_recent_messages(redis, TENANT, AGENT, USER)
    assert len(result) == 1
    assert result[0] == {"role": "user", "content": "Hello!"}


async def test_append_multiple_messages_ordering(redis):
    """Messages are returned in insertion order (oldest first)."""
    await append_message(redis, TENANT, AGENT, USER, role="user", content="First")
    await append_message(redis, TENANT, AGENT, USER, role="assistant", content="Second")
    await append_message(redis, TENANT, AGENT, USER, role="user", content="Third")

    result = await get_recent_messages(redis, TENANT, AGENT, USER)
    assert len(result) == 3
    assert result[0]["content"] == "First"
    assert result[1]["content"] == "Second"
    assert result[2]["content"] == "Third"


async def test_sliding_window_trims_to_window_size(redis):
    """append_message with window=5 keeps only last 5 messages."""
    for i in range(10):
        await append_message(redis, TENANT, AGENT, USER, role="user", content=f"msg-{i}", window=5)

    result = await get_recent_messages(redis, TENANT, AGENT, USER, n=20)
    assert len(result) == 5
    # Should have the last 5 messages: msg-5 through msg-9
    contents = [m["content"] for m in result]
    assert contents == ["msg-5", "msg-6", "msg-7", "msg-8", "msg-9"]


async def test_default_window_20(redis):
    """Default window is 20 — 21st message pushes out the first."""
    for i in range(21):
        await append_message(redis, TENANT, AGENT, USER, role="user", content=f"msg-{i}")

    result = await get_recent_messages(redis, TENANT, AGENT, USER)
    assert len(result) == 20
    assert result[0]["content"] == "msg-1"
    assert result[-1]["content"] == "msg-20"


async def test_get_recent_messages_n_parameter(redis):
    """get_recent_messages n parameter limits results."""
    for i in range(10):
        await append_message(redis, TENANT, AGENT, USER, role="user", content=f"msg-{i}")

    result = await get_recent_messages(redis, TENANT, AGENT, USER, n=3)
    assert len(result) == 3
    # n=3 returns last 3: msg-7, msg-8, msg-9
    contents = [m["content"] for m in result]
    assert contents == ["msg-7", "msg-8", "msg-9"]


async def test_key_namespacing_user_isolation(redis):
    """Different users of the same agent have isolated memory."""
    await append_message(redis, TENANT, AGENT, "user-A", role="user", content="User A message")
    await append_message(redis, TENANT, AGENT, "user-B", role="user", content="User B message")

    result_a = await get_recent_messages(redis, TENANT, AGENT, "user-A")
    result_b = await get_recent_messages(redis, TENANT, AGENT, "user-B")

    assert len(result_a) == 1
    assert result_a[0]["content"] == "User A message"
    assert len(result_b) == 1
    assert result_b[0]["content"] == "User B message"


async def test_key_namespacing_tenant_isolation(redis):
    """Different tenants with same agent+user IDs have isolated memory."""
    await append_message(redis, "tenant-1", AGENT, USER, role="user", content="Tenant 1 message")
    await append_message(redis, "tenant-2", AGENT, USER, role="user", content="Tenant 2 message")

    result_1 = await get_recent_messages(redis, "tenant-1", AGENT, USER)
    result_2 = await get_recent_messages(redis, "tenant-2", AGENT, USER)

    assert result_1[0]["content"] == "Tenant 1 message"
    assert result_2[0]["content"] == "Tenant 2 message"


async def test_key_namespacing_agent_isolation(redis):
    """Different agents for the same tenant+user have isolated memory."""
    await append_message(redis, TENANT, "agent-1", USER, role="user", content="Agent 1 context")
    await append_message(redis, TENANT, "agent-2", USER, role="user", content="Agent 2 context")

    result_1 = await get_recent_messages(redis, TENANT, "agent-1", USER)
    result_2 = await get_recent_messages(redis, TENANT, "agent-2", USER)

    assert result_1[0]["content"] == "Agent 1 context"
    assert result_2[0]["content"] == "Agent 2 context"


async def test_message_role_and_content_round_trip(redis):
    """Messages store and retrieve role + content correctly."""
    await append_message(redis, TENANT, AGENT, USER, role="assistant", content="I can help you with that.")
    result = await get_recent_messages(redis, TENANT, AGENT, USER)
    msg = result[0]
    assert msg["role"] == "assistant"
    assert msg["content"] == "I can help you with that."
    # Verify it has exactly role and content keys
    assert set(msg.keys()) == {"role", "content"}
