from __future__ import annotations

import asyncio
from typing import Any, cast

import pytest

from agents import (
    Agent,
    AgentHookContext,
    FunctionTool,
    HandoffInputData,
    ItemHelpers,
    ModelRetrySettings,
    ModelSettings,
    MultiProvider,
    RunConfig,
    RunContextWrapper,
    RunResult,
    RunResultStreaming,
    SessionSettings,
    ToolExecutionConfig,
    ToolGuardrailFunctionOutput,
    ToolInputGuardrailData,
    ToolOutputGuardrailData,
    Usage,
    tool_input_guardrail,
    tool_output_guardrail,
)
from agents.tool_context import ToolContext


def test_run_config_positional_arguments_remain_backward_compatible() -> None:
    async def keep_handoff_input(data: HandoffInputData) -> HandoffInputData:
        return data

    config = RunConfig(None, MultiProvider(), None, keep_handoff_input)

    assert config.handoff_input_filter is keep_handoff_input
    assert config.session_settings is None


def test_run_config_session_settings_positional_binding_is_preserved() -> None:
    session_settings = SessionSettings(limit=123)
    config = RunConfig(
        None,
        MultiProvider(),
        None,
        None,
        False,
        None,
        None,
        None,
        False,
        None,
        True,
        "Agent workflow",
        None,
        None,
        None,
        None,
        None,
        None,
        session_settings,
    )

    assert config.session_settings == session_settings
    assert config.reasoning_item_id_policy is None


def test_run_config_reasoning_item_id_policy_positional_binding() -> None:
    session_settings = SessionSettings(limit=123)
    config = RunConfig(
        None,
        MultiProvider(),
        None,
        None,
        False,
        None,
        None,
        None,
        False,
        None,
        True,
        "Agent workflow",
        None,
        None,
        None,
        None,
        None,
        None,
        session_settings,
        "omit",
    )

    assert config.session_settings == session_settings
    assert config.reasoning_item_id_policy == "omit"
    assert config.sandbox is None
    assert config.tool_execution is None


def test_run_config_tool_execution_append_preserves_sandbox_position() -> None:
    session_settings = SessionSettings(limit=123)
    tool_execution = ToolExecutionConfig(max_function_tool_concurrency=2)
    config = RunConfig(
        None,
        MultiProvider(),
        None,
        None,
        False,
        None,
        None,
        None,
        False,
        None,
        True,
        "Agent workflow",
        None,
        None,
        None,
        None,
        None,
        None,
        session_settings,
        "omit",
        None,
        tool_execution,
    )

    assert config.session_settings == session_settings
    assert config.reasoning_item_id_policy == "omit"
    assert config.sandbox is None
    assert config.tool_execution is tool_execution


def test_run_config_tool_not_found_behavior_append_preserves_tool_execution_position() -> None:
    session_settings = SessionSettings(limit=123)
    tool_execution = ToolExecutionConfig(max_function_tool_concurrency=2)
    config = RunConfig(
        None,
        MultiProvider(),
        None,
        None,
        False,
        None,
        None,
        None,
        False,
        None,
        True,
        "Agent workflow",
        None,
        None,
        None,
        None,
        None,
        None,
        session_settings,
        "omit",
        None,
        tool_execution,
        "return_error_to_model",
    )

    assert config.session_settings == session_settings
    assert config.reasoning_item_id_policy == "omit"
    assert config.sandbox is None
    assert config.tool_execution is tool_execution
    assert config.tool_not_found_behavior == "return_error_to_model"


def test_tool_execution_config_pre_approval_append_preserves_max_concurrency() -> None:
    config = ToolExecutionConfig(2, True)

    assert config.max_function_tool_concurrency == 2
    assert config.pre_approval_tool_input_guardrails is True


def test_tool_execution_config_rejects_non_bool_pre_approval_guardrails() -> None:
    with pytest.raises(
        ValueError,
        match="tool_execution.pre_approval_tool_input_guardrails must be a bool",
    ):
        ToolExecutionConfig(pre_approval_tool_input_guardrails=cast(Any, "true"))


def test_model_settings_context_management_append_preserves_retry_position() -> None:
    retry = ModelRetrySettings(max_retries=1)
    settings = ModelSettings(
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        retry,
    )

    assert settings.retry is retry
    assert settings.context_management is None


def test_function_tool_positional_arguments_keep_guardrail_positions() -> None:
    async def invoke(_ctx: ToolContext[Any], _args: str) -> str:
        return "ok"

    @tool_input_guardrail
    def allow_input(_data: ToolInputGuardrailData) -> ToolGuardrailFunctionOutput:
        return ToolGuardrailFunctionOutput.allow()

    @tool_output_guardrail
    def allow_output(_data: ToolOutputGuardrailData) -> ToolGuardrailFunctionOutput:
        return ToolGuardrailFunctionOutput.allow()

    input_guardrails = [allow_input]
    output_guardrails = [allow_output]

    tool = FunctionTool(
        "tool_name",
        "tool_description",
        {"type": "object", "properties": {}},
        invoke,
        True,
        True,
        input_guardrails,
        output_guardrails,
    )

    assert tool.needs_approval is False
    assert tool.tool_input_guardrails is not None
    assert tool.tool_output_guardrails is not None
    assert tool.tool_input_guardrails[0] is allow_input
    assert tool.tool_output_guardrails[0] is allow_output
    assert tool.timeout_seconds is None
    assert tool.timeout_behavior == "error_as_result"
    assert tool.timeout_error_function is None


def test_agent_hook_context_third_positional_argument_is_turn_input() -> None:
    turn_input = ItemHelpers.input_to_new_input_list("hello")
    context = AgentHookContext(None, Usage(), turn_input)

    assert context.turn_input == turn_input
    assert isinstance(context._approvals, dict)


def test_tool_context_v070_positional_constructor_still_works() -> None:
    usage = Usage()
    context = ToolContext(None, usage, "tool_name", "call_id", '{"x":1}', None)

    assert context.usage is usage
    assert context.tool_name == "tool_name"
    assert context.tool_call_id == "call_id"
    assert context.tool_arguments == '{"x":1}'
    assert context.agent is None


def test_tool_context_supports_agent_keyword_argument() -> None:
    usage = Usage()
    agent = Agent(name="agent")
    context = ToolContext(None, usage, "tool_name", "call_id", '{"x":1}', None, agent=agent)

    assert context.usage is usage
    assert context.tool_name == "tool_name"
    assert context.tool_call_id == "call_id"
    assert context.tool_arguments == '{"x":1}'
    assert context.agent is agent


def test_run_result_v070_positional_constructor_still_works() -> None:
    result = RunResult(
        "x",
        [],
        [],
        "ok",
        [],
        [],
        [],
        [],
        RunContextWrapper(context=None),
        Agent(name="agent"),
    )
    assert result.final_output == "ok"
    assert result.interruptions == []


def test_run_result_streaming_v070_positional_constructor_still_works() -> None:
    result = RunResultStreaming(
        "x",
        [],
        [],
        "ok",
        [],
        [],
        [],
        [],
        RunContextWrapper(context=None),
        Agent(name="agent"),
        0,
        1,
        None,
        None,
    )
    assert result.final_output == "ok"
    assert result.interruptions == []


def test_run_result_streaming_v070_optional_positional_constructor_still_works() -> None:
    event_queue: asyncio.Queue[Any] = asyncio.Queue()
    input_guardrail_queue: asyncio.Queue[Any] = asyncio.Queue()
    result = RunResultStreaming(
        "x",
        [],
        [],
        "ok",
        [],
        [],
        [],
        [],
        RunContextWrapper(context=None),
        Agent(name="agent"),
        0,
        1,
        None,
        None,
        True,
        [],
        event_queue,
        input_guardrail_queue,
        None,
    )
    assert result.is_complete is True
    assert result.run_loop_task is None
    assert result._event_queue is event_queue
    assert result._input_guardrail_queue is input_guardrail_queue
    assert result.interruptions == []


def test_run_result_streaming_accepts_legacy_run_impl_task_keyword() -> None:
    sentinel_task = cast(Any, object())
    result = RunResultStreaming(
        input="x",
        new_items=[],
        raw_responses=[],
        final_output="ok",
        input_guardrail_results=[],
        output_guardrail_results=[],
        tool_input_guardrail_results=[],
        tool_output_guardrail_results=[],
        context_wrapper=RunContextWrapper(context=None),
        current_agent=Agent(name="agent"),
        current_turn=0,
        max_turns=1,
        _current_agent_output_schema=None,
        trace=None,
        _run_impl_task=sentinel_task,
    )
    assert result.run_loop_task is sentinel_task


def test_run_result_streaming_accepts_run_loop_task_keyword() -> None:
    sentinel_task = cast(Any, object())
    result = RunResultStreaming(
        input="x",
        new_items=[],
        raw_responses=[],
        final_output="ok",
        input_guardrail_results=[],
        output_guardrail_results=[],
        tool_input_guardrail_results=[],
        tool_output_guardrail_results=[],
        context_wrapper=RunContextWrapper(context=None),
        current_agent=Agent(name="agent"),
        current_turn=0,
        max_turns=1,
        _current_agent_output_schema=None,
        trace=None,
        run_loop_task=sentinel_task,
    )
    assert result.run_loop_task is sentinel_task


def test_run_result_streaming_v070_run_impl_task_positional_binding_is_preserved() -> None:
    sentinel_task = cast(Any, object())
    event_queue: asyncio.Queue[Any] = asyncio.Queue()
    input_guardrail_queue: asyncio.Queue[Any] = asyncio.Queue()
    result = RunResultStreaming(
        "x",
        [],
        [],
        "ok",
        [],
        [],
        [],
        [],
        RunContextWrapper(context=None),
        Agent(name="agent"),
        0,
        1,
        None,
        None,
        False,
        [],
        event_queue,
        input_guardrail_queue,
        sentinel_task,
    )
    assert result._event_queue is event_queue
    assert result._input_guardrail_queue is input_guardrail_queue
    assert result.run_loop_task is sentinel_task
