"""High-value tests for the custom Anthropic-compatible endpoint provider.

Covers the URL-based is_available logic, create_llm base_url passthrough +
keyless/empty-URL handling, env-key-leak prevention, model listing via the
anthropic SDK, and class metadata. Mirrors test_custom_openai_high_value.py.
"""

from unittest.mock import Mock, patch

import pytest

from local_deep_research.llm.providers.base import OPTIONAL_API_KEY_PLACEHOLDER
from local_deep_research.llm.providers.implementations.anthropic import (
    AnthropicProvider,
)
from local_deep_research.llm.providers.implementations.custom_anthropic_endpoint import (
    CustomAnthropicEndpointProvider as P,
)

ANTHRO_MOD = "local_deep_research.llm.providers.implementations.custom_anthropic_endpoint"
GET_SETTING = f"{ANTHRO_MOD}.get_setting_from_snapshot"
# create_llm (inherited from AnthropicProvider) binds ChatAnthropic in anthropic.py
CHAT = (
    "local_deep_research.llm.providers.implementations.anthropic.ChatAnthropic"
)

URL_KEY = "llm.anthropic_endpoint.url"
KEY_KEY = "llm.anthropic_endpoint.api_key"


class TestCustomAnthropicIsAvailable:
    """is_available is True when either an API key OR a URL is configured."""

    def test_available_with_api_key_only(self):
        def side_effect(key, default=None, **kwargs):
            if key == KEY_KEY:
                return "my-key"
            return default

        with patch(GET_SETTING, side_effect=side_effect):
            assert P.is_available() is True

    def test_available_with_url_only(self):
        def side_effect(key, default=None, **kwargs):
            if key == URL_KEY:
                return "http://localhost:9090"
            return default

        with patch(GET_SETTING, side_effect=side_effect):
            assert P.is_available() is True

    def test_not_available_with_neither(self):
        with patch(GET_SETTING, return_value=None):
            assert P.is_available() is False

    def test_whitespace_key_and_url_not_available(self):
        def side_effect(key, default=None, **kwargs):
            return "   "

        with patch(GET_SETTING, side_effect=side_effect):
            assert P.is_available() is False

    def test_exception_in_api_key_check_falls_through_to_url(self):
        def side_effect(key, default=None, **kwargs):
            if key == KEY_KEY:
                raise RuntimeError("settings error")
            if key == URL_KEY:
                return "http://myserver:9090"
            return default

        with patch(GET_SETTING, side_effect=side_effect):
            assert P.is_available() is True

    def test_both_exceptions_return_false(self):
        with patch(GET_SETTING, side_effect=RuntimeError("boom")):
            assert P.is_available() is False


class TestCustomAnthropicCreateLlm:
    """create_llm passes the configured URL as base_url and handles keys."""

    def test_custom_url_passed_as_base_url(self):
        snap = {URL_KEY: "http://localhost:9090", KEY_KEY: "k"}
        with patch(CHAT) as mock_ctor:
            mock_ctor.return_value = Mock()
            P.create_llm(model_name="claude-3-5-sonnet", settings_snapshot=snap)
            _, kwargs = mock_ctor.call_args
            assert kwargs["base_url"] == "http://localhost:9090"
            assert kwargs["anthropic_api_key"] == "k"

    def test_keyless_uses_placeholder(self):
        snap = {URL_KEY: "http://localhost:9090"}
        with patch(CHAT) as mock_ctor:
            mock_ctor.return_value = Mock()
            P.create_llm(model_name="claude-3-5-sonnet", settings_snapshot=snap)
            _, kwargs = mock_ctor.call_args
            assert kwargs["anthropic_api_key"] == OPTIONAL_API_KEY_PLACEHOLDER

    def test_empty_url_raises(self):
        with pytest.raises(ValueError, match="requires a base URL"):
            P.create_llm(model_name="claude-3", settings_snapshot={URL_KEY: ""})

    def test_empty_model_raises(self):
        with pytest.raises(ValueError, match="model not configured"):
            P.create_llm(
                model_name="",
                settings_snapshot={URL_KEY: "http://localhost:9090"},
            )

    def test_env_api_key_not_leaked_to_endpoint(self, monkeypatch):
        """A real ANTHROPIC_API_KEY in the env must NOT be sent to a
        self-hosted endpoint: the explicit placeholder kwarg wins."""
        monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-real-cloud-key")
        snap = {URL_KEY: "http://localhost:9090"}  # no endpoint key configured
        with patch(CHAT) as mock_ctor:
            mock_ctor.return_value = Mock()
            P.create_llm(model_name="claude-3-5-sonnet", settings_snapshot=snap)
            _, kwargs = mock_ctor.call_args
            assert kwargs["anthropic_api_key"] == OPTIONAL_API_KEY_PLACEHOLDER
            assert kwargs["anthropic_api_key"] != "sk-real-cloud-key"


class TestCustomAnthropicClassAttributes:
    def test_inherits_from_anthropic_provider(self):
        assert issubclass(P, AnthropicProvider)

    def test_provider_key(self):
        assert P.provider_key == "ANTHROPIC_ENDPOINT"

    def test_is_cloud_is_none(self):
        assert P.is_cloud is None

    def test_api_key_optional(self):
        assert P.api_key_optional is True

    def test_url_setting(self):
        assert P.url_setting == URL_KEY

    def test_requires_auth_for_models_false(self):
        assert P.requires_auth_for_models() is False


class TestCustomAnthropicListModels:
    def test_list_models_success(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 = P.list_models_for_api(
                api_key="k", base_url="http://localhost:9090"
            )
        assert models == [
            {"value": "claude-3-5-sonnet", "label": "Claude 3.5 Sonnet"}
        ]
        mk.assert_called_once()

    def test_list_models_falls_back_to_id_label(self):
        fake_model = Mock(id="custom-model", display_name=None)
        fake_client = Mock()
        fake_client.models.list.return_value = Mock(data=[fake_model])
        with patch("anthropic.Anthropic", return_value=fake_client):
            models = P.list_models_for_api(base_url="http://localhost:9090")
        assert models == [{"value": "custom-model", "label": "custom-model"}]

    def test_list_models_ssrf_blocked_returns_empty(self):
        """A metadata-IP base_url is rejected and degrades to []."""
        assert P.list_models_for_api(base_url="http://169.254.169.254/") == []

    def test_list_models_no_base_url_returns_empty_without_calling_sdk(self):
        """With no URL configured there is no endpoint — return [] and never
        fall back to the anthropic SDK's cloud default."""
        with patch("anthropic.Anthropic") as mk:
            assert P.list_models_for_api(api_key="k", base_url=None) == []
            assert P.list_models_for_api(api_key="k", base_url="") == []
        mk.assert_not_called()

    def test_list_models_sdk_error_returns_empty(self):
        fake_client = Mock()
        fake_client.models.list.side_effect = RuntimeError("connection refused")
        with patch("anthropic.Anthropic", return_value=fake_client):
            assert P.list_models_for_api(base_url="http://localhost:9090") == []
