"""
OpenTelemetry Tracing for The Iron Vault

Provides distributed tracing for filesystem operations,
enabling end-to-end visibility across the Aperion ecosystem.

Configuration via environment variables:
- OTEL_EXPORTER_OTLP_ENDPOINT: OTLP collector endpoint (default: none/disabled)
- OTEL_SERVICE_NAME: Service name (default: aperion-fsal)
- FSAL_TRACING_ENABLED: Enable tracing (default: false)
"""

from __future__ import annotations

import os
from collections.abc import Callable, Generator
from contextlib import contextmanager
from functools import wraps
from typing import Any

# Tracing is optional - gracefully handle missing dependencies
_TRACING_AVAILABLE = False
_tracer = None

try:
    from opentelemetry import trace
    from opentelemetry.sdk.resources import SERVICE_NAME, Resource
    from opentelemetry.sdk.trace import TracerProvider
    from opentelemetry.sdk.trace.export import BatchSpanProcessor
    from opentelemetry.trace import Status, StatusCode
    from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator

    _TRACING_AVAILABLE = True
except ImportError:
    pass


def is_tracing_enabled() -> bool:
    """Check if tracing is enabled and available."""
    if not _TRACING_AVAILABLE:
        return False
    return os.environ.get("FSAL_TRACING_ENABLED", "").lower() in ("true", "1", "yes")


def setup_tracing(service_name: str = "aperion-fsal") -> None:
    """
    Initialize OpenTelemetry tracing.

    Call this once at application startup if tracing is desired.
    """
    global _tracer

    if not _TRACING_AVAILABLE:
        return

    if not is_tracing_enabled():
        return

    # Create resource with service name
    resource = Resource.create({SERVICE_NAME: service_name})

    # Create tracer provider
    provider = TracerProvider(resource=resource)

    # Add OTLP exporter if endpoint is configured
    otlp_endpoint = os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT")
    if otlp_endpoint:
        try:
            from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
                OTLPSpanExporter,
            )

            exporter = OTLPSpanExporter(endpoint=otlp_endpoint)
            provider.add_span_processor(BatchSpanProcessor(exporter))
        except ImportError:
            # OTLP exporter not installed
            pass

    # Set as global provider
    trace.set_tracer_provider(provider)
    _tracer = trace.get_tracer(__name__)


def get_tracer():
    """Get the configured tracer, or a no-op tracer if not configured."""
    global _tracer
    if _tracer is None and _TRACING_AVAILABLE:
        _tracer = trace.get_tracer(__name__)
    return _tracer


@contextmanager
def trace_operation(
    name: str,
    attributes: dict[str, Any] | None = None,
) -> Generator[Any, None, None]:
    """
    Context manager to trace a filesystem operation.

    Usage:
        with trace_operation("read_file", {"path": "/data/file.txt"}) as span:
            # do the work
            span.set_attribute("bytes_read", 1024)
    """
    if not is_tracing_enabled():
        # Return a no-op context
        yield _NoOpSpan()
        return

    tracer = get_tracer()
    if tracer is None:
        yield _NoOpSpan()
        return

    with tracer.start_as_current_span(name) as span:
        if attributes:
            for key, value in attributes.items():
                span.set_attribute(key, value)
        try:
            yield span
        except Exception as e:
            span.set_status(Status(StatusCode.ERROR, str(e)))
            span.record_exception(e)
            raise


class _NoOpSpan:
    """No-op span for when tracing is disabled."""

    def set_attribute(self, key: str, value: Any) -> None:
        pass

    def set_status(self, status: Any) -> None:
        pass

    def record_exception(self, exception: Exception) -> None:
        pass

    def add_event(self, name: str, attributes: dict | None = None) -> None:
        pass


def trace_function(name: str | None = None):
    """
    Decorator to automatically trace a function.

    Usage:
        @trace_function("custom_operation")
        async def my_operation():
            ...

        @trace_function()  # Uses function name
        def another_operation():
            ...
    """

    def decorator(func: Callable) -> Callable:
        span_name = name or func.__name__

        @wraps(func)
        async def async_wrapper(*args, **kwargs):
            with trace_operation(span_name):
                result = await func(*args, **kwargs)
                return result

        @wraps(func)
        def sync_wrapper(*args, **kwargs):
            with trace_operation(span_name):
                result = func(*args, **kwargs)
                return result

        # Return appropriate wrapper based on function type
        import asyncio

        if asyncio.iscoroutinefunction(func):
            return async_wrapper
        return sync_wrapper

    return decorator


def extract_trace_context(headers: dict[str, str]) -> dict | None:
    """
    Extract trace context from incoming request headers.

    This allows propagating traces from upstream services.
    """
    if not _TRACING_AVAILABLE:
        return None

    propagator = TraceContextTextMapPropagator()
    return propagator.extract(carrier=headers)


def inject_trace_context(headers: dict[str, str]) -> None:
    """
    Inject trace context into outgoing request headers.

    Use this when making calls to downstream services.
    """
    if not _TRACING_AVAILABLE:
        return

    propagator = TraceContextTextMapPropagator()
    propagator.inject(carrier=headers)
