"""
[PHASE3-R3-SDK-REQUESTID-NATIVE] Instrumented OTLP exporter wrapper.

TELEMETRY_CONTRACT v1.0.0 §3.2 / §16: every batch sent to /otel-collector MUST
carry a per-batch ``X-Request-Id`` (UUID v4) that is REUSED on retry, so the
collector's ``insert_otel_wal_idempotent`` RPC can dedupe (HTTP 409) instead of
double-billing.

The native ``OTLPSpanExporter`` from ``opentelemetry-exporter-otlp-proto-http``
only accepts a *static* headers dict, so users who bypass
``AgentBillSpanProcessor`` and wire raw OTLP get no idempotency.

Usage::

    from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
    from agentbill import wrap_otlp_exporter

    exporter = wrap_otlp_exporter(OTLPSpanExporter(
        endpoint="https://api.agentbill.io/functions/v1/otel-collector",
        headers={"X-API-Key": os.environ["AGENTBILL_API_KEY"]},
    ))

Concurrency: ``BatchSpanProcessor`` calls ``exporter.export()`` sequentially per
processor and waits for the result before issuing the next batch, so mutating
the exporter's ``_session.headers`` per call is safe. Internal HTTP retries
(408/429/5xx) reuse the same ``Session`` headers — same X-Request-Id.

[LEGACY_COMPAT:PHASE3-R3-SDK-REQUESTID-NATIVE] Existing direct OTLPSpanExporter
usage is unchanged. This wrapper is additive and opt-in.
"""
from __future__ import annotations

import uuid
from typing import Any, Dict, Optional

CONTRACT_VERSION = "3"


def wrap_otlp_exporter(exporter: Any) -> Any:
    """
    Wrap an OTLPSpanExporter (or any object exposing ``export(spans)`` and a
    ``_session`` with mutable ``headers``) so each batch carries
    ``X-Request-Id`` and ``Contract-Version: 1``. Returns the same instance.
    """
    if getattr(exporter, "_agentbill_request_id_wrapped", False):
        return exporter

    original_export = exporter.export

    # Ensure a session exists. The official OTLP HTTP exporter creates one in
    # __init__; defensively create one if a custom exporter omits it.
    session = getattr(exporter, "_session", None)
    if session is None:
        try:
            import requests  # type: ignore
            session = requests.Session()
            exporter._session = session
        except Exception:
            session = None

    if session is not None:
        session.headers["Contract-Version"] = CONTRACT_VERSION

    def _export(spans):
        request_id = uuid.uuid4().hex
        if session is not None:
            session.headers["X-Request-Id"] = request_id
        return original_export(spans)

    exporter.export = _export  # type: ignore[assignment]
    exporter._agentbill_request_id_wrapped = True  # type: ignore[attr-defined]
    return exporter


def create_instrumented_exporter(
    *,
    url: str,
    api_key: str,
    extra_headers: Optional[Dict[str, str]] = None,
) -> Any:
    """
    Convenience factory: builds an OTLPSpanExporter pointing at
    ``/otel-collector`` and wraps it. Use this for raw-OTEL pipelines that need
    contract-compliant idempotency without ``AgentBillSpanProcessor``.
    """
    from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter  # type: ignore

    headers = {"X-API-Key": api_key}
    if extra_headers:
        headers.update(extra_headers)
    return wrap_otlp_exporter(OTLPSpanExporter(endpoint=url, headers=headers))