"""Tests for noisegate.llm — error handling, model resolution, structured calls."""

from unittest.mock import patch

import pytest
from pydantic import BaseModel

from noisegate.exceptions import LLMError
from noisegate.llm import (
    FALLBACK_MODEL,
    _make_llm_error,
    get_default_model,
    run_structured_llm_call,
)

# ---------------------------------------------------------------------------
# _make_llm_error
# ---------------------------------------------------------------------------

def test_api_key_error_returns_friendly_hint():
    err = _make_llm_error(Exception("missing api_key"), "anthropic:claude-sonnet-4-6")
    assert "ANTHROPIC_API_KEY" in str(err)
    assert "noisegate llm" in str(err)


def test_api_key_error_case_insensitive():
    err = _make_llm_error(Exception("No API Key found"), "openai:gpt-4o")
    assert "OPENAI_API_KEY" in str(err)


def test_api_key_error_environment_variable_keyword():
    err = _make_llm_error(Exception("environment variable not set"), "google-gla:gemini")
    assert "GOOGLE_API_KEY" in str(err)


def test_api_key_error_apikey_keyword():
    err = _make_llm_error(Exception("apikey required"), "anthropic:claude-sonnet-4-6")
    assert "ANTHROPIC_API_KEY" in str(err)


def test_ollama_no_env_var_hint():
    err = _make_llm_error(Exception("api_key issue"), "ollama:llama3")
    # Ollama has no env var, so it falls through to a generic message
    assert "ollama" in str(err)


def test_unknown_provider_falls_back_to_uppercase():
    err = _make_llm_error(Exception("api_key issue"), "mistral:model")
    assert "MISTRAL_API_KEY" in str(err)


def test_non_auth_error_passes_through():
    err = _make_llm_error(Exception("connection timeout"), "anthropic:claude-sonnet-4-6")
    assert str(err) == "connection timeout"


def test_non_auth_error_returns_llm_error_type():
    err = _make_llm_error(Exception("something broke"), "anthropic:claude-sonnet-4-6")
    assert isinstance(err, LLMError)


# ---------------------------------------------------------------------------
# get_default_model
# ---------------------------------------------------------------------------

def test_get_default_model_returns_configured():
    with patch("noisegate.llm.get_configured_model", return_value="openai:gpt-4o"):
        assert get_default_model() == "openai:gpt-4o"


def test_get_default_model_falls_back_when_none():
    with patch("noisegate.llm.get_configured_model", return_value=None):
        assert get_default_model() == FALLBACK_MODEL


# ---------------------------------------------------------------------------
# run_structured_llm_call
# ---------------------------------------------------------------------------

def test_run_structured_llm_call_wraps_exception_as_llm_error():
    with patch("noisegate.llm.Agent", side_effect=Exception("boom")):
        with pytest.raises(LLMError, match="boom"):
            run_structured_llm_call(
                model="anthropic:claude-sonnet-4-6",
                output_type=BaseModel,
                system_prompt="test",
                user_message="test",
            )
