"""Django middleware for automatic trace context and tenant_id injection."""

import logging
from opentelemetry import trace

logger = logging.getLogger(__name__)

# Maximum characters of request body forwarded to OTel span
MAX_BODY_LOG_SIZE = 4096

# Import context holding vars
try:
    from varicon_observability.context import tenant_context, username_context, request_id_context, client_type_context
except ImportError:
    try:
        from varicon_observability.celery_helpers import tenant_context
        import contextvars
        username_context = contextvars.ContextVar("username", default="")
        request_id_context = contextvars.ContextVar("request_id", default="")
        client_type_context = contextvars.ContextVar("client_type", default="")
    except ImportError:
        import contextvars
        tenant_context = contextvars.ContextVar("tenant_id", default="unknown-tenant")
        username_context = contextvars.ContextVar("username", default="")
        request_id_context = contextvars.ContextVar("request_id", default="")
        client_type_context = contextvars.ContextVar("client_type", default="")


class TraceContextMiddleware:
    """
    Django middleware that:
    1. Extracts tenant_id from request headers and sets it in context
    2. Creates a span for each HTTP request (for ASGI/Channels compatibility)
    3. Ensures all logs within the request have trace_id, span_id, and tenant_id
    """
    
    def __init__(self, get_response):
        self.get_response = get_response
        self.tracer = trace.get_tracer("varicon.django.middleware", "1.0.0")
    
    def __call__(self, request):
        # Extract tenant_id from request
        tenant_id = self._extract_tenant_id(request)
        
        # Set tenant_id in context variable FIRST
        if tenant_id:
            tenant_context.set(str(tenant_id))
            
        # Extract username
        username = ""
        if hasattr(request, 'user') and not getattr(request.user, 'is_anonymous', True):
            username = getattr(request.user, 'username', "")
        username_context.set(str(username))
        
        # Extract request_id
        request_id = getattr(request, 'id', "")
        request_id_context.set(str(request_id))
        
        # Create a span for this request
        span_name = f"{request.method} {request.path}"
        
        with self.tracer.start_as_current_span(
            span_name,
            kind=trace.SpanKind.SERVER,
            attributes={
                "http.method": request.method,
                "http.url": request.get_full_path(),
                "http.scheme": request.scheme,
                "http.host": request.get_host(),
                "tenant_id": str(tenant_id) if tenant_id else "unknown",
            }
        ) as span:
            # Log span info for debugging
            ctx = span.get_span_context()
            if ctx.is_valid:
                trace_id = format(ctx.trace_id, "032x")
                span_id = format(ctx.span_id, "016x")
                logger.debug(f"[TRACE_MIDDLEWARE] Request {span_name} trace_id: {trace_id}, span_id: {span_id}, tenant_id: {tenant_id}, user: {username}")
            
            # Capture POST/PUT/PATCH JSON body on span before downstream processing
            if (
                request.method in ("POST", "PUT", "PATCH")
                and getattr(request, "content_type", "")
                and "application/json" in request.content_type
            ):
                try:
                    body = request.body
                    if body:
                        body_str = body.decode("utf-8", errors="replace")
                        if len(body_str) > MAX_BODY_LOG_SIZE:
                            body_str = body_str[:MAX_BODY_LOG_SIZE] + " ...[truncated]"
                        span.set_attribute("http.request.body", body_str)
                except Exception:
                    pass

            # Process the request
            response = self.get_response(request)

            # Add response info to span
            if hasattr(response, 'status_code'):
                span.set_attribute("http.status_code", response.status_code)

            # Add client_type to span (set by ClientTypeMiddleware during request processing)
            client_type = client_type_context.get()
            if client_type:
                span.set_attribute("client.type", client_type)

            return response
    
    def _extract_tenant_id(self, request):
        """Extract tenant_id from request in order of priority."""
        # 1. Try request attribute (set by auth middleware)
        tenant_id = getattr(request, 'tenant_id', None)
        
        # 2. Try Tenant-Id header
        if not tenant_id:
            tenant_id = request.headers.get('Tenant-Id')
        
        # 3. Try X-Tenant-Id header
        if not tenant_id:
            tenant_id = request.headers.get('X-Tenant-Id')
        
        # 4. Try META (for WSGI compatibility)
        if not tenant_id:
            tenant_id = request.META.get('HTTP_TENANT_ID')
        
        return tenant_id
