# Copyright (C) 2009 Bruno Tarquini <btarquini AT gmail.com>
#
# This file is part of Gajim.
#
# SPDX-License-Identifier: GPL-3.0-only

import logging
import os
import sys
import time
from collections.abc import Callable
from datetime import datetime

from gajim.common import app
from gajim.common import configpaths
from gajim.common.i18n import _

LogCallback = Callable[[str], None]


def parseLogLevel(arg: str) -> int:
    '''
    Either numeric value or level name from logging module
    '''
    if arg.isdigit():
        return int(arg)
    if arg.isupper() and hasattr(logging, arg):
        return getattr(logging, arg)
    print(_('%s is not a valid loglevel') % repr(arg), file=sys.stderr)
    return 0


def parseLogTarget(arg: str) -> str:
    '''
    [gajim.]c.x.y  ->  gajim.c.x.y
    .other_logger  ->  other_logger
    <None>         ->  gajim
    '''
    arg = arg.lower()
    if not arg:
        return 'gajim'
    if arg.startswith('.'):
        return arg[1:]
    if arg.startswith('gajim'):
        return arg
    return 'gajim.' + arg


def parseAndSetLogLevels(arg: str) -> None:
    '''
    [=]LOGLEVEL     ->  gajim=LOGLEVEL
    gajim=LOGLEVEL  ->  gajim=LOGLEVEL
    .other=10       ->  other=10
    .=10            ->  <nothing>
    c.x.y=c.z=20    ->  gajim.c.x.y=20
                        gajim.c.z=20
    gajim=10,c.x=20 ->  gajim=10
                        gajim.c.x=20
    '''
    for directive in arg.split(','):
        directive = directive.strip()
        if not directive:
            continue
        if '=' not in directive:
            directive = '=' + directive
        targets, level = directive.rsplit('=', 1)
        level = parseLogLevel(level.strip())
        for target in targets.split('='):
            target = parseLogTarget(target.strip())
            if target:
                logging.getLogger(target).setLevel(level)
                print('Logger %s level set to %d' % (target, level),
                      file=sys.stderr)


class Colors:
    NONE = chr(27) + '[0m'
    BLACK = chr(27) + '[30m'
    RED = chr(27) + '[31m'
    GREEN = chr(27) + '[32m'
    BROWN = chr(27) + '[33m'
    BLUE = chr(27) + '[34m'
    MAGENTA = chr(27) + '[35m'
    CYAN = chr(27) + '[36m'
    LIGHT_GRAY = chr(27) + '[37m'
    DARK_GRAY = chr(27) + '[30;1m'
    BRIGHT_RED = chr(27) + '[31;1m'
    BRIGHT_GREEN = chr(27) + '[32;1m'
    YELLOW = chr(27) + '[33;1m'
    BRIGHT_BLUE = chr(27) + '[34;1m'
    PURPLE = chr(27) + '[35;1m'
    BRIGHT_CYAN = chr(27) + '[36;1m'
    WHITE = chr(27) + '[37;1m'


def colorize(text: str, color: str) -> str:
    return color + text + Colors.NONE


class LogConsoleHandler(logging.StreamHandler):  # pyright: ignore
    def __init__(self) -> None:
        super().__init__()  # pyright: ignore
        self._callback: LogCallback | None = None

    def emit(self, record: logging.LogRecord) -> None:
        if record.levelno < logging.WARNING:
            return

        msg = self.format(record)
        app.logging_records.append(msg)
        if self._callback is not None:
            self._callback(msg)

    def set_callback(self,
                     func: LogCallback | None
                     ) -> None:
        self._callback = func


logging.Formatter.default_msec_format = None

class FancyFormatter(logging.Formatter):
    '''
    An eye-candy formatter with Colors
    '''
    colors_mapping = {
        'DEBUG': Colors.BLUE,
        'INFO': Colors.GREEN,
        'WARNING': Colors.BROWN,
        'ERROR': Colors.RED,
        'CRITICAL': Colors.BRIGHT_RED,
    }

    def __init__(self,
                 fmt: str | None = None,
                 use_color: bool = False) -> None:
        logging.Formatter.__init__(self, fmt)
        self.use_color = use_color

    def format(self, record: logging.LogRecord) -> str:
        level = record.levelname
        record.levelname = '(%s)' % level[0]

        if self.use_color:
            c = FancyFormatter.colors_mapping.get(level, '')
            record.levelname = colorize(record.levelname, c)
            record.name = '%-25s' % colorize(record.name, Colors.CYAN)
        else:
            record.name = '%-25s|' % record.name

        return logging.Formatter.format(self, record)


def init() -> None:
    '''
    Initialize the logging system
    '''

    if app.get_debug_mode():
        _cleanup_debug_logs()
        _redirect_output()

    use_color = False
    if os.name != 'nt':
        use_color = sys.stderr.isatty()

    stream_handler = logging.StreamHandler()
    stream_handler.setFormatter(
        FancyFormatter(
            '%(asctime)s %(levelname)s %(name)-35s %(message)s',
            use_color
        )
    )

    root_log = logging.getLogger('gajim')
    root_log.setLevel(logging.WARNING)
    root_log.addHandler(_log_console_handler)
    root_log.addHandler(stream_handler)
    root_log.propagate = False

    root_log = logging.getLogger('nbxmpp')
    root_log.setLevel(logging.WARNING)
    root_log.addHandler(_log_console_handler)
    root_log.addHandler(stream_handler)
    root_log.propagate = False

    root_log = logging.getLogger('gnupg')
    root_log.setLevel(logging.WARNING)
    root_log.addHandler(_log_console_handler)
    root_log.addHandler(stream_handler)
    root_log.propagate = False

    root_log = logging.getLogger('omemo_dr')
    root_log.setLevel(logging.WARNING)
    root_log.addHandler(_log_console_handler)
    root_log.addHandler(stream_handler)
    root_log.propagate = False

    if os.environ.get('GAJIM_LEAK'):
        logging.getLogger('gajim.leak').setLevel(logging.DEBUG)

    # GAJIM_DEBUG is set only on Windows when using Gajim-Debug.exe
    # Gajim-Debug.exe shows a command line prompt and we want to redirect
    # log output to it
    if app.get_debug_mode() or os.environ.get('GAJIM_DEBUG', False):
        set_verbose()


def set_loglevels(loglevels_string: str) -> None:
    parseAndSetLogLevels(loglevels_string)


def set_verbose() -> None:
    parseAndSetLogLevels('gajim=DEBUG')
    parseAndSetLogLevels('.nbxmpp=INFO')
    parseAndSetLogLevels('.omemo_dr=DEBUG')


def set_quiet() -> None:
    parseAndSetLogLevels('gajim=CRITICAL')
    parseAndSetLogLevels('.nbxmpp=CRITICAL')
    parseAndSetLogLevels('.omemo_dr=CRITICAL')


def get_log_console_handler() -> LogConsoleHandler:
    return _log_console_handler


def _redirect_output() -> None:
    debug_folder = configpaths.get('DEBUG')
    date = datetime.today().strftime('%Y-%m-%d-%H%M%S')
    filename = '%s-debug.log' % date
    fd = open(debug_folder / filename, 'a', encoding='utf8')
    sys.stderr = sys.stdout = fd


def _cleanup_debug_logs() -> None:
    debug_folder = configpaths.get('DEBUG')
    debug_files = list(debug_folder.glob('*-debug.log*'))
    now = time.time()
    for file in debug_files:
        # Delete everything older than 3 days
        if file.stat().st_ctime < now - 259200:
            file.unlink()


_log_console_handler = LogConsoleHandler()
_log_console_handler.setFormatter(
    logging.Formatter(
        '%(asctime)s (%(levelname).1s) %(name)-35s | %(message)s\n',
    )
)


if __name__ == '__main__':
    init()

    set_loglevels('gajim.c=DEBUG,INFO')

    log = logging.getLogger('gajim')
    log.debug('debug')
    log.info('info')
    log.warning('warn')
    log.error('error')
    log.critical('critical')

    log = logging.getLogger('gajim.c.x.dispatcher')
    log.debug('debug')
    log.info('info')
    log.warning('warn')
    log.error('error')
    log.critical('critical')
