"""
LogHandler.py
=============
"""

import logging
import os
import platform
import sys
from logging.config import dictConfig
from logging.handlers import RotatingFileHandler

import colors

from lib.Config import Configuration


class HostnameFilter(logging.Filter):
    hostname = platform.node()

    def filter(self, record):
        record.hostname = HostnameFilter.hostname
        return True


class HelperLogger(logging.Logger):
    """
    The HelperLogger is used by the application / gui as their logging class and *extends* the default python
    logger.logging class.

    This will separate the logging from the application / gui from that of the daemons.

    """

    # Logging state variables for fault tolerance; reference as "static" with 'HelperLogger.variableName'.
    testedLogFile = False
    testedUpdateLogFile = False
    failedLogFile = False
    failedUpdateLogFile = False

    config = Configuration()

    logDict = {
        "version": 1,
        "formatters": {
            "sysLogFormatter": {
                "format": "%(asctime)s - %(name)-8s - %(levelname)-8s - %(message)s"
            },
            "simpleFormatter": {
                "format": "%(asctime)s - %(name)-8s - %(levelname)-8s - %(message)s"
            },
        },
        "handlers": {
            "consoleHandler": {
                "class": "logging.StreamHandler",
                "level": "INFO",
                "stream": "ext://sys.stdout",
                "formatter": "simpleFormatter",
            }
        },
        "root": {"level": "DEBUG", "handlers": ["consoleHandler"]},
    }

    dictConfig(logDict)

    level_map = {
        "debug": "magenta",
        "info": "white",
        "warning": "yellow",
        "error": "red",
        "critical": "red",
    }

    def __init__(self, name, level=logging.NOTSET):
        super().__init__(name, level)

    def debug(self, msg, *args, **kwargs):
        """
        Log ‘msg % args’ with severity ‘DEBUG’ and color *MAGENTA.

        To pass exception information, use the keyword argument exc_info with a true value, e.g.

        logger.debug(“Houston, we have a %s”, “thorny problem”, exc_info=1)

        :param msg: Message to log
        :type msg: str
        """

        msg = colors.color("{}".format(msg), fg=HelperLogger.level_map["debug"])

        return super(HelperLogger, self).debug(msg, *args, **kwargs)

    def info(self, msg, *args, **kwargs):
        """
        Log ‘msg % args’ with severity ‘INFO’ and color *WHITE*.

        To pass exception information, use the keyword argument exc_info with a true value, e.g.

        logger.info(“Houston, we have a %s”, “interesting problem”, exc_info=1)

        :param msg: Message to log
        :type msg: str
        """

        msg = colors.color("{}".format(msg), fg=HelperLogger.level_map["info"])

        return super(HelperLogger, self).info(msg, *args, **kwargs)

    def warning(self, msg, *args, **kwargs):
        """
        Log ‘msg % args’ with severity ‘WARNING’ and color *YELLOW*.

        To pass exception information, use the keyword argument exc_info with a true value, e.g.

        logger.warning(“Houston, we have a %s”, “bit of a problem”, exc_info=1)

        :param msg: Message to log
        :type msg: str
        """

        msg = colors.color("{}".format(msg), fg=HelperLogger.level_map["warning"])

        return super(HelperLogger, self).warning(msg, *args, **kwargs)

    def error(self, msg, *args, **kwargs):
        """
        Log ‘msg % args’ with severity ‘ERROR’ and color *RED*.

        Store logged message to the database for dashboard alerting.

        To pass exception information, use the keyword argument exc_info with a true value, e.g.

        logger.error(“Houston, we have a %s”, “major problem”, exc_info=1)

        :param msg: Message to log
        :type msg: str
        """

        msg = colors.color("{}".format(msg), fg=HelperLogger.level_map["error"])

        return super(HelperLogger, self).error(msg, *args, **kwargs)

    def critical(self, msg, *args, **kwargs):
        """
        Log ‘msg % args’ with severity ‘CRITICAL’ and color *RED*.

        Store logged message to the database for dashboard alerting.

        To pass exception information, use the keyword argument exc_info with a true value, e.g.

        logger.critical(“Houston, we have a %s”, “hell of a problem”, exc_info=1)

        :param msg: Message to log
        :type msg: str
        """

        msg = colors.color("{}".format(msg), fg=HelperLogger.level_map["critical"])

        return super(HelperLogger, self).critical(msg, *args, **kwargs)

    @staticmethod
    def testLogging(log):
        """
        Static method for creating missing log directories and testing log operation.

        Returns True if logging is possible and False on any failure.

        :param log: Path to the log file to test/create.
        :type log: str (or os.PathLike object)
        """
        logFile = os.path.realpath(log)
        logPath = os.path.dirname(logFile)
        try:
            if not os.path.exists(logPath):
                os.makedirs(logPath)
            with open(logFile, "a"):
                os.utime(logFile, None)
        except:
            print(
                "Warning! Could not write log to {}. Disabling temporarily.".format(
                    logFile
                )
            )
            return False
        return True


class AppLogger(HelperLogger):
    def __init__(self, name, level=logging.NOTSET):
        formatter = logging.Formatter(
            "%(asctime)s - %(name)-8s - %(levelname)-8s - %(message)s"
        )

        root = logging.getLogger()

        root.setLevel(logging.DEBUG)

        root.handlers.clear()

        cli = logging.StreamHandler(stream=sys.stdout)
        cli.setFormatter(formatter)
        cli.setLevel(logging.INFO)

        root.addHandler(cli)

        super().__init__(name, level)

        if self.config.getLogging() and not HelperLogger.testedLogFile:
            if not HelperLogger.testLogging(self.config.getLogfile()):
                HelperLogger.failedLogFile = True
            HelperLogger.testedLogFile = True

        if self.config.getLogging() and not HelperLogger.failedLogFile:
            crf = RotatingFileHandler(
                filename=self.config.getLogfile(),
                maxBytes=self.config.getMaxLogSize(),
                backupCount=self.config.getBacklog(),
            )
            crf.setLevel(logging.DEBUG)
            crf.setFormatter(formatter)
            self.addHandler(crf)

        # syslog_formatter = logging.Formatter(
        #     "%(asctime)s [%(hostname)s] - %(name)-8s - %(levelname)-8s - %(message)s"
        # )
        #
        # if self.config.SYSLOG_ENABLE:
        #     syslog = SysLogHandler(
        #         address=(self.config.SYSLOG_SERVER, int(self.config.SYSLOG_PORT)),
        #         facility=SysLogHandler.LOG_LOCAL0,
        #     )
        #
        #     syslog.setLevel(logging.DEBUG)
        #     syslog.addFilter(HostnameFilter())
        #     syslog.setFormatter(syslog_formatter)
        #     self.addHandler(syslog)


class UpdateHandler(HelperLogger):
    def __init__(self, name, level=logging.NOTSET):
        super().__init__(name, level)

        formatter = logging.Formatter(
            "%(asctime)s - %(name)-8s - %(levelname)-8s - %(message)s"
        )

        if self.config.getLogging() and not HelperLogger.testedUpdateLogFile:
            if not HelperLogger.testLogging(self.config.getUpdateLogFile()):
                HelperLogger.failedUpdateLogFile = True
            HelperLogger.testedUpdateLogFile = True

        if self.config.getLogging() and not HelperLogger.failedUpdateLogFile:
            crf = RotatingFileHandler(
                filename=self.config.getUpdateLogFile(),
                maxBytes=self.config.getMaxLogSize(),
                backupCount=self.config.getBacklog(),
            )
            crf.setLevel(logging.DEBUG)
            crf.setFormatter(formatter)
            self.addHandler(crf)
