"""OpenTelemetry observability SDK for Python — structured logging, distributed tracing, and auto-instrumentation."""

import importlib
import logging
from typing import Any, Dict, List, Optional

from .handler import OTelLoggingHandler
from .initializer import OTelInitializer
from .instrumentation_config import InitResult, InstrumentationConfig
from .log_formatter import LogFormatter
from .log_record import DEFAULT_SERVICE_NAME, LogRecord
from .otlp_exporter import ExporterConfig, OTLPExporter
from .resource_provider import ResourceConfig, ResourceProvider
from .severity_mapper import SeverityMapper
from .trace_context import TraceContext, TraceContextExtractor

__version__ = "0.4.1"

__all__ = [
    "LogRecord",
    "LogFormatter",
    "OTelLoggingHandler",
    "OTelInitializer",
    "InstrumentationConfig",
    "InitResult",
    "OTLPExporter",
    "ExporterConfig",
    "SeverityMapper",
    "TraceContext",
    "TraceContextExtractor",
    "ResourceConfig",
    "ResourceProvider",
    "setup",
    "auto_instrument",
]


def setup(
    service_name: str,
    *,
    version: Optional[str] = None,
    environment: Optional[str] = None,
    endpoint: Optional[str] = None,
    protocol: Optional[str] = None,
    logger_name: Optional[str] = None,
    log_level: int = logging.DEBUG,
    custom_attributes: Optional[Dict[str, Any]] = None,
) -> InitResult:
    """One-line setup for OTel-compliant logging and optional tracing.

    This is the recommended entry point for most applications. It configures
    structured JSON logging and (when an endpoint is provided) distributed
    tracing in a single call.

    The handler is automatically added to the specified logger (or the root
    logger). The returned ``InitResult`` gives access to the tracer and
    shutdown function.

    Args:
        service_name: Your service name (required). Appears in every log
            record and trace span as ``service.name``.
        version: Service version string (e.g. ``"1.0.0"``).
        environment: Deployment environment (e.g. ``"production"``).
        endpoint: OTLP endpoint for traces (e.g. ``"localhost:4317"``).
            When ``None``, tracing is disabled (no-op tracer). Env var
            ``OTEL_EXPORTER_OTLP_ENDPOINT`` is also checked.
        protocol: Transport protocol — ``"grpc"`` or ``"http"``.
        logger_name: Logger to attach the handler to. ``None`` uses the
            root logger.
        log_level: Minimum log level (default ``logging.DEBUG``).
        custom_attributes: Additional resource attributes
            (e.g. ``{"cloud.region": "us-east-1"}``).

    Returns:
        An :class:`InitResult` with ``.handler``, ``.tracer``, and
        ``.shutdown``.

    Example::

        import otel_log

        result = otel_log.setup("my-service", version="1.0.0")
        # Logging is now configured. Use the standard library:
        import logging
        logging.info("Hello from my-service")

        # Tracer is always safe to use (no-op when no endpoint):
        with result.tracer.start_as_current_span("my-op"):
            logging.info("Inside a span — TraceId auto-injected")

        # On shutdown:
        result.shutdown()
    """
    resource_config = ResourceConfig(
        service_name=service_name,
        service_version=version,
        deployment_environment=environment,
        custom_attributes=custom_attributes,
    )

    instr_config = None
    if endpoint or protocol:
        kwargs: Dict[str, Any] = {}
        if endpoint:
            kwargs["endpoint"] = endpoint
        if protocol:
            kwargs["protocol"] = protocol
        instr_config = InstrumentationConfig(**kwargs)

    result = OTelInitializer.initialize(
        resource_config=resource_config,
        instrumentation_config=instr_config,
    )

    # Auto-attach handler to the specified logger
    target_logger = logging.getLogger(logger_name)
    target_logger.addHandler(result.handler)
    if target_logger.level == logging.NOTSET or target_logger.level > log_level:
        target_logger.setLevel(log_level)

    return result


_logger = logging.getLogger(__name__)


def auto_instrument(
    *,
    app: Optional[Any] = None,
    exclude: Optional[List[str]] = None,
) -> List[str]:
    """Discover and activate all installed OpenTelemetry instrumentors.

    Iterates over the instrumentor registry, attempts to import each one,
    and activates it if the package is installed. Missing packages are
    silently skipped. Errors during activation are logged and do not
    prevent other instrumentors from activating.

    Args:
        app: Optional application instance for framework instrumentors
            that require it (e.g. Flask, FastAPI, Falcon). Instrumentors
            with ``needs_app=True`` receive this; if ``app`` is ``None``
            they are skipped.
        exclude: Optional list of instrumentor names to skip
            (e.g. ``["django", "redis"]``).

    Returns:
        List of instrumentor names that were successfully activated.

    Example::

        import otel_log

        result = otel_log.setup("my-service")
        activated = otel_log.auto_instrument(app=app)
        # activated might be ["flask", "requests", "redis"]
    """
    from .instrumentors import get_all_instrumentors

    exclude_set = set(exclude) if exclude else set()
    activated: List[str] = []

    for name, entry in get_all_instrumentors().items():
        if name in exclude_set:
            _logger.debug("Skipping instrumentor %s (excluded)", name)
            continue

        try:
            mod = importlib.import_module(entry.module)
            cls = getattr(mod, entry.class_name)
        except (ImportError, AttributeError):
            _logger.debug("Skipping instrumentor %s (package not installed)", name)
            continue

        if entry.needs_app and app is None:
            _logger.debug("Skipping instrumentor %s (needs app but app is None)", name)
            continue

        try:
            instrumentor = cls()
            if entry.needs_app:
                instrumentor.instrument_app(app)
            else:
                instrumentor.instrument()
            activated.append(name)
            _logger.info("Activated instrumentor: %s", name)
        except Exception as exc:
            _logger.warning("Failed to activate instrumentor %s: %s", name, exc)

    return activated
