import math
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from any_agent import AgentConfig, AgentFramework, AnyAgent
from any_agent.config import MCPStdio
from any_agent.tools import (
    search_web,
)


def test_load_openai_default() -> None:
    mock_agent = MagicMock()
    mock_function_tool = MagicMock()
    mock_model = MagicMock()

    with (
        patch("any_agent.frameworks.openai.Agent", mock_agent),
        patch("agents.function_tool", mock_function_tool),
        patch("any_agent.frameworks.openai.DEFAULT_MODEL_TYPE", mock_model),
    ):
        AnyAgent.create(
            AgentFramework.OPENAI, AgentConfig(model_id="mistral:mistral-small-latest")
        )

        mock_model.assert_called_once_with(
            model="mistral:mistral-small-latest",
            base_url=None,
            api_key=None,
        )
        mock_agent.assert_called_once_with(
            name="any_agent",
            model=mock_model.return_value,
            instructions=None,
            tools=[],
            mcp_servers=[],
        )


def test_openai_with_api_base() -> None:
    mock_agent = MagicMock()
    mock_model = MagicMock()
    with (
        patch("any_agent.frameworks.openai.Agent", mock_agent),
        patch(
            "any_agent.frameworks.openai.DEFAULT_MODEL_TYPE",
            mock_model,
        ),
    ):
        AnyAgent.create(
            AgentFramework.OPENAI,
            AgentConfig(
                model_id="mistral:mistral-small-latest", model_args={}, api_base="FOO"
            ),
        )
        mock_model.assert_called_once_with(
            model="mistral:mistral-small-latest",
            base_url="FOO",
            api_key=None,
        )


def test_openai_with_api_key() -> None:
    mock_agent = MagicMock()
    mock_model = MagicMock()
    with (
        patch("any_agent.frameworks.openai.Agent", mock_agent),
        patch(
            "any_agent.frameworks.openai.DEFAULT_MODEL_TYPE",
            mock_model,
        ),
    ):
        AnyAgent.create(
            AgentFramework.OPENAI,
            AgentConfig(
                model_id="mistral:mistral-small-latest", model_args={}, api_key="FOO"
            ),
        )
        mock_model.assert_called_once_with(
            model="mistral:mistral-small-latest",
            base_url=None,
            api_key="FOO",
        )


def test_openai_forwards_any_llm_args() -> None:
    mock_agent = MagicMock()
    mock_model = MagicMock()
    any_llm_args = {"timeout": 42, "headers": {"x-test": "1"}}

    with (
        patch("any_agent.frameworks.openai.Agent", mock_agent),
        patch("any_agent.frameworks.openai.DEFAULT_MODEL_TYPE", mock_model),
    ):
        AnyAgent.create(
            AgentFramework.OPENAI,
            AgentConfig(
                model_id="mistral:mistral-small-latest",
                any_llm_args=any_llm_args,
            ),
        )

        mock_model.assert_called_once_with(
            model="mistral:mistral-small-latest",
            base_url=None,
            api_key=None,
            any_llm_args=any_llm_args,
        )


def test_load_openai_with_mcp_server() -> None:
    mock_agent = MagicMock()
    mock_function_tool = MagicMock()
    mock_model = MagicMock()
    mock_wrap_tools = MagicMock()

    with (
        patch("any_agent.frameworks.openai.Agent", mock_agent),
        patch("agents.function_tool", mock_function_tool),
        patch("any_agent.frameworks.openai.DEFAULT_MODEL_TYPE", mock_model),
        patch.object(AnyAgent, "_load_tools", mock_wrap_tools),
    ):

        async def side_effect(tools):  # type: ignore[no-untyped-def]
            # _load_tools returns just the tools list (MCP clients are stored internally)
            return [
                mock_function_tool(search_web),
                mock_function_tool("mcp_fetch_tool"),
            ]

        mock_wrap_tools.side_effect = side_effect

        AnyAgent.create(
            AgentFramework.OPENAI,
            AgentConfig(
                model_id="mistral:mistral-small-latest",
                tools=[
                    MCPStdio(
                        command="docker",
                        args=["run", "-i", "--rm", "mcp/fetch"],
                        tools=["fetch"],
                    ),
                ],
            ),
        )

        # Verify Agent was called with tools (including converted MCP tools)
        # No separate MCP servers with new architecture
        mock_agent.assert_called_once_with(
            name="any_agent",
            model=mock_model.return_value,
            instructions=None,
            tools=[
                mock_function_tool(search_web),
                mock_function_tool("mcp_fetch_tool"),
            ],
            mcp_servers=[],
        )


def test_load_openai_agent_missing() -> None:
    with patch("any_agent.frameworks.openai.agents_available", False):
        with pytest.raises(ImportError):
            AnyAgent.create(
                AgentFramework.OPENAI,
                AgentConfig(model_id="mistral:mistral-small-latest"),
            )


def test_run_openai_with_custom_args() -> None:
    mock_agent = MagicMock()
    mock_runner = AsyncMock()

    with (
        patch("any_agent.frameworks.openai.Runner", mock_runner),
        patch("any_agent.frameworks.openai.Agent", mock_agent),
        patch("agents.function_tool"),
        patch("any_agent.frameworks.openai.DEFAULT_MODEL_TYPE"),
    ):
        agent = AnyAgent.create(
            AgentFramework.OPENAI, AgentConfig(model_id="mistral:mistral-small-latest")
        )
        agent.run("foo", max_turns=30)
        mock_runner.run.assert_called_once_with(
            mock_agent.return_value, "foo", max_turns=30
        )


def test_run_openai_with_inf_max_turns() -> None:
    mock_agent = MagicMock()
    mock_runner = AsyncMock()

    with (
        patch("any_agent.frameworks.openai.Runner", mock_runner),
        patch("any_agent.frameworks.openai.Agent", mock_agent),
        patch("agents.function_tool"),
        patch("any_agent.frameworks.openai.DEFAULT_MODEL_TYPE"),
    ):
        agent = AnyAgent.create(
            AgentFramework.OPENAI, AgentConfig(model_id="mistral:mistral-small-latest")
        )
        agent.run("foo")
        mock_runner.run.assert_called_once_with(
            mock_agent.return_value, "foo", max_turns=math.inf
        )
