"""Tests for core routing logic."""

from unittest.mock import patch

import pytest

from llm_router.router import route_and_call
from llm_router.types import LLMResponse, RoutingProfile, TaskType


@pytest.mark.asyncio
async def test_routes_to_first_available_model(mock_env, mock_acompletion):
    resp = await route_and_call(TaskType.QUERY, "Hello", profile=RoutingProfile.BUDGET)
    assert isinstance(resp, LLMResponse)
    assert resp.content == "Mock response"
    # Should have called acompletion with the first model in budget/query chain
    call_kwargs = mock_acompletion.call_args
    assert "gemini/gemini-2.5-flash" in call_kwargs.kwargs["model"]


@pytest.mark.asyncio
async def test_model_override_bypasses_routing(mock_env, mock_acompletion):
    await route_and_call(
        TaskType.QUERY, "Hello",
        model_override="openai/gpt-4o",
    )
    call_kwargs = mock_acompletion.call_args
    assert call_kwargs.kwargs["model"] == "openai/gpt-4o"


@pytest.mark.asyncio
async def test_system_prompt_included(mock_env, mock_acompletion):
    await route_and_call(
        TaskType.GENERATE, "Write a poem",
        system_prompt="You are a poet",
    )
    call_kwargs = mock_acompletion.call_args
    messages = call_kwargs.kwargs["messages"]
    assert messages[0]["role"] == "system"
    assert messages[0]["content"] == "You are a poet"


@pytest.mark.asyncio
async def test_falls_back_on_failure(mock_env, mock_litellm_response):
    call_count = 0

    async def side_effect(**kwargs):
        nonlocal call_count
        call_count += 1
        if call_count == 1:
            raise Exception("Provider down")
        return mock_litellm_response()

    with patch("litellm.acompletion", side_effect=side_effect):
        with patch("litellm.completion_cost", return_value=0.001):
            resp = await route_and_call(
                TaskType.QUERY, "Hello",
                profile=RoutingProfile.BUDGET,
            )
    assert resp.content == "Mock response"
    assert call_count == 2  # first failed, second succeeded


@pytest.mark.asyncio
async def test_raises_when_all_fail(mock_env):
    with patch("litellm.acompletion", side_effect=Exception("All down")):
        with pytest.raises(RuntimeError, match="All models failed"):
            await route_and_call(TaskType.QUERY, "Hello")


@pytest.mark.asyncio
async def test_no_providers_configured(monkeypatch):
    # Explicitly clear all API keys — use setenv("", "") pattern to also
    # override values that may be present in the shell environment.
    for key in ["GEMINI_API_KEY", "OPENAI_API_KEY", "PERPLEXITY_API_KEY",
                "ANTHROPIC_API_KEY", "DEEPSEEK_API_KEY", "GROQ_API_KEY",
                "MISTRAL_API_KEY", "TOGETHER_API_KEY", "XAI_API_KEY",
                "COHERE_API_KEY", "OLLAMA_BASE_URL"]:
        monkeypatch.setenv(key, "")
    monkeypatch.chdir("/tmp")  # no .env file here
    monkeypatch.setattr("llm_router.router.is_codex_available", lambda: False)
    with pytest.raises(ValueError, match="No available models"):
        await route_and_call(TaskType.QUERY, "Hello")


@pytest.mark.asyncio
async def test_research_no_search_params_for_non_perplexity(mock_env, mock_acompletion):
    # Non-Perplexity models explicitly overridden must NOT receive search_recency_filter.
    await route_and_call(TaskType.RESEARCH, "What happened today?", model_override="openai/gpt-4o")
    call_kwargs = mock_acompletion.call_args.kwargs
    extra_body = call_kwargs.get("extra_body", {})
    assert "search_recency_filter" not in extra_body


@pytest.mark.asyncio
async def test_research_adds_search_params_for_perplexity(mock_env, mock_acompletion, monkeypatch):
    # Perplexity sonar models should receive the recency filter.
    monkeypatch.setenv("LLM_ROUTER_PROFILE", "balanced")
    await route_and_call(TaskType.RESEARCH, "What happened today?", model_override="perplexity/sonar")
    call_kwargs = mock_acompletion.call_args.kwargs
    assert call_kwargs.get("extra_body", {}).get("search_recency_filter") == "week"
