"""OpenRouter chat factory backed by ``langchain_openrouter``.

LangChain-native shape (3tears v0.6.0+): :func:`create_openrouter_chat`
returns a fully-configured ``ChatOpenRouter`` instance. ``ChatOpenRouter``
expects the request timeout in milliseconds — the factory accepts seconds
to match the rest of the API surface and converts internally.

Tool-name translation: OpenRouter routes some upstream models through
backends with strict tool-name validators (Bedrock requires
``^[a-zA-Z0-9_-]{1,128}$`` -- no dots), but the canonical 3tears tool
name is the dotted ``threetears.X`` form. The factory returns a
:class:`_NameTranslatingChatOpenRouter` subclass -- a thin binding of the
shared
:class:`~threetears.models.providers._name_translation_mixin.NameTranslatingChatMixin`
(identical hooks across the OpenAI / OpenRouter / Anthropic wrappers) --
that translates dot-to-underscore on outgoing tool specs / history and
underscore-to-dot on incoming ``tool_calls``, so application code never
sees the wire form. The translation is keyed by the dotted -> underscored
mapping built at ``bind_tools`` time, so it round-trips losslessly even
for tools whose names contain underscores already (e.g.
``threetears.web_search`` -> ``threetears_web_search`` and back).
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from pydantic import PrivateAttr

from threetears.models.capabilities import ModelCapabilities, register_capabilities
from threetears.models.enums import ModelStatus, ModelTier, ModelType
from threetears.models.providers._name_translation_mixin import NameTranslatingChatMixin

if TYPE_CHECKING:
    from langchain_openrouter import ChatOpenRouter

__all__ = [
    "OPENROUTER_PROVIDER_NAME",
    "create_openrouter_chat",
]


OPENROUTER_PROVIDER_NAME = "openrouter"


def _build_translating_chat_class() -> type[ChatOpenRouter]:
    """build the :class:`ChatOpenRouter` subclass with name-translation hooks.

    Defined inside a function so the langchain-openrouter import is
    lazy -- :mod:`threetears.models.providers.openrouter` is imported
    eagerly at package load to populate the capability registry, and we
    must not require ``langchain-openrouter`` to be installed for that
    side-effect import to succeed (it is an optional dependency,
    selected via ``3tears-models[openrouter]``).

    :return: name-translating ChatOpenRouter subclass
    :rtype: type[ChatOpenRouter]
    """
    from langchain_openrouter import ChatOpenRouter

    class _NameTranslatingChatOpenRouter(NameTranslatingChatMixin, ChatOpenRouter):
        """``ChatOpenRouter`` with dot<->underscore tool-name translation at
        the wire boundary, hiding Bedrock-style provider quirks from the rest
        of the codebase.

        All translation hooks live in :class:`NameTranslatingChatMixin`
        (mixed in ahead of ``ChatOpenRouter`` so ``super()`` resolves to it);
        this subclass only supplies the per-instance reverse-map slot.

        :ivar _name_reverse_map: underscored-wire -> canonical-dotted map,
            populated at ``bind_tools`` time.
        :ptype _name_reverse_map: dict[str, str]
        """

        _name_reverse_map: dict[str, str] = PrivateAttr(default_factory=dict)

    return _NameTranslatingChatOpenRouter


def create_openrouter_chat(
    model_name: str,
    api_key: str,
    *,
    timeout: int = 120,
    max_retries: int = 2,
    **extra_kwargs: object,
) -> ChatOpenRouter:
    """creates a configured ``ChatOpenRouter`` for OpenRouter-routed models.

    Returns the :class:`_NameTranslatingChatOpenRouter` subclass, not
    a vanilla ``ChatOpenRouter``. Application code interacts with it
    exactly the same way (it IS a ``ChatOpenRouter``); the only
    difference is the wire-side tool-name translation that hides
    Bedrock-style provider quirks from the rest of the codebase.

    :param model_name: OpenRouter model identifier (e.g. ``deepseek/deepseek-chat-v3-0324``)
    :ptype model_name: str
    :param api_key: OpenRouter API key
    :ptype api_key: str
    :param timeout: request timeout in seconds (converted to ms internally)
    :ptype timeout: int
    :param max_retries: maximum retry attempts for failed requests
    :ptype max_retries: int
    :param extra_kwargs: additional keyword arguments forwarded to ``ChatOpenRouter``
    :ptype extra_kwargs: object
    :return: configured ``ChatOpenRouter`` (the name-translating subclass)
    :rtype: ChatOpenRouter
    """
    chat_cls = _build_translating_chat_class()

    # langchain-openrouter 0.1.0 defaults app_title="langchain" and forwards
    # it as `x_title` to the underlying openrouter SDK. openrouter 0.8+
    # renamed that kwarg to `x_open_router_title`, so the old name now
    # raises TypeError. setting both to None restores compatibility until
    # langchain-openrouter ships a fix; callers that need attribution can
    # pass app_title/app_url via extra_kwargs.
    kwargs: dict[str, object] = {
        "model": model_name,
        "api_key": api_key,
        "timeout": timeout * 1000,
        "max_retries": max_retries,
        "app_title": None,
        "app_url": None,
    }
    kwargs.update(extra_kwargs)

    model: ChatOpenRouter = chat_cls(**kwargs)
    return model


# -- capability registration -------------------------------------------------

# representative OpenRouter ids. additional ids can be registered by host
# apps at boot via register_capabilities().
_OPENROUTER_CAPABILITIES: dict[str, ModelCapabilities] = {
    "deepseek/deepseek-chat-v3-0324": ModelCapabilities(
        model_name="deepseek/deepseek-chat-v3-0324",
        provider_name=OPENROUTER_PROVIDER_NAME,
        model_type=ModelType.CHAT,
        model_tier=ModelTier.LARGE,
        model_status=ModelStatus.ACTIVE,
        context_window=64_000,
        max_output_tokens=8_192,
        supports_streaming=True,
        supports_tools=True,
        supports_vision=False,
        requires_alternating_roles=True,
        # DeepSeek's direct API runs automatic context caching and surfaces
        # ``cached_tokens`` on the response without an opt-in marker; the
        # ``deepseek/`` slug routed through OpenRouter inherits the same
        # behavior. Same request shape as OpenAI auto-cache.
        supports_anthropic_cache_control=False,
        supports_openai_auto_cache=True,
        min_cacheable_tokens=0,
        cache_ttl_seconds=0,
    ),
    "deepseek/deepseek-r1": ModelCapabilities(
        model_name="deepseek/deepseek-r1",
        provider_name=OPENROUTER_PROVIDER_NAME,
        model_type=ModelType.CHAT,
        model_tier=ModelTier.LARGE,
        model_status=ModelStatus.ACTIVE,
        context_window=64_000,
        max_output_tokens=8_192,
        supports_streaming=True,
        supports_tools=False,
        supports_vision=False,
        requires_alternating_roles=True,
        supports_anthropic_cache_control=False,
        supports_openai_auto_cache=True,
        min_cacheable_tokens=0,
        cache_ttl_seconds=0,
    ),
}


for _model_id, _caps in _OPENROUTER_CAPABILITIES.items():
    register_capabilities(_model_id, _caps)
