"""Span processor to filter out unwanted spans (like database queries)."""

from typing import Optional
from opentelemetry.sdk.trace import SpanProcessor, ReadableSpan
from opentelemetry.context import Context


class DatabaseSpanFilter(SpanProcessor):
    """
    A span processor that filters out database query spans and health check spans.

    This processor prevents database query spans and health check HTTP spans from
    being exported to the tracing backend (Signoz), keeping only meaningful spans.
    """

    # Health check URL substrings — matched against http.target / http.route / url.path
    HEALTH_CHECK_URL_PATTERNS = [
        "/health_check",
        "/healthcheck",
        "/health",
        "/ping",
    ]

    # List of span names or patterns to filter out
    FILTERED_SPAN_PREFIXES = [
        "SELECT",
        "INSERT",
        "UPDATE",
        "DELETE",
        "BEGIN",
        "COMMIT",
        "ROLLBACK",
        "CREATE",
        "ALTER",
        "DROP",
        "TRUNCATE",
        # PostgreSQL specific
        "psycopg2",
        "postgres",
        # Django ORM specific
        "django.db",
    ]
    
    def __init__(self, next_processor: SpanProcessor):
        """
        Initialize the filter with a downstream processor.
        
        Args:
            next_processor: The actual span processor (e.g., BatchSpanProcessor)
                          that should receive non-filtered spans
        """
        self.next_processor = next_processor
    
    def on_start(
        self, 
        span: "Span", 
        parent_context: Optional[Context] = None
    ) -> None:
        """Called when a span starts."""
        # Forward to next processor if span should not be filtered
        if not self._should_filter(span):
            self.next_processor.on_start(span, parent_context)
    
    def on_end(self, span: ReadableSpan) -> None:
        """Called when a span ends. Filter here before exporting."""
        if not self._should_filter(span):
            self.next_processor.on_end(span)
    
    def shutdown(self) -> None:
        """Shutdown the processor."""
        self.next_processor.shutdown()
    
    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Force flush the processor."""
        return self.next_processor.force_flush(timeout_millis)
    
    def _should_filter(self, span) -> bool:
        """
        Determine if a span should be filtered out.
        
        Args:
            span: The span to check
            
        Returns:
            True if the span should be filtered (not exported), False otherwise
        """
        span_name = span.name if hasattr(span, 'name') else str(span)
        
        # Check if span name starts with any filtered prefix
        for prefix in self.FILTERED_SPAN_PREFIXES:
            if span_name.startswith(prefix):
                return True
        
        # Check span attributes for database-related and health check attributes
        if hasattr(span, 'attributes'):
            attributes = span.attributes or {}

            # Filter by db.system attribute (added by DB instrumentors)
            if "db.system" in attributes:
                return True

            # Filter by db.statement attribute (SQL queries)
            if "db.statement" in attributes:
                return True

            # Filter by db.operation attribute
            if "db.operation" in attributes:
                return True

            # Filter health check HTTP spans by URL attributes
            for url_attr in ("http.target", "http.route", "url.path", "http.url"):
                val = attributes.get(url_attr, "")
                if val:
                    for pattern in self.HEALTH_CHECK_URL_PATTERNS:
                        if pattern in val:
                            return True

        # Filter health check spans by span name (e.g. "GET /health_check/")
        for pattern in self.HEALTH_CHECK_URL_PATTERNS:
            if pattern in span_name:
                return True

        return False

