import asyncio

import pytest

from letta.config import LettaConfig
from letta.schemas.agent import CreateAgent
from letta.schemas.block import CreateBlock
from letta.schemas.group import (
    DynamicManager,
    DynamicManagerUpdate,
    GroupCreate,
    GroupUpdate,
    ManagerType,
    RoundRobinManagerUpdate,
    SupervisorManager,
)
from letta.schemas.message import MessageCreate
from letta.server.server import SyncServer


@pytest.fixture(scope="module")
def server():
    config = LettaConfig.load()
    print("CONFIG PATH", config.config_path)

    config.save()

    server = SyncServer()
    return server


@pytest.fixture
async def default_organization(server: SyncServer):
    """Fixture to create and return the default organization."""
    yield await server.organization_manager.create_default_organization_async()


@pytest.fixture
async def default_user(server: SyncServer, default_organization):
    """Fixture to create and return the default user within the default organization."""
    yield await server.user_manager.create_default_actor_async(org_id=default_organization.id)


@pytest.fixture
async def four_participant_agents(server, default_user):
    agent_fred = await server.create_agent_async(
        request=CreateAgent(
            name="fred",
            memory_blocks=[
                CreateBlock(
                    label="persona",
                    value="Your name is fred and you like to ski and have been wanting to go on a ski trip soon. You are speaking in a group chat with other agent pals where you participate in friendly banter.",
                ),
            ],
            model="openai/gpt-4o-mini",
            embedding="openai/text-embedding-3-small",
        ),
        actor=default_user,
    )
    agent_velma = await server.create_agent_async(
        request=CreateAgent(
            name="velma",
            memory_blocks=[
                CreateBlock(
                    label="persona",
                    value="Your name is velma and you like tropical locations. You are speaking in a group chat with other agent friends and you love to include everyone.",
                ),
            ],
            model="openai/gpt-4o-mini",
            embedding="openai/text-embedding-3-small",
        ),
        actor=default_user,
    )
    agent_daphne = await server.create_agent_async(
        request=CreateAgent(
            name="daphne",
            memory_blocks=[
                CreateBlock(
                    label="persona",
                    value="Your name is daphne and you love traveling abroad. You are speaking in a group chat with other agent friends and you love to keep in touch with them.",
                ),
            ],
            model="openai/gpt-4o-mini",
            embedding="openai/text-embedding-3-small",
        ),
        actor=default_user,
    )
    agent_shaggy = await server.create_agent_async(
        request=CreateAgent(
            name="shaggy",
            memory_blocks=[
                CreateBlock(
                    label="persona",
                    value="Your name is shaggy and your best friend is your dog, scooby. You are speaking in a group chat with other agent friends and you like to solve mysteries with them.",
                ),
            ],
            model="openai/gpt-4o-mini",
            embedding="openai/text-embedding-3-small",
        ),
        actor=default_user,
    )
    yield [agent_fred, agent_velma, agent_daphne, agent_shaggy]


@pytest.fixture
async def manager_agent(server, default_user):
    agent_scooby = await server.create_agent_async(
        request=CreateAgent(
            name="scooby",
            memory_blocks=[
                CreateBlock(
                    label="persona",
                    value="You are a puppy operations agent for Letta and you help run multi-agent group chats. Your job is to get to know the agents in your group and pick who is best suited to speak next in the conversation.",
                ),
                CreateBlock(
                    label="human",
                    value="",
                ),
            ],
            model="openai/gpt-4o-mini",
            embedding="openai/text-embedding-3-small",
        ),
        actor=default_user,
    )
    yield agent_scooby


async def test_empty_group(server, default_user):
    group = await server.group_manager.create_group_async(
        group=GroupCreate(
            description="This is a group chat between best friends all like to hang out together. In their free time they like to solve mysteries.",
            agent_ids=[],
        ),
        actor=default_user,
    )
    with pytest.raises(ValueError, match="Empty group"):
        await server.send_group_message_to_agent(
            group_id=group.id,
            actor=default_user,
            input_messages=[
                MessageCreate(
                    role="user",
                    content="what is everyone up to for the holidays?",
                ),
            ],
            stream_steps=False,
            stream_tokens=False,
        )
    await server.group_manager.delete_group_async(group_id=group.id, actor=default_user)


async def test_modify_group_pattern(server, default_user, four_participant_agents, manager_agent):
    group = await server.group_manager.create_group_async(
        group=GroupCreate(
            description="This is a group chat between best friends all like to hang out together. In their free time they like to solve mysteries.",
            agent_ids=[agent.id for agent in four_participant_agents],
        ),
        actor=default_user,
    )
    with pytest.raises(ValueError, match="Cannot change group pattern"):
        await server.group_manager.modify_group_async(
            group_id=group.id,
            group_update=GroupUpdate(
                manager_config=DynamicManagerUpdate(
                    manager_type=ManagerType.dynamic,
                    manager_agent_id=manager_agent.id,
                ),
            ),
            actor=default_user,
        )

    await server.group_manager.delete_group_async(group_id=group.id, actor=default_user)


async def test_list_agent_groups(server, default_user, four_participant_agents):
    group_a = await server.group_manager.create_group_async(
        group=GroupCreate(
            description="This is a group chat between best friends all like to hang out together. In their free time they like to solve mysteries.",
            agent_ids=[agent.id for agent in four_participant_agents],
        ),
        actor=default_user,
    )
    group_b = await server.group_manager.create_group_async(
        group=GroupCreate(
            description="This is a group chat between best friends all like to hang out together. In their free time they like to solve mysteries.",
            agent_ids=[four_participant_agents[0].id],
        ),
        actor=default_user,
    )

    agent_a_groups = server.agent_manager.list_groups(agent_id=four_participant_agents[0].id, actor=default_user)
    assert sorted([group.id for group in agent_a_groups]) == sorted([group_a.id, group_b.id])
    agent_b_groups = server.agent_manager.list_groups(agent_id=four_participant_agents[1].id, actor=default_user)
    assert [group.id for group in agent_b_groups] == [group_a.id]

    await server.group_manager.delete_group_async(group_id=group_a.id, actor=default_user)
    await server.group_manager.delete_group_async(group_id=group_b.id, actor=default_user)


async def test_round_robin(server, default_user, four_participant_agents):
    description = (
        "This is a group chat between best friends all like to hang out together. In their free time they like to solve mysteries."
    )
    group = await server.group_manager.create_group_async(
        group=GroupCreate(
            description=description,
            agent_ids=[agent.id for agent in four_participant_agents],
        ),
        actor=default_user,
    )

    # verify group creation
    assert group.manager_type == ManagerType.round_robin
    assert group.description == description
    assert group.agent_ids == [agent.id for agent in four_participant_agents]
    assert group.max_turns is None
    assert group.manager_agent_id is None
    assert group.termination_token is None

    try:
        server.group_manager.reset_messages(group_id=group.id, actor=default_user)
        response = await server.send_group_message_to_agent(
            group_id=group.id,
            actor=default_user,
            input_messages=[
                MessageCreate(
                    role="user",
                    content="what is everyone up to for the holidays?",
                ),
            ],
            stream_steps=False,
            stream_tokens=False,
        )
        assert response.usage.step_count == len(group.agent_ids)
        assert len(response.messages) == response.usage.step_count * 2
        for i, message in enumerate(response.messages):
            assert message.message_type == "reasoning_message" if i % 2 == 0 else "assistant_message"
            assert message.name == four_participant_agents[i // 2].name

        for agent_id in group.agent_ids:
            agent_messages = await server.get_agent_recall(
                user_id=default_user.id,
                agent_id=agent_id,
                group_id=group.id,
                reverse=True,
                return_message_object=False,
            )
            assert len(agent_messages) == len(group.agent_ids) + 2  # add one for user message, one for reasoning message

        # TODO: filter this to return a clean conversation history
        messages = server.group_manager.list_group_messages(
            group_id=group.id,
            actor=default_user,
        )
        assert len(messages) == (len(group.agent_ids) + 2) * len(group.agent_ids)

        max_turns = 3
        group = await server.group_manager.modify_group_async(
            group_id=group.id,
            group_update=GroupUpdate(
                agent_ids=[agent.id for agent in four_participant_agents][::-1],
                manager_config=RoundRobinManagerUpdate(
                    max_turns=max_turns,
                ),
            ),
            actor=default_user,
        )
        assert group.manager_type == ManagerType.round_robin
        assert group.description == description
        assert group.agent_ids == [agent.id for agent in four_participant_agents][::-1]
        assert group.max_turns == max_turns
        assert group.manager_agent_id is None
        assert group.termination_token is None

        server.group_manager.reset_messages(group_id=group.id, actor=default_user)

        response = await server.send_group_message_to_agent(
            group_id=group.id,
            actor=default_user,
            input_messages=[
                MessageCreate(
                    role="user",
                    content="when should we plan our next adventure?",
                ),
            ],
            stream_steps=False,
            stream_tokens=False,
        )
        assert response.usage.step_count == max_turns
        assert len(response.messages) == max_turns * 2

        for i, message in enumerate(response.messages):
            assert message.message_type == "reasoning_message" if i % 2 == 0 else "assistant_message"
            assert message.name == four_participant_agents[::-1][i // 2].name

        for i in range(len(group.agent_ids)):
            agent_messages = await server.get_agent_recall(
                user_id=default_user.id,
                agent_id=group.agent_ids[i],
                group_id=group.id,
                reverse=True,
                return_message_object=False,
            )
            expected_message_count = max_turns + 1 if i >= max_turns else max_turns + 2
            assert len(agent_messages) == expected_message_count

    finally:
        await server.group_manager.delete_group_async(group_id=group.id, actor=default_user)


async def test_supervisor(server, default_user, four_participant_agents):
    agent_scrappy = await server.create_agent_async(
        request=CreateAgent(
            name="shaggy",
            memory_blocks=[
                CreateBlock(
                    label="persona",
                    value="You are a puppy operations agent for Letta and you help run multi-agent group chats. Your role is to supervise the group, sending messages and aggregating the responses.",
                ),
                CreateBlock(
                    label="human",
                    value="",
                ),
            ],
            model="openai/gpt-4o-mini",
            embedding="openai/text-embedding-3-small",
        ),
        actor=default_user,
    )

    group = await server.group_manager.create_group_async(
        group=GroupCreate(
            description="This is a group chat between best friends all like to hang out together. In their free time they like to solve mysteries.",
            agent_ids=[agent.id for agent in four_participant_agents],
            manager_config=SupervisorManager(
                manager_agent_id=agent_scrappy.id,
            ),
        ),
        actor=default_user,
    )
    try:
        response = await server.send_group_message_to_agent(
            group_id=group.id,
            actor=default_user,
            input_messages=[
                MessageCreate(
                    role="user",
                    content="ask everyone what they like to do for fun and then come up with an activity for everyone to do together.",
                ),
            ],
            stream_steps=False,
            stream_tokens=False,
        )
        assert response.usage.step_count == 2
        assert len(response.messages) == 5

        # verify tool call
        assert response.messages[0].message_type == "reasoning_message"
        assert (
            response.messages[1].message_type == "tool_call_message"
            and response.messages[1].tool_call.name == "send_message_to_all_agents_in_group"
        )
        assert response.messages[2].message_type == "tool_return_message" and len(eval(response.messages[2].tool_return)) == len(
            four_participant_agents
        )
        assert response.messages[3].message_type == "reasoning_message"
        assert response.messages[4].message_type == "assistant_message"

    finally:
        await server.group_manager.delete_group_async(group_id=group.id, actor=default_user)
        server.agent_manager.delete_agent(agent_id=agent_scrappy.id, actor=default_user)


@pytest.mark.flaky(max_runs=2)
async def test_dynamic_group_chat(server, default_user, manager_agent, four_participant_agents):
    description = (
        "This is a group chat between best friends all like to hang out together. In their free time they like to solve mysteries."
    )
    # error on duplicate agent in participant list
    with pytest.raises(ValueError, match="Duplicate agent ids"):
        await server.group_manager.create_group_async(
            group=GroupCreate(
                description=description,
                agent_ids=[agent.id for agent in four_participant_agents] + [four_participant_agents[0].id],
                manager_config=DynamicManager(
                    manager_agent_id=manager_agent.id,
                ),
            ),
            actor=default_user,
        )
    # error on duplicate agent names
    duplicate_agent_shaggy = server.create_agent(
        request=CreateAgent(
            name="shaggy",
            model="openai/gpt-4o-mini",
            embedding="openai/text-embedding-3-small",
        ),
        actor=default_user,
    )
    with pytest.raises(ValueError, match="Duplicate agent names"):
        await server.group_manager.create_group_async(
            group=GroupCreate(
                description=description,
                agent_ids=[agent.id for agent in four_participant_agents] + [duplicate_agent_shaggy.id],
                manager_config=DynamicManager(
                    manager_agent_id=manager_agent.id,
                ),
            ),
            actor=default_user,
        )
    server.agent_manager.delete_agent(duplicate_agent_shaggy.id, actor=default_user)

    group = await server.group_manager.create_group_async(
        group=GroupCreate(
            description=description,
            agent_ids=[agent.id for agent in four_participant_agents],
            manager_config=DynamicManager(
                manager_agent_id=manager_agent.id,
            ),
        ),
        actor=default_user,
    )
    try:
        response = await server.send_group_message_to_agent(
            group_id=group.id,
            actor=default_user,
            input_messages=[
                MessageCreate(role="user", content="what is everyone up to for the holidays?"),
            ],
            stream_steps=False,
            stream_tokens=False,
        )
        assert response.usage.step_count == len(four_participant_agents) * 2
        assert len(response.messages) == response.usage.step_count * 2

    finally:
        await server.group_manager.delete_group_async(group_id=group.id, actor=default_user)
