"""Tests for Agent.run() tool call loop."""
import pytest

from logos_adk.agent import Agent
from logos_adk.providers import provider_registry
from logos_adk.providers.mock import MockProvider
from logos_adk.runtime import _default_runtime_holder
from logos_adk.tools import tool
from logos_adk.types import Response, ToolCall, Usage


@pytest.fixture(autouse=True)
def _reset_runtime_and_registry():
    _default_runtime_holder.reset()
    provider_registry._entries.clear()
    yield
    _default_runtime_holder.reset()
    provider_registry._entries.clear()


@tool
async def add(a: int, b: int) -> int:
    """Add two numbers."""
    return a + b


@tool
async def multiply(a: int, b: int) -> int:
    """Multiply two numbers."""
    return a * b


@pytest.mark.asyncio
async def test_single_tool_call():
    tc = ToolCall(id="tc1", name="add", arguments={"a": 2, "b": 3})
    provider = MockProvider(
        responses=[
            Response(content="", tool_calls=[tc], usage=Usage(10, 5), raw={}),
            "The result is 5",
        ]
    )
    agent = Agent("bot").model(provider).tools([add])
    result = await agent.run("what is 2 + 3?")
    assert result.content == "The result is 5"
    assert len(provider.call_history) == 2


@pytest.mark.asyncio
async def test_multiple_tool_calls_in_one_response():
    tc1 = ToolCall(id="tc1", name="add", arguments={"a": 1, "b": 2})
    tc2 = ToolCall(id="tc2", name="multiply", arguments={"a": 3, "b": 4})
    provider = MockProvider(
        responses=[
            Response(content="", tool_calls=[tc1, tc2], usage=Usage(10, 5), raw={}),
            "1+2=3 and 3*4=12",
        ]
    )
    agent = Agent("bot").model(provider).tools([add, multiply])
    result = await agent.run("compute")
    assert result.content == "1+2=3 and 3*4=12"

    second_call_messages = provider.call_history[1]["messages"]
    tool_results = [message for message in second_call_messages if message.role == "tool"]
    assert len(tool_results) == 2


@pytest.mark.asyncio
async def test_multi_step_tool_calls():
    tc1 = ToolCall(id="tc1", name="add", arguments={"a": 1, "b": 2})
    tc2 = ToolCall(id="tc2", name="multiply", arguments={"a": 3, "b": 3})
    provider = MockProvider(
        responses=[
            Response(content="", tool_calls=[tc1], usage=Usage(10, 5), raw={}),
            Response(content="", tool_calls=[tc2], usage=Usage(10, 5), raw={}),
            "1+2=3, then 3*3=9",
        ]
    )
    agent = Agent("bot").model(provider).tools([add, multiply])
    result = await agent.run("compute step by step")
    assert result.content == "1+2=3, then 3*3=9"
    assert len(provider.call_history) == 3


@pytest.mark.asyncio
async def test_tool_error_sent_to_llm():
    @tool
    async def failing_tool(x: str) -> str:
        """Always fails."""
        raise ValueError("something broke")

    tc = ToolCall(id="tc1", name="failing_tool", arguments={"x": "test"})
    provider = MockProvider(
        responses=[
            Response(content="", tool_calls=[tc], usage=Usage(10, 5), raw={}),
            "Sorry, the tool failed",
        ]
    )
    agent = Agent("bot").model(provider).tools([failing_tool])
    result = await agent.run("try it")
    assert result.content == "Sorry, the tool failed"

    second_call = provider.call_history[1]["messages"]
    tool_msg = [message for message in second_call if message.role == "tool"][0]
    assert tool_msg.tool_result is not None
    assert "ValueError" in tool_msg.tool_result.error


@pytest.mark.asyncio
async def test_unknown_tool_is_reported_back_to_model():
    tc = ToolCall(id="tc-missing", name="missing_tool", arguments={"x": "test"})
    provider = MockProvider(
        responses=[
            Response(content="", tool_calls=[tc], usage=Usage(10, 5), raw={}),
            "I could not run that tool",
        ]
    )

    agent = Agent("bot").model(provider)
    result = await agent.run("try missing tool")

    assert result.content == "I could not run that tool"
    second_call_messages = provider.call_history[1]["messages"]
    tool_msg = [message for message in second_call_messages if message.role == "tool"][0]
    assert tool_msg.content == ""
    assert tool_msg.tool_result is not None
    assert tool_msg.tool_result.error == "Tool 'missing_tool' not found"


@pytest.mark.asyncio
async def test_max_tool_rounds():
    tc = ToolCall(id="tc1", name="add", arguments={"a": 1, "b": 1})
    infinite_response = Response(content="", tool_calls=[tc], usage=Usage(10, 5), raw={})
    provider = MockProvider(responses=[infinite_response])

    agent = Agent("bot").model(provider).tools([add])
    result = await agent.run("loop forever")
    assert result.tool_calls == [tc]
    assert len(provider.call_history) <= 12
