"""OpenAI-chat streamed usage request and extraction helpers."""

import json
from collections.abc import Mapping
from typing import Any

import openai

_USAGE_OPTION_KEYS = ("stream_options", "include_usage")
_USAGE_REJECTION_WORDS = (
    "unsupported",
    "not supported",
    "unknown",
    "unrecognized",
    "unexpected",
    "invalid",
    "extra",
    "forbidden",
    "not permitted",
)


def request_stream_usage(body: dict[str, Any]) -> None:
    """Ask an OpenAI-compatible streaming endpoint for its final usage chunk."""
    stream_options = body.get("stream_options")
    if stream_options is None:
        body["stream_options"] = {"include_usage": True}
        return
    if isinstance(stream_options, dict):
        stream_options["include_usage"] = True


def clone_without_stream_usage(body: dict[str, Any]) -> dict[str, Any] | None:
    """Return a clone with only ``include_usage`` removed from stream options."""
    stream_options = body.get("stream_options")
    if not isinstance(stream_options, dict):
        return None
    if "include_usage" not in stream_options:
        return None

    retry_body = dict(body)
    retry_stream_options = dict(stream_options)
    retry_stream_options.pop("include_usage", None)
    if retry_stream_options:
        retry_body["stream_options"] = retry_stream_options
    else:
        retry_body.pop("stream_options", None)
    return retry_body


def is_stream_usage_rejection(error: Exception) -> bool:
    """Return whether upstream rejected the optional streamed-usage request."""
    if not _is_bad_request_like(error):
        return False
    text = _error_text(error)
    if not any(key in text for key in _USAGE_OPTION_KEYS):
        return False
    return any(word in text for word in _USAGE_REJECTION_WORDS)


def usage_int(usage_info: Any, key: str) -> int | None:
    """Extract an integer usage field from OpenAI SDK objects or plain dicts."""
    if usage_info is None:
        return None
    if isinstance(usage_info, Mapping):
        value = usage_info.get(key)
    else:
        value = getattr(usage_info, key, None)
        if value is None:
            extra = getattr(usage_info, "model_extra", None)
            if isinstance(extra, Mapping):
                value = extra.get(key)
    return value if isinstance(value, int) and not isinstance(value, bool) else None


def _is_bad_request_like(error: Exception) -> bool:
    if isinstance(error, openai.BadRequestError):
        return True
    status = getattr(error, "status_code", None)
    if status is None:
        response = getattr(error, "response", None)
        status = (
            getattr(response, "status_code", None) if response is not None else None
        )
    return status in (400, 422)


def _error_text(error: Exception) -> str:
    parts = [str(error)]
    body = getattr(error, "body", None)
    if body is not None:
        parts.append(json.dumps(body, default=str))
    response = getattr(error, "response", None)
    if response is not None:
        text = getattr(response, "text", None)
        if isinstance(text, str) and text:
            parts.append(text)
    return " ".join(parts).lower()
