"""Tests for the LLM provider abstraction."""

from __future__ import annotations

from types import SimpleNamespace
from unittest.mock import AsyncMock, patch

from agent_scout.events import EventBus
from agent_scout.llm.provider import (
    AgentLLMProvider,
    CustomEndpoint,
    LLMClient,
    ProviderConfig,
)
from agent_scout.react_loop import Action, ActionType, Step


def _make_litellm_response(
    content: str = "Hello",
    tool_calls: list | None = None,
    prompt_tokens: int = 10,
    completion_tokens: int = 20,
    finish_reason: str = "stop",
):
    """Build a mock LiteLLM response object."""
    message = SimpleNamespace(content=content, tool_calls=tool_calls)
    choice = SimpleNamespace(message=message, finish_reason=finish_reason)
    usage = SimpleNamespace(prompt_tokens=prompt_tokens, completion_tokens=completion_tokens)
    return SimpleNamespace(choices=[choice], usage=usage)


def _make_tool_call(name: str = "web_search", arguments: str = '{"query": "test"}'):
    func = SimpleNamespace(name=name, arguments=arguments)
    return SimpleNamespace(id="call_123", function=func)


# --- LLMClient tests ---


async def test_complete_basic():
    mock_resp = _make_litellm_response(content="The answer is 42")

    with patch("agent_scout.llm.provider.litellm") as mock_litellm:
        mock_litellm.acompletion = AsyncMock(return_value=mock_resp)

        client = LLMClient()
        result = await client.complete(messages=[{"role": "user", "content": "What is 6*7?"}])

    assert result.content == "The answer is 42"
    assert result.input_tokens == 10
    assert result.output_tokens == 20
    assert result.finish_reason == "stop"


async def test_complete_with_tools():
    tc = _make_tool_call()
    mock_resp = _make_litellm_response(content="", tool_calls=[tc])

    with patch("agent_scout.llm.provider.litellm") as mock_litellm:
        mock_litellm.acompletion = AsyncMock(return_value=mock_resp)

        client = LLMClient()
        result = await client.complete(
            messages=[{"role": "user", "content": "Search for flights"}],
            tools=[{"type": "function", "function": {"name": "web_search"}}],
        )

    assert len(result.tool_calls) == 1
    assert result.tool_calls[0]["name"] == "web_search"
    assert result.tool_calls[0]["arguments"] == {"query": "test"}


async def test_complete_with_custom_model():
    mock_resp = _make_litellm_response(content="Custom response")

    with patch("agent_scout.llm.provider.litellm") as mock_litellm:
        mock_litellm.acompletion = AsyncMock(return_value=mock_resp)

        client = LLMClient(config=ProviderConfig(default_model="openai/gpt-4o"))
        result = await client.complete(messages=[{"role": "user", "content": "Hi"}])

    assert result.content == "Custom response"
    call_kwargs = mock_litellm.acompletion.call_args.kwargs
    assert call_kwargs["model"] == "openai/gpt-4o"


async def test_complete_with_custom_endpoint():
    mock_resp = _make_litellm_response(content="Custom endpoint response")

    with patch("agent_scout.llm.provider.litellm") as mock_litellm:
        mock_litellm.acompletion = AsyncMock(return_value=mock_resp)

        endpoint = CustomEndpoint(
            name="myapi",
            base_url="https://api.myservice.com/v1",
            api_key="sk-test-123",
            model_name="my-model-v2",
        )
        client = LLMClient(config=ProviderConfig(custom_endpoints=[endpoint]))
        result = await client.complete(
            messages=[{"role": "user", "content": "Hi"}],
            model="custom/myapi",
        )

    assert result.content == "Custom endpoint response"
    call_kwargs = mock_litellm.acompletion.call_args.kwargs
    assert call_kwargs["model"] == "my-model-v2"
    assert call_kwargs["api_base"] == "https://api.myservice.com/v1"
    assert call_kwargs["api_key"] == "sk-test-123"


async def test_complete_emits_cost_event():
    mock_resp = _make_litellm_response(prompt_tokens=100, completion_tokens=50)
    bus = EventBus()
    received = []

    async def handler(event):
        received.append(event)

    bus.subscribe(handler)

    with patch("agent_scout.llm.provider.litellm") as mock_litellm:
        mock_litellm.acompletion = AsyncMock(return_value=mock_resp)

        client = LLMClient(event_bus=bus)
        await client.complete(messages=[{"role": "user", "content": "Hi"}])

    cost_events = [e for e in received if e.data.get("input_tokens")]
    assert len(cost_events) == 1
    assert cost_events[0].data["input_tokens"] == 100
    assert cost_events[0].data["output_tokens"] == 50


async def test_complete_raises_on_error():
    with patch("agent_scout.llm.provider.litellm") as mock_litellm:
        mock_litellm.acompletion = AsyncMock(side_effect=RuntimeError("API down"))

        client = LLMClient()
        try:
            await client.complete(messages=[{"role": "user", "content": "Hi"}])
            raise AssertionError("Should have raised")
        except RuntimeError as e:
            assert "API down" in str(e)


async def test_provider_config_defaults():
    config = ProviderConfig()
    assert "claude" in config.default_model.lower() or "anthropic" in config.default_model.lower()
    assert config.temperature == 0.3
    assert config.max_tokens == 4096


# --- AgentLLMProvider tests ---


async def test_agent_provider_think_with_tool_call():
    tc = _make_tool_call(name="web_search", arguments='{"query": "flights"}')
    mock_resp = _make_litellm_response(
        content="I need to search for flights.",
        tool_calls=[tc],
    )

    with patch("agent_scout.llm.provider.litellm") as mock_litellm:
        mock_litellm.acompletion = AsyncMock(return_value=mock_resp)

        client = LLMClient()
        provider = AgentLLMProvider(client)

        thought, action = await provider.think(
            task="Find flights NYC to London",
            steps=[],
            available_tools=[{"name": "web_search", "description": "Search the web"}],
        )

    assert "search" in thought.lower() or thought == "I need to search for flights."
    assert action.action_type == ActionType.TOOL_CALL
    assert action.tool_name == "web_search"


async def test_agent_provider_think_finish():
    mock_resp = _make_litellm_response(
        content="The best flight is $450 on BA.",
        finish_reason="stop",
    )

    with patch("agent_scout.llm.provider.litellm") as mock_litellm:
        mock_litellm.acompletion = AsyncMock(return_value=mock_resp)

        client = LLMClient()
        provider = AgentLLMProvider(client)

        thought, action = await provider.think(
            task="Find flights",
            steps=[],
            available_tools=[],
        )

    assert action.action_type == ActionType.FINISH
    assert "$450" in action.message


async def test_agent_provider_reflect():
    mock_resp = _make_litellm_response(content="Making good progress.")

    with patch("agent_scout.llm.provider.litellm") as mock_litellm:
        mock_litellm.acompletion = AsyncMock(return_value=mock_resp)

        client = LLMClient()
        provider = AgentLLMProvider(client)

        reflection = await provider.reflect(
            task="Find flights",
            steps=[],
            latest_observation="Found 3 results",
        )

    assert "progress" in reflection.lower()


async def test_agent_provider_builds_history():
    """Messages should include step history."""
    mock_resp = _make_litellm_response(content="Next step.", finish_reason="stop")

    with patch("agent_scout.llm.provider.litellm") as mock_litellm:
        mock_litellm.acompletion = AsyncMock(return_value=mock_resp)

        client = LLMClient()
        provider = AgentLLMProvider(client)

        steps = [
            Step(
                step_number=1,
                thought="I should search",
                action=Action(
                    action_type=ActionType.TOOL_CALL,
                    tool_name="web_search",
                ),
                observation="Found 3 results",
            ),
        ]

        await provider.think(
            task="Find flights",
            steps=steps,
            available_tools=[],
        )

    call_kwargs = mock_litellm.acompletion.call_args.kwargs
    messages = call_kwargs["messages"]
    # system + user(task) + assistant(thought) + user(observation)
    assert len(messages) >= 4
    assert "I should search" in messages[2]["content"]
    assert "Found 3 results" in messages[3]["content"]
