import logging
from os import path
from typing import Optional

from ddtrace.internal.logger import get_logger
from ddtrace.internal.telemetry import get_config
from ddtrace.internal.utils.formats import asbool


log = get_logger(__name__)

DD_LOG_FORMAT = "%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(lineno)d] {}- %(message)s".format(
    "[dd.service=%(dd.service)s dd.env=%(dd.env)s dd.version=%(dd.version)s"
    " dd.trace_id=%(dd.trace_id)s dd.span_id=%(dd.span_id)s] "
)

DEFAULT_FILE_SIZE_BYTES = 15 << 20  # 15 MB


def configure_ddtrace_logger() -> None:
    """Configures ddtrace log levels and file paths.

    Customization is possible with the environment variables:
        ``DD_TRACE_DEBUG``, ``DD_TRACE_LOG_LEVEL``, ``DD_TRACE_LOG_FILE_LEVEL``, and ``DD_TRACE_LOG_FILE``

    By default, when none of the settings have been changed, ddtrace loggers
        inherit from the root logger in the logging module and no logs are written to a file.

    When DD_TRACE_DEBUG has been enabled:
        - Logs are propagated up so that they appear in the application logs if a file path wasn't provided
        - Logs are routed to a file when DD_TRACE_LOG_FILE is specified, using the log level in DD_TRACE_LOG_FILE_LEVEL.
        - Child loggers inherit from the parent ddtrace logger
        - Takes precedence over DD_TRACE_LOG_LEVEL

    When DD_TRACE_LOG_LEVEL is set to NOTSET, DEBUG, INFO, WARNING, ERROR, or CRITICAL:
        - The ddtrace logger level will be set to the specified value based on https://docs.python.org/3/library/logging.html#levels
        - When NOTSET is used, getEffectiveLevel() returns the parent logger's level (inherits from root)
        - Overrides the default root logger behavior
        - DD_TRACE_DEBUG takes precedence

    Note(s):
        1) The ddtrace-run logs under commands/ddtrace_run do not follow DD_TRACE_LOG_FILE if DD_TRACE_DEBUG is enabled.
            This is because ddtrace-run calls ``logging.basicConfig()`` when DD_TRACE_DEBUG is enabled, so
            this configuration is not applied.
        2) Python 2: If the application is using DD_TRACE_DEBUG=true, logging will need to be configured,
            ie: ``logging.basicConfig()``.

    """
    ddtrace_logger = logging.getLogger("ddtrace")
    if get_config("DD_TRACE_LOG_STREAM_HANDLER", True, asbool):
        ddtrace_logger.addHandler(logging.StreamHandler())

    _configure_ddtrace_debug_logger(ddtrace_logger)
    _configure_ddtrace_file_logger(ddtrace_logger)
    # Calling _configure_ddtrace_native_logger should come after Python logging has been configured.
    _configure_ddtrace_native_logger()


def _configure_ddtrace_debug_logger(logger):
    """Configure the ddtrace logger level based on DD_TRACE_DEBUG and DD_TRACE_LOG_LEVEL.

    The priority is DD_TRACE_DEBUG, then DD_TRACE_LOG_LEVEL

    Note: DD_TRACE_DEBUG=true implies a log level of DEBUG, but DD_TRACE_LOG_LEVEL=DEBUG
    does not imply DD_TRACE_DEBUG is enabled.
    """
    # reading the values of the two possible debug settings so they can be displayed in telemetry/debug logs
    trace_debug = get_config("DD_TRACE_DEBUG", False, asbool)
    log_level = get_config("DD_TRACE_LOG_LEVEL")

    if trace_debug:
        logger.setLevel(logging.DEBUG)
        return

    if log_level is not None:
        log_level_upper = log_level.upper()
        try:
            log_level_value = getattr(logging, log_level_upper)
            logger.setLevel(log_level_value)
        except AttributeError:
            log.warning(
                "DD_TRACE_LOG_LEVEL is invalid (%s). Default log level will be used. "
                "Must be NOTSET/DEBUG/INFO/WARNING/ERROR/CRITICAL.",
                log_level_upper,
            )


def _configure_ddtrace_file_logger(logger):
    log_file_level = get_config("DD_TRACE_LOG_FILE_LEVEL", "DEBUG").upper()
    try:
        file_log_level_value = getattr(logging, log_file_level)
    except AttributeError:
        raise ValueError(
            "DD_TRACE_LOG_FILE_LEVEL is invalid. Log level must be CRITICAL/ERROR/WARNING/INFO/DEBUG.",
            log_file_level,
        )
    max_file_bytes = get_config("DD_TRACE_LOG_FILE_SIZE_BYTES", DEFAULT_FILE_SIZE_BYTES, int)
    log_path = get_config("DD_TRACE_LOG_FILE")
    _add_file_handler(logger=logger, log_path=log_path, log_level=file_log_level_value, max_file_bytes=max_file_bytes)


def _add_file_handler(
    logger: logging.Logger,
    log_path: Optional[str],
    log_level: int,
    handler_name: Optional[str] = None,
    max_file_bytes: int = DEFAULT_FILE_SIZE_BYTES,
    formatter: Optional[logging.Formatter] = None,
):
    ddtrace_file_handler = None
    if log_path is not None:
        log_path = path.abspath(log_path)
        num_backup = 1
        from logging.handlers import RotatingFileHandler

        ddtrace_file_handler = RotatingFileHandler(
            filename=log_path, mode="a", maxBytes=max_file_bytes, backupCount=num_backup
        )
        if formatter is None:
            log_format = "%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(lineno)d] - %(message)s"
            log_formatter = logging.Formatter(log_format)
        else:
            log_formatter = formatter
        ddtrace_file_handler.setLevel(log_level)
        ddtrace_file_handler.setFormatter(log_formatter)
        if handler_name:
            ddtrace_file_handler.set_name(handler_name)
        logger.addHandler(ddtrace_file_handler)
        logger.debug("ddtrace logs will be routed to %s", log_path)
    return ddtrace_file_handler


def _configure_ddtrace_native_logger():
    try:
        from ddtrace.internal.native._native import logger
        from ddtrace.internal.settings._config import config

        if config._trace_writer_native:
            backend = get_config("_DD_NATIVE_LOGGING_BACKEND")
            if not backend:
                return
            kwargs = {"output": backend}
            if backend == "file":
                kwargs["path"] = get_config("_DD_NATIVE_LOGGING_FILE_PATH", "native.log", report_telemetry=True)
                kwargs["max_size_bytes"] = get_config(
                    "_DD_NATIVE_LOGGING_FILE_SIZE_BYTES", 4096, int, report_telemetry=True
                )
                kwargs["max_files"] = get_config("_DD_NATIVE_LOGGING_FILE_ROTATION_LEN", 1, int, report_telemetry=True)

            logger.configure(**kwargs)
            logger.set_log_level(get_config("_DD_NATIVE_LOGGING_LOG_LEVEL", "warning", report_telemetry=True))
    except Exception:
        log.warning("Failed to initialize native logger", exc_info=True)
