import logging
from opentelemetry._logs import set_logger_provider, get_logger_provider
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.sdk.resources import Resource
from .config import ObservabilityConfig
from .filters import NoisyLoggerFilter
from .log_processor import TraceContextLogProcessor

logger = logging.getLogger(__name__)

_logger_provider = None

def setup_logging() -> bool:
    global _logger_provider

    if not ObservabilityConfig.OTEL_ENABLED or not ObservabilityConfig.OTEL_EXPORTER_OTLP_ENDPOINT:
        return False

    try:
        resource = Resource.create({
            "service.name": ObservabilityConfig.OTEL_SERVICE_NAME,
            "service.version": ObservabilityConfig.OTEL_SERVICE_VERSION,
            "deployment.environment": ObservabilityConfig.OTEL_DEPLOYMENT_ENVIRONMENT,
        })

        # Only reuse existing provider if our module-level reference is still set.
        # _reset_otel_providers() clears _logger_provider before Celery re-init,
        # so this check correctly forces a new provider after fork().
        if _logger_provider is not None:
            return True

        _logger_provider = LoggerProvider(resource=resource)

        headers = ObservabilityConfig.parse_headers()

        if ObservabilityConfig.OTEL_EXPORTER_OTLP_PROTOCOL == "grpc":
            from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
            endpoint = ObservabilityConfig.OTEL_EXPORTER_OTLP_ENDPOINT
            insecure = not ObservabilityConfig.is_secure_endpoint()
            exporter = OTLPLogExporter(
                endpoint=endpoint,
                headers=headers if headers else None,
                insecure=insecure,
            )
        else:
            from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
            endpoint = ObservabilityConfig.OTEL_EXPORTER_OTLP_ENDPOINT
            if not endpoint.endswith("/v1/logs"):
                endpoint = endpoint.rstrip("/") + "/v1/logs"
            exporter = OTLPLogExporter(
                endpoint=endpoint,
                headers=headers if headers else None,
            )

        # Create a batch processor with the exporter
        batch_processor = BatchLogRecordProcessor(exporter)

        # Wrap it with our trace context processor
        # The trace context processor will inject trace_id before batching
        trace_processor = TraceContextLogProcessor()

        # Add both processors - trace context first, then batch
        _logger_provider.add_log_record_processor(trace_processor)
        _logger_provider.add_log_record_processor(batch_processor)
        set_logger_provider(_logger_provider)

        logger.info("OpenTelemetry logging configured with trace context injection")
        return True

    except Exception as e:
        logger.error(f"Failed to setup logging: {e}", exc_info=True)
        return False


def attach_log_handlers():
    from .trace_filter import TraceContextFilter

    if not _logger_provider:
        return

    root_logger = logging.getLogger()

    # Remove any existing OTel handlers — they may reference a stale provider
    # (e.g., after fork, the old provider's batch threads are dead).
    # Always replace with a handler using the current _logger_provider.
    root_logger.handlers = [h for h in root_logger.handlers if not isinstance(h, LoggingHandler)]

    handler = LoggingHandler(logger_provider=_logger_provider)
    handler.addFilter(NoisyLoggerFilter())
    root_logger.addHandler(handler)
    # Don't override root logger level — respect the level set by Django's LOGGING config.
    # Setting DEBUG here causes noisy botocore/boto3 logs to flood the console handler.
    if root_logger.level == logging.NOTSET:
        root_logger.setLevel(logging.INFO)

    # Add trace context filter to root logger (only once)
    if not any(isinstance(f, TraceContextFilter) for f in root_logger.filters):
        trace_filter = TraceContextFilter()
        root_logger.addFilter(trace_filter)

    framework_loggers = [
        "django",
        "django.request",
        "django.db.backends",
        "fastapi",
        "uvicorn",
        "uvicorn.access",
        "uvicorn.error",
        "celery",
        "celery.task",
        "celery.worker",
        "integration-logger",
        "integration",
        "integration.xero",
        "integration.xero.background_tasks",
        "integration.xero.background_tasks.sync_cost_code",
        "integration.xero.background_tasks.sync_suppliers",
        "integration.xero.background_tasks.sync_purchase_order",
        "integration.myob",
        "application",
        "project",
        "project.tasks",
        "project.entrypoints",
        "varicon",
        "api",
        "api.management",
        "api.management.commands",
        "api.management.commands.sqs_worker",
    ]

    for name in framework_loggers:
        try:
            lg = logging.getLogger(name)
            # Remove any stale or accidental OTel handlers from child loggers
            # to prevent duplicate exports when propagate is True
            lg.handlers = [h for h in lg.handlers if not isinstance(h, LoggingHandler)]
            
            # Add trace context filter (only once)
            if not any(isinstance(f, TraceContextFilter) for f in lg.filters):
                lg.addFilter(TraceContextFilter())
            lg.propagate = True
            if lg.level == logging.NOTSET or lg.level > logging.INFO:
                lg.setLevel(logging.INFO)
        except Exception:
            pass

    manager = logging.Logger.manager
    for logger_name in list(manager.loggerDict.keys()):
        try:
            lg = manager.loggerDict[logger_name]
            if isinstance(lg, logging.Logger):
                lg.propagate = True
        except Exception:
            pass


def ensure_all_logs_captured():
    """Ensure all logs are captured by OpenTelemetry.

    Adds a LoggingHandler to the root logger if one is not already present,
    so that all application logs are forwarded to the OTel log pipeline.
    """
    provider = get_logger_provider()
    if not provider or not isinstance(provider, LoggerProvider):
        return

    root_logger = logging.getLogger()
    if any(isinstance(h, LoggingHandler) for h in root_logger.handlers):
        return

    handler = LoggingHandler(logger_provider=provider)
    handler.addFilter(NoisyLoggerFilter())
    root_logger.addHandler(handler)
    logger.debug("Added OTel LoggingHandler to root logger via ensure_all_logs_captured()")

