"""Anthropic LLM provider for Local Deep Research."""

from langchain_anthropic import ChatAnthropic
from loguru import logger

# get_setting_from_snapshot and NoSettingsContextError are imported inside
# the methods that use them so test patches at the source module
# (`local_deep_research.config.thread_settings`) are picked up by the
# function-local imports at call time.
from ..base import OPTIONAL_API_KEY_PLACEHOLDER
from ..openai_base import OpenAICompatibleProvider
from ....security.ssrf_validator import assert_base_url_safe


class AnthropicProvider(OpenAICompatibleProvider):
    """Anthropic provider for Local Deep Research.

    This is the official Anthropic API provider.
    """

    provider_name = "Anthropic"
    api_key_setting = "llm.anthropic.api_key"
    default_model = ""  # User must explicitly pick a model — no silent fallback
    default_base_url = "https://api.anthropic.com/v1"

    # Metadata for auto-discovery
    provider_key = "ANTHROPIC"
    company_name = "Anthropic"
    # Annotated so subclasses (e.g. the custom-endpoint provider, which may be
    # local or cloud) can set is_cloud = None without a type conflict.
    is_cloud: bool | None = True

    @classmethod
    def create_llm(cls, model_name=None, temperature=0.7, **kwargs):
        """Factory function for Anthropic LLMs.

        Args:
            model_name: Name of the model to use
            temperature: Model temperature (0.0-1.0)
            **kwargs: Additional arguments including settings_snapshot

        Returns:
            A configured ChatAnthropic instance

        Raises:
            ValueError: If API key is not configured
        """
        from ....config.thread_settings import NoSettingsContextError

        settings_snapshot = kwargs.get("settings_snapshot")

        # resolve_api_key raises ValueError when the required key is missing
        # (preserves the legacy behavior with a unified error message). When
        # api_key_optional is True (custom self-hosted endpoints), fall back
        # to the shared placeholder instead of raising — and pass it
        # explicitly so langchain_anthropic does not silently read a real
        # ANTHROPIC_API_KEY from the environment and ship it to the endpoint.
        api_key: str | None
        if cls.api_key_optional:
            api_key = cls.resolve_api_key_or_placeholder(settings_snapshot)
        else:
            api_key = cls.resolve_api_key(settings_snapshot)

        # Require an explicit model — no silent fallback to a hardcoded default.
        if not model_name or not model_name.strip():
            logger.error(f"{cls.provider_name} model name not provided")
            raise ValueError(
                f"{cls.provider_name} model not configured. "
                f"Please set llm.model in settings "
                f"(e.g. 'claude-3-5-sonnet-20241022')."
            )

        # Build Anthropic-specific parameters
        anthropic_params = {
            "model": model_name,
            "anthropic_api_key": api_key,
            "temperature": temperature,
        }

        # Apply context-window-aware max_tokens cap (was previously only
        # applied in dead code in llm_config.get_llm).
        from .._helpers import (
            compute_max_tokens,
            get_context_window_for_provider,
        )

        try:
            context_window_size = get_context_window_for_provider(
                "anthropic", settings_snapshot=settings_snapshot
            )
            max_tokens = compute_max_tokens(
                settings_snapshot=settings_snapshot,
                context_window_size=context_window_size,
            )
            if max_tokens:  # Treat 0 as unset (matches legacy behavior)
                anthropic_params["max_tokens"] = max_tokens
        except NoSettingsContextError:
            pass  # Optional parameter

        # Operator-configurable base_url for self-hosted Anthropic-format
        # endpoints. No-op for the official cloud provider, whose url_setting
        # is None (so it always talks to api.anthropic.com via the SDK
        # default). Subclasses like CustomAnthropicEndpointProvider set
        # url_setting to opt in.
        if cls.url_setting:
            from ....config.thread_settings import get_setting_from_snapshot

            custom_url = get_setting_from_snapshot(
                cls.url_setting,
                default=None,
                settings_snapshot=settings_snapshot,
            )
            custom_url = str(custom_url).strip() if custom_url else ""
            if not custom_url:
                # No silent fallback to the cloud endpoint — a custom-endpoint
                # provider with no URL is a misconfiguration, not a default.
                raise ValueError(
                    f"{cls.provider_name} requires a base URL. "
                    f"Please set {cls.url_setting} in settings."
                )
            # SSRF guard before constructing the client. Cloud-metadata IPs
            # stay blocked even under the permissive localhost/private-IP
            # posture that legitimate self-hosted endpoints rely on.
            anthropic_params["base_url"] = assert_base_url_safe(
                custom_url, setting_key=cls.url_setting
            )

        logger.info(
            f"Creating {cls.provider_name} LLM with model: {model_name}, "
            f"temperature: {temperature}"
        )

        return ChatAnthropic(**anthropic_params)

    @classmethod
    def list_models_for_api(cls, api_key=None, base_url=None):
        """List models via the anthropic SDK.

        Overrides the OpenAICompatibleProvider implementation, which uses the
        OpenAI SDK and cannot talk to an Anthropic endpoint — it sends
        ``Authorization: Bearer`` while Anthropic requires ``x-api-key``, so
        the inherited path returns []. Works for both the official cloud
        provider (``base_url`` None → the SDK's api.anthropic.com default) and
        the custom-endpoint subclass (``base_url`` from
        ``llm.anthropic_endpoint.url``, SSRF-guarded). Degrades to [] on any
        failure so model-listing never 500s.
        """
        try:
            # A URL-based (custom-endpoint) provider with no URL configured has
            # no endpoint to query — return [] rather than falling back to the
            # cloud default. ``url_setting`` is None for the cloud provider,
            # where the cloud default IS the intended target.
            if cls.url_setting and not base_url:
                return []

            # SSRF-guard an operator-configured base_url (custom-endpoint
            # subclass). The cloud provider has url_setting=None and base_url
            # None, so it skips this and uses the SDK's cloud default.
            if base_url and cls.url_setting:
                try:
                    base_url = assert_base_url_safe(
                        base_url, setting_key=cls.url_setting
                    )
                except ValueError:
                    logger.warning(
                        f"{cls.provider_name} base_url failed SSRF "
                        f"validation; check {cls.url_setting} config"
                    )
                    return []

            from anthropic import Anthropic

            # Pass the key explicitly (placeholder when keyless) so the SDK does
            # not read ANTHROPIC_API_KEY from the environment and ship a real
            # cloud key to a self-hosted endpoint.
            client = Anthropic(
                api_key=api_key or OPTIONAL_API_KEY_PLACEHOLDER,
                base_url=base_url or None,
            )

            logger.debug(f"Fetching models from {cls.provider_name}")
            models_response = client.models.list()

            models = []
            for model in models_response.data:
                model_id = getattr(model, "id", None)
                if model_id:
                    label = getattr(model, "display_name", None) or model_id
                    models.append({"value": model_id, "label": label})

            logger.info(f"Found {len(models)} models from {cls.provider_name}")
            return models

        except Exception:
            # Connection failures are expected when the endpoint isn't running.
            logger.warning(f"Could not list models from {cls.provider_name}")
            return []
