# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations

import logging

from opentelemetry._logs import set_logger_provider
from opentelemetry.configuration._common import (
    _map_compression,
    _parse_headers,
    _parse_otlp_file_output_stream,
    load_entry_point,
)
from opentelemetry.configuration._exceptions import (
    ConfigurationError,
    MissingDependencyError,
)
from opentelemetry.configuration.models import (
    BatchLogRecordProcessor as BatchLogRecordProcessorConfig,
)
from opentelemetry.configuration.models import (
    ExperimentalOtlpFileExporter as ExperimentalOtlpFileExporterConfig,
)
from opentelemetry.configuration.models import (
    LoggerProvider as LoggerProviderConfig,
)
from opentelemetry.configuration.models import (
    LogRecordExporter as LogRecordExporterConfig,
)
from opentelemetry.configuration.models import (
    LogRecordProcessor as LogRecordProcessorConfig,
)
from opentelemetry.configuration.models import (
    OtlpGrpcExporter as OtlpGrpcExporterConfig,
)
from opentelemetry.configuration.models import (
    OtlpHttpExporter as OtlpHttpExporterConfig,
)
from opentelemetry.configuration.models import (
    SimpleLogRecordProcessor as SimpleLogRecordProcessorConfig,
)
from opentelemetry.sdk._logs import LoggerProvider
from opentelemetry.sdk._logs._internal.export import (
    BatchLogRecordProcessor,
    ConsoleLogRecordExporter,
    LogRecordExporter,
    SimpleLogRecordProcessor,
)
from opentelemetry.sdk.resources import Resource

_logger = logging.getLogger(__name__)

# BatchLogRecordProcessor defaults per OTel spec (milliseconds).
_DEFAULT_SCHEDULE_DELAY_MILLIS = 1000
_DEFAULT_EXPORT_TIMEOUT_MILLIS = 30000
_DEFAULT_MAX_QUEUE_SIZE = 2048
_DEFAULT_MAX_EXPORT_BATCH_SIZE = 512


def _create_console_log_exporter() -> ConsoleLogRecordExporter:
    """Create a ConsoleLogRecordExporter."""
    return ConsoleLogRecordExporter()


def _create_otlp_http_log_exporter(
    config: OtlpHttpExporterConfig,
) -> LogRecordExporter:
    """Create an OTLP HTTP log exporter from config."""
    try:
        # pylint: disable=import-outside-toplevel,no-name-in-module
        from opentelemetry.exporter.otlp.proto.http import (  # type: ignore[import-untyped]  # noqa: PLC0415
            Compression,
        )
        from opentelemetry.exporter.otlp.proto.http._log_exporter import (  # type: ignore[import-untyped]  # noqa: PLC0415
            OTLPLogExporter,
        )
    except ImportError as exc:
        raise MissingDependencyError(
            package="opentelemetry-exporter-otlp-proto-http",
            feature="otlp_http log exporter",
        ) from exc

    compression = _map_compression(config.compression, Compression, allow_deflate=True)
    headers = _parse_headers(config.headers, config.headers_list)
    timeout = (config.timeout / 1000.0) if config.timeout is not None else None

    return OTLPLogExporter(  # type: ignore[return-value]
        endpoint=config.endpoint,
        headers=headers,
        timeout=timeout,
        compression=compression,  # type: ignore[arg-type]
    )


def _create_otlp_grpc_log_exporter(
    config: OtlpGrpcExporterConfig,
) -> LogRecordExporter:
    """Create an OTLP gRPC log exporter from config."""
    try:
        # pylint: disable=import-outside-toplevel,no-name-in-module
        import grpc  # type: ignore[import-untyped]  # noqa: PLC0415

        from opentelemetry.exporter.otlp.proto.grpc._log_exporter import (  # type: ignore[import-untyped]  # noqa: PLC0415
            OTLPLogExporter,
        )
    except ImportError as exc:
        raise MissingDependencyError(
            package="opentelemetry-exporter-otlp-proto-grpc",
            feature="otlp_grpc log exporter",
        ) from exc

    compression = _map_compression(config.compression, grpc.Compression)
    headers = _parse_headers(config.headers, config.headers_list)
    timeout = (config.timeout / 1000.0) if config.timeout is not None else None

    return OTLPLogExporter(  # type: ignore[return-value]
        endpoint=config.endpoint,
        headers=headers,
        timeout=timeout,
        compression=compression,  # type: ignore[arg-type]
    )


def _create_otlp_file_development_log_exporter(
    config: ExperimentalOtlpFileExporterConfig,
) -> LogRecordExporter:
    """Create an OTLP file (JSON Lines) log exporter from config."""
    try:
        # pylint: disable=import-outside-toplevel,no-name-in-module
        from opentelemetry.exporter.otlp.json.file._log_exporter import (  # type: ignore[import-untyped]  # noqa: PLC0415
            FileLogExporter,
        )
    except ImportError as exc:
        raise ConfigurationError(
            "otlp_file_development log exporter requires 'opentelemetry-exporter-otlp-json-file'. "
            "Install it with: pip install opentelemetry-exporter-otlp-json-file"
        ) from exc

    path = _parse_otlp_file_output_stream(config.output_stream)
    return FileLogExporter(path) if path is not None else FileLogExporter()


_LOG_EXPORTER_REGISTRY: dict = {
    "otlp_http": _create_otlp_http_log_exporter,
    "otlp_grpc": _create_otlp_grpc_log_exporter,
    "console": lambda _: ConsoleLogRecordExporter(),
    "otlp_file_development": _create_otlp_file_development_log_exporter,
}


def _create_log_record_exporter(
    config: LogRecordExporterConfig,
) -> LogRecordExporter:
    """Create a log record exporter from config.

    Known exporter types are checked via typed fields on the LogRecordExporter
    dataclass. Unknown exporter names captured in additional_properties
    by the @_additional_properties decorator are loaded via the
    ``opentelemetry_logs_exporter`` entry point group.
    """
    for name, factory in _LOG_EXPORTER_REGISTRY.items():
        value = getattr(config, name, None)
        if value is not None:
            return factory(value)
    if config.additional_properties:
        name, plugin_config = next(iter(config.additional_properties.items()))
        return load_entry_point("opentelemetry_logs_exporter", name)(**(plugin_config or {}))
    raise ConfigurationError(
        "No exporter type specified in log record exporter config. "
        "Supported types: console, otlp_http, otlp_grpc, otlp_file_development."
    )


def _create_batch_log_record_processor(
    config: BatchLogRecordProcessorConfig,
) -> BatchLogRecordProcessor:
    """Create a BatchLogRecordProcessor from config.

    Passes explicit defaults to suppress OTEL_BLRP_* env var reading.
    """
    exporter = _create_log_record_exporter(config.exporter)
    schedule_delay = config.schedule_delay if config.schedule_delay is not None else _DEFAULT_SCHEDULE_DELAY_MILLIS
    export_timeout = config.export_timeout if config.export_timeout is not None else _DEFAULT_EXPORT_TIMEOUT_MILLIS
    max_queue_size = config.max_queue_size if config.max_queue_size is not None else _DEFAULT_MAX_QUEUE_SIZE
    max_export_batch_size = (
        config.max_export_batch_size if config.max_export_batch_size is not None else _DEFAULT_MAX_EXPORT_BATCH_SIZE
    )
    return BatchLogRecordProcessor(
        exporter=exporter,
        schedule_delay_millis=float(schedule_delay),
        export_timeout_millis=float(export_timeout),
        max_queue_size=max_queue_size,
        max_export_batch_size=max_export_batch_size,
    )


def _create_simple_log_record_processor(
    config: SimpleLogRecordProcessorConfig,
) -> SimpleLogRecordProcessor:
    """Create a SimpleLogRecordProcessor from config."""
    exporter = _create_log_record_exporter(config.exporter)
    return SimpleLogRecordProcessor(exporter)


def _create_log_record_processor(
    config: LogRecordProcessorConfig,
) -> BatchLogRecordProcessor | SimpleLogRecordProcessor:
    """Create a log record processor from config."""
    if config.batch is not None:
        return _create_batch_log_record_processor(config.batch)
    if config.simple is not None:
        return _create_simple_log_record_processor(config.simple)
    raise ConfigurationError(
        "No processor type specified in log record processor config. Supported types: batch, simple."
    )


def create_logger_provider(
    config: LoggerProviderConfig | None,
    resource: Resource | None = None,
) -> LoggerProvider:
    """Create an SDK LoggerProvider from declarative config.

    Does NOT read OTEL_BLRP_* or other env vars for values explicitly
    controlled by the config. Absent config values use OTel spec defaults.

    Args:
        config: LoggerProvider config from the parsed config file, or None.
        resource: Resource to attach to the provider.

    Returns:
        A configured LoggerProvider.
    """
    provider = LoggerProvider(resource=resource)

    if config is None:
        return provider

    if config.limits is not None:
        _logger.warning(
            "log_record_limits are specified in config but are not supported "
            "by the Python SDK LoggerProvider constructor; limits will be ignored."
        )

    for processor_config in config.processors:
        provider.add_log_record_processor(_create_log_record_processor(processor_config))

    return provider


def configure_logger_provider(
    config: LoggerProviderConfig | None,
    resource: Resource | None = None,
) -> None:
    """Configure the global LoggerProvider from declarative config.

    When config is None (logger_provider section absent from config file),
    the global is not set — matching Java/JS SDK behavior.

    Args:
        config: LoggerProvider config from the parsed config file, or None.
        resource: Resource to attach to the provider.
    """
    if config is None:
        return
    set_logger_provider(create_logger_provider(config, resource))
