# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Setup logging for the application.
"""

import logging
import logging.config
import sys
import typing
import warnings

from . import cfg

default_level = None


class LogEmitterProtocol(typing.Protocol):
    unit: str


class LogEmitter:
    """A mixin for App components that emit logs.

    Used as a simple base class for identifying components that have logs. Use
    the self.unit property to fetch systemd journal logs of the unit.
    """

    unit: str

    def get_logs(self: LogEmitterProtocol) -> dict[str, str]:
        from plinth.privileged import service as service_privileged
        return service_privileged.get_logs(self.unit)


class ColoredFormatter(logging.Formatter):
    """Print parts of log message in color."""
    codes = {
        'black': 30,
        'red': 31,
        'green': 32,
        'yellow': 33,
        'blue': 34,
        'magenta': 35,
        'cyan': 36,
        'white': 37,
        'bright_black': 90,
        'bright_red': 91,
        'bright_green': 92,
        'bright_yellow': 93,
        'bright_blue': 94,
        'bright_magenta': 95,
        'bright_cyan': 96,
        'bright_white': 97
    }

    level_colors = {
        'DEBUG': 'bright_black',
        'INFO': 'bright_white',
        'WARNING': 'bright_yellow',
        'ERROR': 'red',
        'CRITICAL': 'bright_red'
    }

    def wrap_color(self, string, color=None):
        """Return a string wrapped in terminal escape codes for coloring."""
        if not color:
            return string

        return '\x1b[{}m'.format(self.codes[color]) + string + '\x1b[0m'

    def format(self, record):
        """Format a record into a string"""
        record_name = '{:<20}'.format(record.name)
        record.colored_name = self.wrap_color(record_name, 'bright_blue')

        level_color = self.level_colors.get(record.levelname, None)
        level_name = '{:>8}'.format(record.levelname)
        record.colored_levelname = self.wrap_color(level_name, level_color)
        return super().format(record)


def _capture_warnings():
    """Capture all warnings include deprecation warnings."""
    # Capture all Python warnings such as deprecation warnings
    logging.captureWarnings(True)

    # Log all deprecation warnings when in develop mode
    if cfg.develop:
        warnings.filterwarnings('default', '', DeprecationWarning)
        warnings.filterwarnings('default', '', PendingDeprecationWarning)
        warnings.filterwarnings('default', '', ImportWarning)


def action_init(console: bool = False):
    """Initialize logging for action scripts."""
    _capture_warnings()

    configuration = get_configuration(
        syslog_identifier='freedombox-privileged')
    if console:
        configuration['root']['handlers'] = ['console']
    else:
        configuration['root']['handlers'] = ['journal']

    logging.config.dictConfig(configuration)


def init():
    """Setup the logging framework."""
    import cherrypy

    # Remove default handlers and let the log message propagate to root logger.
    for cherrypy_logger in [cherrypy.log.error_log, cherrypy.log.access_log]:
        for handler in list(cherrypy_logger.handlers):
            cherrypy_logger.removeHandler(handler)

    _capture_warnings()


def setup_cherrypy_static_directory(app):
    """Hush output from cherrypy static file request logging.

    Static file serving logs are hardly useful.
    """
    app.log.access_log.propagate = False
    app.log.error_log.propagate = False


def get_configuration(syslog_identifier='freedombox'):
    """Return the main python logging module configuration."""
    configuration = {
        'version': 1,
        'disable_existing_loggers': False,
        'formatters': {
            'color': {
                '()': 'plinth.log.ColoredFormatter',
                'format': '{colored_levelname} {colored_name} {message}',
                'style': '{'
            }
        },
        'handlers': {
            'console': {
                'class': 'logging.StreamHandler',
                'formatter': 'color'
            },
            'journal': {
                'class': 'systemd.journal.JournalHandler',
                'SYSLOG_IDENTIFIER': syslog_identifier,
            }
        },
        'root': {
            'handlers': ['console', 'journal'],
            'level': default_level or ('DEBUG' if cfg.develop else 'INFO')
        },
        'loggers': {
            'django.db.backends': {
                'level': 'INFO'  # Set to 'DEBUG' to log database queries
            },
            'axes': {
                'level': 'INFO'  # Too verbose during DEBUG
            }
        }
    }
    if not sys.stdin.isatty():
        # If running on a console, say due to user manually starting it, then
        # log everything to console. However, if we are running as a systemd
        # unit then no need to log to console. This allows us to capture
        # stdout/stderr in systemd unit without repeating the log messages.
        configuration['root']['handlers'].remove('console')

    return configuration
