"""LM Studio LLM provider for Local Deep Research."""

from ....config.constants import DEFAULT_LMSTUDIO_URL
from ....utilities.url_utils import normalize_url
from ..openai_base import OpenAICompatibleProvider


class LMStudioProvider(OpenAICompatibleProvider):
    """LM Studio provider using OpenAI-compatible endpoint.

    LM Studio provides a local OpenAI-compatible API for running models.
    Recent LM Studio versions can require an API key on the local server;
    the key is optional here so unauthenticated instances keep working.
    """

    provider_name = "LM Studio"
    # LM Studio HAS an API key setting; it's just optional (newer LM Studio
    # versions can require auth on the local server). The api_key_optional
    # flag tells the base resolver to fall back to a placeholder when no
    # key is configured, instead of raising.
    api_key_setting = "llm.lmstudio.api_key"
    api_key_optional = True
    url_setting = "llm.lmstudio.url"  # type: ignore[assignment]  # Settings key for URL
    default_base_url = DEFAULT_LMSTUDIO_URL
    default_model = (
        ""  # User must specify the model they loaded — no silent fallback
    )

    @classmethod
    def _ensure_v1_suffix(cls, url: str) -> str:
        """Ensure URL ends with /v1 — LM Studio always uses this path prefix."""
        normalized = normalize_url(url)
        if not normalized.rstrip("/").endswith("/v1"):
            normalized = normalized.rstrip("/") + "/v1"
        return normalized

    # Metadata for auto-discovery
    provider_key = "LMSTUDIO"
    company_name = "LM Studio"
    is_cloud = False  # Local provider

    @classmethod
    def _get_auth_headers(cls, settings_snapshot=None):
        """Build Authorization header from the optional API key setting.

        Returns an empty dict when no key is configured so unauthenticated
        LM Studio instances continue to work.
        """
        return cls.build_bearer_header(settings_snapshot=settings_snapshot)

    @classmethod
    def create_llm(cls, model_name=None, temperature=0.7, **kwargs):
        """Override to handle LM Studio specifics."""
        from ....config.thread_settings import get_setting_from_snapshot

        settings_snapshot = kwargs.get("settings_snapshot")

        # Get LM Studio URL from settings (default includes /v1 for backward compatibility)
        lmstudio_url = get_setting_from_snapshot(
            "llm.lmstudio.url",
            cls.default_base_url,
            settings_snapshot=settings_snapshot,
        )

        kwargs["base_url"] = cls._ensure_v1_suffix(lmstudio_url)

        # Real key when configured (LM Studio with auth enabled), otherwise
        # the unified placeholder ChatOpenAI accepts; a no-auth LM Studio
        # ignores it.
        kwargs["api_key"] = cls.resolve_api_key_or_placeholder(
            settings_snapshot
        )  # gitleaks:allow

        # Use parent's create_llm but bypass API key check
        return super()._create_llm_instance(model_name, temperature, **kwargs)

    @classmethod
    def is_available(cls, settings_snapshot=None):
        """Check if LM Studio is available.

        Sends ``Authorization: Bearer`` when a key is configured so
        authenticated LM Studio instances are correctly detected as available.
        Empty key → no auth header → unauthenticated installs still work.
        """
        try:
            from ....config.thread_settings import get_setting_from_snapshot
            from ....security import safe_get

            lmstudio_url = get_setting_from_snapshot(
                "llm.lmstudio.url",
                cls.default_base_url,
                settings_snapshot=settings_snapshot,
            )
            base_url = cls._ensure_v1_suffix(lmstudio_url)
            response = safe_get(
                f"{base_url}/models",
                timeout=1,
                headers=cls._get_auth_headers(settings_snapshot),
                allow_localhost=True,
                allow_private_ips=True,
            )
            return response.status_code == 200
        except Exception:
            return False

    @classmethod
    def requires_auth_for_models(cls):
        """LM Studio doesn't require authentication for listing models.

        Returning False keeps unauthenticated installs working (parent
        ``list_models_for_api`` substitutes a dummy key when the real key is
        falsy). Authenticated installs are handled by the override of
        ``list_models_for_api`` below, which reads the user's key from
        settings when no key is passed in directly by the caller.
        """
        return False

    @classmethod
    def list_models_for_api(cls, api_key=None, base_url=None):
        """List models, attaching the optional API key when configured.

        When ``api_key`` is provided directly (e.g., from the settings route),
        it is used as-is. When the caller doesn't supply a key, the key is
        read from the thread-local settings here so authenticated installs are
        handled correctly on both paths. Empty/whitespace falls through to the
        parent's dummy-key path, preserving backward compat for
        unauthenticated installs.
        """
        from ....config.thread_settings import get_setting_from_snapshot

        if not api_key:
            api_key = cls.resolve_api_key()

        if not base_url:
            base_url = get_setting_from_snapshot(
                cls.url_setting,
                cls.default_base_url,
                settings_snapshot=None,
            )

        base_url = cls._ensure_v1_suffix(base_url)
        return super().list_models_for_api(api_key=api_key, base_url=base_url)
