"""
OpenTelemetry Configurator Plugin for Clarvynn.

This class is auto-discovered by opentelemetry-instrument via entry_points.
It hooks into OpenTelemetry's initialization to add Clarvynn's SpanProcessor.

ARCHITECTURE NOTE: Uses SpanProcessor (tail-based) NOT Sampler (head-based)
This allows evaluation of actual request outcomes (status_code, duration, errors).
"""

import os
from typing import Optional

from clarvynn.logging import configure_logging, get_logger, log_startup_block
from opentelemetry import trace
from opentelemetry.sdk._configuration import _OTelSDKConfigurator, logging
from opentelemetry.sdk.trace import TracerProvider

logger = get_logger("configurator")


class ClarvynnConfigurator(_OTelSDKConfigurator):
    """
    Integrates Clarvynn with OpenTelemetry auto-instrumentation.

    Activated when CLARVYNN_ENABLED=true environment variable is set.

    This follows OpenTelemetry's standard configurator pattern and is
    automatically discovered via the entry_points mechanism in setup.py.

    ARCHITECTURE: Uses SpanProcessor for Deferred Head Sampling, which evaluates
    spans AFTER they complete, allowing intelligent decisions based on actual
    request outcomes (errors, slow requests, critical paths).
    """

    def _configure(self, **kwargs):
        """
        Called by opentelemetry-instrument during initialization.

        This runs AFTER OpenTelemetry creates the TracerProvider but
        BEFORE auto-instrumentation is applied to the user's app.

        WHAT THIS DOES:
        1. Calls parent to initialize OpenTelemetry SDK
        2. Loads CPL policy from file
        3. Creates ProductionCPLAdapter (orchestrator)
        4. Creates ClarvynnSpanProcessor (tail-based evaluator)
        5. Adds processor to TracerProvider
        6. Sets up logging correlation

        Returns:
            None if configuration succeeds or Clarvynn is disabled
        """

        # Configure Clarvynn logging based on environment
        log_level = os.getenv("CLARVYNN_LOG_LEVEL", "info")
        configure_logging(level=log_level)

        logger.debug("ClarvynnConfigurator._configure() called")
        logger.debug(f"CLARVYNN_ENABLED={os.getenv('CLARVYNN_ENABLED')}")

        enabled = os.getenv("CLARVYNN_ENABLED", "false").lower() == "true"

        if not enabled:
            logger.debug("Clarvynn not enabled (CLARVYNN_ENABLED != true)")
            # Fall back to default OTel initialization
            super()._configure(**kwargs)
            return

        # Call parent to initialize TracerProvider, but we'll manage processors
        logger.debug("Calling parent to initialize TracerProvider")
        super()._configure(**kwargs)
        logger.debug("TracerProvider initialized")

        logger.info("Clarvynn governance initializing...")

        try:
            policy_path = os.getenv("CLARVYNN_POLICY_PATH", "/etc/clarvynn/policy.yaml")

            if not os.path.exists(policy_path):
                logger.error(f"❌ Policy file not found: {policy_path}")
                logger.error("   Set CLARVYNN_POLICY_PATH to valid policy file")
                return

            from core.cpl_engine.python.production_cpl_adapter import ProductionCPLAdapter

            logger.info(f"Loading policy from: {policy_path}")
            adapter = ProductionCPLAdapter(policy_file=policy_path)
            adapter.setup()

            tracer_provider = trace.get_tracer_provider()

            # 2. Initialize Clarvynn components
            if not isinstance(tracer_provider, TracerProvider):
                logger.warning("TracerProvider is not SDK TracerProvider")
                logger.warning("Clarvynn may not work correctly")
                logger.warning("Ensure opentelemetry-instrument is running")
                return

            # Remove the parent's BatchSpanProcessor (it exports everything)
            logger.debug("Removing default BatchSpanProcessor")
            try:
                if hasattr(tracer_provider, "_active_span_processor"):
                    active_processor = tracer_provider._active_span_processor
                    if hasattr(active_processor, "_span_processors"):
                        # The CompositeSpanProcessor stores processors as a tuple
                        # We need to replace it with an empty tuple
                        active_processor._span_processors = ()
                        logger.debug(
                            "✓ Default processor removed successfully - Clarvynn now controls export"
                        )
                    else:
                        logger.warning(
                            "SpanProcessor structure changed (no _span_processors). Duplicate spans may be exported."
                        )
                else:
                    logger.warning(
                        "TracerProvider structure changed (no _active_span_processor). Duplicate spans may be exported."
                    )
            except Exception as e:
                # "Silent success, loud failure" philosophy
                try:
                    import opentelemetry.sdk

                    otel_version = getattr(opentelemetry.sdk, "__version__", "unknown")
                except ImportError:
                    otel_version = "unknown"

                logger.warning(
                    f"Could not remove default processor (OTel SDK v{otel_version}): {e}"
                )
                logger.warning("Clarvynn will work but may export duplicate spans")

            from clarvynn.processors.clarvynn_span_processor import ClarvynnSpanProcessor
            from clarvynn.processors.tracestate_exporter import ClarvynnTraceStateExporter

            # Respect OTEL_TRACES_EXPORTER environment variable
            traces_exporter = os.getenv("OTEL_TRACES_EXPORTER", "otlp")
            logger.debug(f"OTEL_TRACES_EXPORTER={traces_exporter}")

            # Create the appropriate exporter based on user configuration
            if traces_exporter == "console":
                from opentelemetry.sdk.trace.export import ConsoleSpanExporter

                base_exporter = ConsoleSpanExporter()
                logger.info("Using ConsoleSpanExporter (OTEL_TRACES_EXPORTER=console)")
            elif traces_exporter == "otlp" or traces_exporter.startswith("otlp"):
                from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter

                otlp_endpoint = os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317")
                base_exporter = OTLPSpanExporter(endpoint=otlp_endpoint)
                logger.info(f"Using OTLPSpanExporter (endpoint: {otlp_endpoint})")
            elif traces_exporter == "none":
                # No exporter - useful for testing
                from opentelemetry.sdk.trace.export import SpanExporter

                class NoOpExporter(SpanExporter):
                    def export(self, spans):
                        return None

                    def shutdown(self):
                        pass

                    def force_flush(self, timeout_millis=30000):
                        return True

                base_exporter = NoOpExporter()
                logger.info("Using NoOpExporter (OTEL_TRACES_EXPORTER=none)")
            else:
                logger.warning(
                    f"Unknown OTEL_TRACES_EXPORTER='{traces_exporter}', defaulting to OTLP"
                )
                from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter

                otlp_endpoint = os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317")
                base_exporter = OTLPSpanExporter(endpoint=otlp_endpoint)

            # Wrap with BatchExporterAdapter to ensure async export (non-blocking)
            from clarvynn.processors.batch_exporter_adapter import BatchExporterAdapter

            async_exporter = BatchExporterAdapter(base_exporter)

            # Wrap with TraceState exporter for W3C TraceContext Level 2 support
            tracestate_exporter = ClarvynnTraceStateExporter(async_exporter, adapter)

            # Initialize Log Buffer for Flight Recorder
            from core.cpl_engine.python.log_buffer import SharedRingBuffer

            # Flight Recorder Configuration
            max_logs = int(os.getenv("CLARVYNN_MAX_LOGS_PER_TRACE", "100"))
            max_traces = int(os.getenv("CLARVYNN_MAX_ACTIVE_TRACES", "10000"))
            ttl_seconds = int(os.getenv("CLARVYNN_FLIGHT_RECORDER_TTL", "300"))

            logger.debug(
                f"Flight Recorder Config: max_logs={max_logs}, max_traces={max_traces}, ttl={ttl_seconds}s"
            )

            log_buffer = SharedRingBuffer(
                max_logs_per_trace=max_logs, max_active_traces=max_traces, ttl_seconds=ttl_seconds
            )

            # Create processor with wrapped exporter and log buffer
            # Note: We pass log_exporter=None initially, it will be set by _setup_log_filtering
            processor = ClarvynnSpanProcessor(adapter, tracestate_exporter, log_buffer=log_buffer)

            tracer_provider.add_span_processor(processor)

            # Set up log filtering (and update processor with log exporter)
            self._setup_log_filtering(adapter, processor, log_buffer)

            # Log startup information
            conditions_list = "\n".join(
                [f"   - {cond.name}: {cond.when}" for cond in adapter.get_conditions()]
            )

            # Determine endpoint info for display
            if traces_exporter == "console":
                endpoint_display = "console (stdout/stderr)"
            elif traces_exporter == "otlp" or traces_exporter.startswith("otlp"):
                endpoint_display = os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317")
            else:
                endpoint_display = traces_exporter

            log_startup_block(
                "configurator",
                "Clarvynn Governance Enabled Successfully",
                {
                    "Service": os.getenv("OTEL_SERVICE_NAME", "unknown"),
                    "Base sampling rate": f"{adapter.get_base_rate() * 100:.1f}%",
                    "CPL conditions": len(adapter.get_conditions()),
                    "Evaluation": "Tail-based (after request completes)",
                    "W3C TraceContext Level 2": "Enabled",
                    "Log filtering": "Enabled (correlated to trace sampling)",
                    "Flight Recorder": "Enabled (Conditional Log Buffering)",
                    "Traces Exporter": endpoint_display,
                },
            )

            if len(adapter.get_conditions()) > 0:
                logger.info("CPL Conditions:")
                for cond in adapter.get_conditions():
                    logger.info(f"   - {cond.name}: {cond.when}")

        except ImportError as e:
            logger.error(f"❌ Failed to import Clarvynn components: {e}")
            logger.error("   Is clarvynn package installed correctly?")

        except Exception as e:
            logger.error(f"❌ Clarvynn initialization failed: {e}")
            logger.error("   Application will continue without Clarvynn governance")
            import traceback

            traceback.print_exc()

    def _setup_log_filtering(self, adapter, span_processor, log_buffer):
        """
        Set up log filtering correlated to trace sampling.

        Logs associated with exported traces are kept.
        Logs associated with dropped traces are dropped.
        This ensures logs and traces are consistently sampled.

        Args:
            adapter: ProductionCPLAdapter for policy evaluation
            span_processor: ClarvynnSpanProcessor with trace decision cache
            log_buffer: SharedRingBuffer for Flight Recorder
        """
        try:
            import logging as python_logging

            from clarvynn.processors.clarvynn_log_processor import ClarvynnLogProcessor
            from opentelemetry import _logs as logs_api
            from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler

            logger.debug("Setting up log filtering")
            logger_provider = logs_api.get_logger_provider()
            logger.debug(f"LoggerProvider type: {type(logger_provider).__name__}")

            # If NoOp LoggerProvider, initialize SDK LoggerProvider
            if not isinstance(logger_provider, LoggerProvider):
                logger.debug("Initializing LoggerProvider (not enabled by auto-instrumentation)")
                logger_provider = LoggerProvider()
                logs_api.set_logger_provider(logger_provider)

            # Attach LoggingHandler to bridge Python logging → OTel logs
            logger.debug("Attaching LoggingHandler to Python logging")
            handler = LoggingHandler(level=python_logging.NOTSET, logger_provider=logger_provider)
            python_logging.getLogger().addHandler(handler)

            # Respect OTEL_LOGS_EXPORTER environment variable
            logs_exporter = os.getenv("OTEL_LOGS_EXPORTER", "otlp")
            logger.debug(f"OTEL_LOGS_EXPORTER={logs_exporter}")

            # Create the appropriate log exporter based on user configuration
            if logs_exporter == "console":
                from opentelemetry.sdk._logs.export import ConsoleLogExporter

                log_exporter = ConsoleLogExporter()
                logger.info("Using ConsoleLogExporter (OTEL_LOGS_EXPORTER=console)")
            elif logs_exporter == "otlp" or logs_exporter.startswith("otlp"):
                from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter

                otlp_endpoint = os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317")
                log_exporter = OTLPLogExporter(endpoint=otlp_endpoint)
                logger.info(f"Using OTLPLogExporter (endpoint: {otlp_endpoint})")
            elif logs_exporter == "none":
                # No log exporter - logs are filtered but not exported
                from opentelemetry.sdk._logs.export import LogExporter

                class NoOpLogExporter(LogExporter):
                    def export(self, batch):
                        return None

                    def shutdown(self):
                        pass

                    def force_flush(self, timeout_millis=30000):
                        return True

                log_exporter = NoOpLogExporter()
                logger.info("Using NoOpLogExporter (OTEL_LOGS_EXPORTER=none)")
            else:
                logger.warning(f"Unknown OTEL_LOGS_EXPORTER='{logs_exporter}', defaulting to OTLP")
                from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter

                otlp_endpoint = os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317")
                log_exporter = OTLPLogExporter(endpoint=otlp_endpoint)

            # Wrap with BatchLogExporterAdapter to ensure async export
            from clarvynn.processors.batch_exporter_adapter import BatchLogExporterAdapter

            async_log_exporter = BatchLogExporterAdapter(log_exporter)

            # Create Clarvynn log processor (shares trace decision cache and log buffer)
            logger.debug("Creating ClarvynnLogProcessor")
            log_processor = ClarvynnLogProcessor(
                adapter, async_log_exporter, span_processor, log_buffer=log_buffer
            )

            # Add log processor to logger provider
            logger.debug("Adding log processor to LoggerProvider")
            logger_provider.add_log_record_processor(log_processor)

            # CRITICAL: Pass the log exporter to the SpanProcessor so it can flush buffered logs
            span_processor.log_exporter = async_log_exporter

            logger.info("Log processor initialized")

        except ImportError as e:
            logger.warning(f"Could not set up log filtering: {e}")
            logger.warning("Logs will be exported without filtering")

        except Exception as e:
            logger.warning(f"Log filtering setup failed: {e}")
            logger.warning("Logs will be exported without filtering")
