"""Tests for Anthropic LLM provider."""

import pytest
from unittest.mock import Mock, patch

from local_deep_research.llm.providers.implementations.anthropic import (
    AnthropicProvider,
)


class TestAnthropicProviderMetadata:
    """Tests for AnthropicProvider class metadata."""

    def test_provider_name(self):
        """Provider name is correct."""
        assert AnthropicProvider.provider_name == "Anthropic"

    def test_provider_key(self):
        """Provider key is correct."""
        assert AnthropicProvider.provider_key == "ANTHROPIC"

    def test_is_cloud(self):
        """Anthropic is a cloud provider."""
        assert AnthropicProvider.is_cloud is True

    def test_company_name(self):
        """Company name is Anthropic."""
        assert AnthropicProvider.company_name == "Anthropic"

    def test_api_key_setting(self):
        """API key setting is correct."""
        assert AnthropicProvider.api_key_setting == "llm.anthropic.api_key"

    def test_default_model(self):
        """Default model is empty by design — users must explicitly pick one."""
        assert AnthropicProvider.default_model == ""

    def test_default_base_url(self):
        """Default base URL is correct."""
        assert "anthropic.com" in AnthropicProvider.default_base_url


class TestAnthropicCreateLLM:
    """Tests for create_llm method."""

    def test_create_llm_raises_without_api_key(self):
        """Raises ValueError when API key not configured."""
        with patch(
            "local_deep_research.config.thread_settings.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.return_value = None

            with pytest.raises(ValueError) as exc_info:
                AnthropicProvider.create_llm()

            assert "api key" in str(exc_info.value).lower()

    def test_create_llm_with_valid_api_key(self):
        """Successfully creates ChatAnthropic instance with valid API key."""

        def mock_get_setting_side_effect(key, default=None, *args, **kwargs):
            settings_map = {
                "llm.anthropic.api_key": "test-anthropic-key",
                "llm.max_tokens": None,
            }
            return settings_map.get(key, default)

        with patch(
            "local_deep_research.config.thread_settings.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.side_effect = mock_get_setting_side_effect

            with patch(
                "local_deep_research.llm.providers.implementations.anthropic.ChatAnthropic"
            ) as mock_chat:
                mock_llm = Mock()
                mock_chat.return_value = mock_llm

                result = AnthropicProvider.create_llm(model_name="test-model")

                assert result is mock_llm
                mock_chat.assert_called_once()

    def test_create_llm_uses_default_model_when_none(self):
        """Raises ValueError when no model name is provided (no silent default)."""

        def mock_get_setting_side_effect(key, default=None, *args, **kwargs):
            settings_map = {
                "llm.anthropic.api_key": "test-key",
                "llm.max_tokens": None,
            }
            return settings_map.get(key, default)

        with patch(
            "local_deep_research.config.thread_settings.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.side_effect = mock_get_setting_side_effect

            with pytest.raises(ValueError, match="model not configured"):
                AnthropicProvider.create_llm()

    def test_create_llm_with_custom_model(self):
        """Uses custom model when specified."""

        def mock_get_setting_side_effect(key, default=None, *args, **kwargs):
            settings_map = {
                "llm.anthropic.api_key": "test-key",
                "llm.max_tokens": None,
            }
            return settings_map.get(key, default)

        with patch(
            "local_deep_research.config.thread_settings.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.side_effect = mock_get_setting_side_effect

            with patch(
                "local_deep_research.llm.providers.implementations.anthropic.ChatAnthropic"
            ) as mock_chat:
                AnthropicProvider.create_llm(
                    model_name="claude-3-opus-20240229"
                )

                call_kwargs = mock_chat.call_args[1]
                assert call_kwargs["model"] == "claude-3-opus-20240229"

    def test_create_llm_passes_temperature(self):
        """Passes temperature parameter."""

        def mock_get_setting_side_effect(key, default=None, *args, **kwargs):
            settings_map = {
                "llm.anthropic.api_key": "test-key",
                "llm.max_tokens": None,
            }
            return settings_map.get(key, default)

        with patch(
            "local_deep_research.config.thread_settings.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.side_effect = mock_get_setting_side_effect

            with patch(
                "local_deep_research.llm.providers.implementations.anthropic.ChatAnthropic"
            ) as mock_chat:
                AnthropicProvider.create_llm(
                    model_name="test-model", temperature=0.5
                )

                call_kwargs = mock_chat.call_args[1]
                assert call_kwargs["temperature"] == 0.5

    def test_create_llm_passes_max_tokens_when_set(self):
        """Passes max_tokens when configured in settings."""

        def mock_get_setting_side_effect(key, default=None, *args, **kwargs):
            settings_map = {
                "llm.anthropic.api_key": "test-key",
                "llm.max_tokens": 4096,
            }
            return settings_map.get(key, default)

        with patch(
            "local_deep_research.config.thread_settings.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.side_effect = mock_get_setting_side_effect

            with patch(
                "local_deep_research.llm.providers.implementations.anthropic.ChatAnthropic"
            ) as mock_chat:
                AnthropicProvider.create_llm(model_name="test-model")

                call_kwargs = mock_chat.call_args[1]
                assert call_kwargs["max_tokens"] == 4096

    def test_create_llm_uses_anthropic_api_key_param(self):
        """Uses anthropic_api_key parameter name."""

        def mock_get_setting_side_effect(key, default=None, *args, **kwargs):
            settings_map = {
                "llm.anthropic.api_key": "my-anthropic-key",
                "llm.max_tokens": None,
            }
            return settings_map.get(key, default)

        with patch(
            "local_deep_research.config.thread_settings.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.side_effect = mock_get_setting_side_effect

            with patch(
                "local_deep_research.llm.providers.implementations.anthropic.ChatAnthropic"
            ) as mock_chat:
                AnthropicProvider.create_llm(model_name="test-model")

                call_kwargs = mock_chat.call_args[1]
                assert call_kwargs["anthropic_api_key"] == "my-anthropic-key"


class TestAnthropicIsAvailable:
    """Tests for is_available method."""

    def test_is_available_true_when_key_exists(self):
        """Returns True when API key is configured."""
        with patch(
            "local_deep_research.config.thread_settings.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.return_value = "test-key"

            result = AnthropicProvider.is_available()
            assert result is True

    def test_is_available_false_when_no_key(self):
        """Returns False when API key is not configured."""
        with patch(
            "local_deep_research.config.thread_settings.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.return_value = None

            result = AnthropicProvider.is_available()
            assert result is False

    def test_is_available_false_when_empty_key(self):
        """Returns False when API key is empty string."""
        with patch(
            "local_deep_research.config.thread_settings.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.return_value = ""

            result = AnthropicProvider.is_available()
            assert result is False

    def test_is_available_false_on_exception(self):
        """Returns False when exception occurs."""
        with patch(
            "local_deep_research.config.thread_settings.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.side_effect = Exception("Settings error")

            result = AnthropicProvider.is_available()
            assert result is False


class TestAnthropicListModels:
    """The cloud provider lists models via the anthropic SDK, not the OpenAI
    SDK it would otherwise inherit from OpenAICompatibleProvider (which sends
    Bearer auth and 401s against the Anthropic API). This is the fix for the
    discovered-provider loop clobbering the cloud Anthropic model list."""

    def test_uses_anthropic_sdk_and_maps_models(self):
        fake_model = Mock(
            id="claude-3-5-sonnet", display_name="Claude 3.5 Sonnet"
        )
        fake_client = Mock()
        fake_client.models.list.return_value = Mock(data=[fake_model])
        with patch("anthropic.Anthropic", return_value=fake_client) as mk:
            models = AnthropicProvider.list_models_for_api(api_key="sk-test")
        assert models == [
            {"value": "claude-3-5-sonnet", "label": "Claude 3.5 Sonnet"}
        ]
        fake_client.models.list.assert_called_once()
        # Cloud provider (url_setting is None) -> SDK uses its cloud default.
        _, kwargs = mk.call_args
        assert kwargs.get("base_url") is None

    def test_cloud_no_base_url_does_not_short_circuit(self):
        """Unlike the custom-endpoint subclass, the cloud provider has
        url_setting=None, so a missing base_url must NOT return [] — it must
        query the cloud default."""
        fake_client = Mock()
        fake_client.models.list.return_value = Mock(data=[])
        with patch("anthropic.Anthropic", return_value=fake_client) as mk:
            AnthropicProvider.list_models_for_api(
                api_key="sk-test", base_url=None
            )
        mk.assert_called_once()

    def test_sdk_error_returns_empty(self):
        fake_client = Mock()
        fake_client.models.list.side_effect = RuntimeError("401 Unauthorized")
        with patch("anthropic.Anthropic", return_value=fake_client):
            assert (
                AnthropicProvider.list_models_for_api(api_key="sk-test") == []
            )
