"""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"
    # api_key_setting=None tells the parent class no key is *required*; the
    # create_llm override below still reads `llm.lmstudio.api_key` for the
    # optional auth-enabled case and falls back to a placeholder otherwise.
    api_key_setting = None  # type: ignore[assignment]
    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
    )

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

    # Hardcoded since `api_key_setting` is None at the class level (the route
    # reads via `cls.api_key_setting`; LM Studio handles the key inside its
    # own methods instead, so the route's path stays neutral).
    _API_KEY_PATH = "llm.lmstudio.api_key"

    @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.
        """
        from ....config.thread_settings import get_setting_from_snapshot

        headers: dict[str, str] = {}
        api_key = get_setting_from_snapshot(
            cls._API_KEY_PATH,
            "",
            settings_snapshot=settings_snapshot,
        )
        if api_key and str(api_key).strip():
            headers["Authorization"] = f"Bearer {str(api_key).strip()}"
        return headers

    @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,
        )
        api_key = get_setting_from_snapshot(
            cls._API_KEY_PATH,
            "",
            settings_snapshot=settings_snapshot,
        )

        # Use URL as-is (user should provide complete URL including /v1 if needed)
        kwargs["base_url"] = normalize_url(lmstudio_url)

        # If user configured a real API key (LM Studio with auth enabled), use
        # it. Otherwise pass a placeholder ChatOpenAI accepts; a no-auth
        # LM Studio ignores it.
        kwargs["api_key"] = api_key or "not-required"  # 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,
            )
            # Use URL as-is (default already includes /v1)
            base_url = normalize_url(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:
            raw = get_setting_from_snapshot(
                cls._API_KEY_PATH,
                "",
                settings_snapshot=None,
            )
            api_key = str(raw or "").strip() or None

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

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