import logging
import os
from typing import Any, Optional

from openinference.semconv.resource import ResourceAttributes
from opentelemetry import trace as trace_api
from opentelemetry.sdk import trace as trace_sdk
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import SpanLimits
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SimpleSpanProcessor

from .ctracing_otl_span_exporter import CtracingOTLSpanExporter

logger = logging.getLogger(__name__)


def _build_exporter_headers(headers: Optional[dict]) -> dict:
    """Build OTLP exporter headers, adding Phoenix auth when available.

    If PHOENIX_API_KEY is set, add `authorization: Bearer <key>` unless an
    authorization header is already provided by the caller.
    """
    merged_headers = dict(headers or {})

    phoenix_api_key = os.getenv("PHOENIX_API_KEY")
    has_authorization_header = any(header.lower() == "authorization" for header in merged_headers)
    if phoenix_api_key and not has_authorization_header:
        if phoenix_api_key.lower().startswith("bearer "):
            auth_value = phoenix_api_key
        else:
            auth_value = f"Bearer {phoenix_api_key}"
        merged_headers["authorization"] = auth_value

    return merged_headers


def _extract_text_content(value: Any) -> str:
    """Extract plain text from Google GenAI Part/Content objects.

    LiveKit passes system_instruction as a list of Part objects, but OTel span
    attributes only accept primitive types. This converts them to strings.
    """
    if isinstance(value, str):
        return value
    if isinstance(value, list):
        texts = []
        for item in value:
            if isinstance(item, str):
                texts.append(item)
            elif hasattr(item, "text") and item.text:
                texts.append(item.text)
            else:
                texts.append(str(item))
        return "\n".join(texts)
    if hasattr(value, "parts"):
        return _extract_text_content(value.parts)
    if hasattr(value, "text") and value.text:
        return value.text
    return str(value)


def _patch_google_genai_instrumentor() -> None:
    """Patch the OpenInference Google GenAI instrumentor to handle Part objects.

    The instrumentor's request extractor stores system_instruction (which LiveKit
    builds as a list of Part objects) directly as a span attribute. OTel rejects
    non-primitive types. This patch converts Part objects to plain text strings.
    """
    try:
        from openinference.instrumentation.google_genai._request_attributes_extractor import (
            _RequestAttributesExtractor,
        )

        original = _RequestAttributesExtractor.get_extra_attributes_from_request

        def _patched_get_extra_attributes(self, request_parameters):
            for key, value in original(self, request_parameters):
                if "message.content" in key and not isinstance(value, (str, int, float, bool)):
                    yield key, _extract_text_content(value)
                else:
                    yield key, value

        _RequestAttributesExtractor.get_extra_attributes_from_request = _patched_get_extra_attributes
        logger.debug("Patched Google GenAI instrumentor for Part object handling")
    except Exception as e:
        logger.warning(f"Could not patch Google GenAI instrumentor: {e}")


def init_tracing(
    endpoint_url: str,
    project_name: str,
    exporter_user_agent: str,
    max_span_attribute_length: Optional[int] = None,
    processor_type: Optional[str] = "batch",
    instrument_openai: Optional[bool] = True,
    instrument_aiohttp: Optional[bool] = False,
    instrument_google: Optional[bool] = True,
    instrument_claude: Optional[bool] = False,
    headers: Optional[dict] = None,
):
    """
    Initializes tracing for the application. Includes instrumentation for OpenAI

    Parameters
    ----------
        endpoint_url : str
            The URL of the tracing endpoint (e.g., an OTLP collector).

        project_name : str
            The name of the project.

        exporter_user_agent : str
            User-Agent header value to pass in the span exporter request

        max_span_attribute_length : Optional[int]
            The maximum length for span attributes.
            This limits the length of any string attribute in a span. If not specified, a default
            value from the OpenTelemetry SDK will be used.

        processor_type : Optional[str]
            The type of processor to use.
            Acceptable values are "simple" or "batch".
            Defaults to "batch".

        instrument_openai : Optional[bool]
            Enable auto-instrumentation for OpenAI

        instrument_google : Optional[bool]
            Enable auto-instrumentation for Google GenAI (Gemini)

    Returns
    -------
    None

    Example
    -------
    .. code-block:: python

        from ytracing import init_tracing

        init_tracing(
            endpoint_url="http://localhost:4317",
            project_name="my-project",
            exporter_user_agent="x-omni-agent"
        )
    """

    if processor_type not in ["simple", "batch"]:
        raise ValueError("Invalid processor_type. Must be 'simple' or 'batch'.")

    resource = Resource(attributes={ResourceAttributes.PROJECT_NAME: project_name})
    tracer_provider = trace_sdk.TracerProvider(
        resource=resource,
        span_limits=SpanLimits(max_span_attribute_length=max_span_attribute_length),
    )
    span_exporter = CtracingOTLSpanExporter(
        endpoint=endpoint_url,
        user_agent=exporter_user_agent,
        headers=_build_exporter_headers(headers=headers),
    )

    if processor_type == "simple":
        span_processor = SimpleSpanProcessor(span_exporter=span_exporter)
    else:
        span_processor = BatchSpanProcessor(span_exporter=span_exporter)

    tracer_provider.add_span_processor(span_processor=span_processor)
    if instrument_openai:
        from openinference.instrumentation.openai import OpenAIInstrumentor

        OpenAIInstrumentor().instrument(tracer_provider=tracer_provider)
    if instrument_aiohttp:
        from opentelemetry.instrumentation.aiohttp_client import AioHttpClientInstrumentor

        AioHttpClientInstrumentor().instrument(tracer_provider=tracer_provider)
    if instrument_claude:
        from openinference.instrumentation.anthropic import AnthropicInstrumentor

        AnthropicInstrumentor().instrument(tracer_provider=tracer_provider)
    if instrument_google:
        try:
            from openinference.instrumentation.google_genai import GoogleGenAIInstrumentor

            GoogleGenAIInstrumentor().instrument(tracer_provider=tracer_provider)
            _patch_google_genai_instrumentor()
            logger.info("Google GenAI (OpenInference) instrumentation enabled")
        except Exception as e:
            logger.error(f"Failed to instrument Google GenAI: {e}", exc_info=True)

    trace_api.set_tracer_provider(tracer_provider=tracer_provider)
    return tracer_provider
