"""Monkey-patches for known bugs in upstream dependencies."""

from __future__ import annotations

from typing import Any

_applied: set[str] = set()


def apply_all() -> None:
    """Apply all registered patches (idempotent)."""
    _patch_openai_responses_tools_bug()
    _patch_google_genai_missing_llm_prompts()


def _patch_openai_responses_tools_bug() -> None:
    """Fix ``TracedData.model_dump`` returning ``tools: None``.

    In ``opentelemetry-instrumentation-openai`` <= 0.52.3, the responses
    instrumentor does::

        merged_tools = existing_data.get("tools", []) + request_tools

    When ``TracedData.tools`` is ``None``, ``model_dump()`` produces
    ``{"tools": None}``.  ``.get("tools", [])`` then returns ``None``
    (the key *exists*, so the default is ignored), causing
    ``TypeError: unsupported operand type(s) for +: 'NoneType' and 'list'``.

    This patch wraps ``TracedData.model_dump`` so that a ``None`` tools
    value is normalised to ``[]``.
    """
    tag = "openai_responses_tools"
    if tag in _applied:
        return
    _applied.add(tag)

    try:
        from opentelemetry.instrumentation.openai.v1 import (  # pyright: ignore[reportMissingTypeStubs]
            responses_wrappers,
        )
    except ImportError:
        return

    if not hasattr(responses_wrappers, "TracedData"):
        return

    _original: Any = responses_wrappers.TracedData.model_dump

    def _patched(self: object, *args: object, **kwargs: object) -> dict[str, Any]:
        result: dict[str, Any] = _original(self, *args, **kwargs)
        if result.get("tools") is None:
            result["tools"] = []
        return result

    responses_wrappers.TracedData.model_dump = _patched  # type: ignore[method-assign]


def _patch_google_genai_missing_llm_prompts() -> None:
    """Add missing ``SpanAttributes.LLM_PROMPTS`` used by the Gemini instrumentor.

    In ``opentelemetry-instrumentation-google-generativeai`` <= 0.21.5,
    ``span_utils.py`` references ``SpanAttributes.LLM_PROMPTS`` to set
    input/prompt attributes when ``contents`` is a list. However, the
    ``opentelemetry-semantic-conventions-ai`` package removed or never
    defined ``LLM_PROMPTS``, causing an ``AttributeError`` that is
    silently swallowed by the ``@dont_throw`` decorator. As a result,
    user messages are never recorded on spans.

    This patch defines the missing attribute so that prompt content is
    correctly captured as ``gen_ai.prompt.<i>.content`` /
    ``gen_ai.prompt.<i>.role``.
    """
    tag = "google_genai_llm_prompts"
    if tag in _applied:
        return
    _applied.add(tag)

    try:
        from opentelemetry.semconv_ai import (  # pyright: ignore[reportMissingTypeStubs]
            SpanAttributes,
        )
    except ImportError:
        return

    if hasattr(SpanAttributes, "LLM_PROMPTS"):
        return

    SpanAttributes.LLM_PROMPTS = "gen_ai.prompt"
