import threading, time, logging, os, os.path
from logging import Logger, Handler
from collections import namedtuple
from typing import Mapping

# global logging configuration
LogSystemData = namedtuple('LogSystemData', ['plugin_formater', 'log_path', 'plugin_logs', "default_logger"])
# per thread logging configuration
LogPluginData = namedtuple('LogPluginData', ['is_plugin', 'plugin', 'plugin_path', 'endpoint_name', 'endpoint'])


# singleton log configuration
class LogginConfig:
    instance = None

    def __new__(cls):
        if not LogginConfig.instance:
            instance = super(LogginConfig, cls).__new__(cls)
            instance._system_data = None
            instance._local_data = threading.local()
            instance._local_data.data = LogPluginData(False, "", "", "", "")

            # key: string plugin name; value is int logging level
            instance.log_levels = {}
            LogginConfig.instance = instance

        return LogginConfig.instance

    @property
    def endpoint_name(self) -> str:
        return self.local_data.endpoint_name

    @property
    def endpoint(self) -> str:
        return self.local_data.endpoint

    @property
    def is_plugin(self) -> bool:
        if not self._system_data:
            return False
        return self.local_data.is_plugin

    @property
    def plugin(self) -> str:
        return self.local_data.plugin

    @property
    def plugin_path(self) -> str:
        return self.local_data.plugin_path

    @property
    def local_data(self) -> LogPluginData:
        return self._local_data.data

    @local_data.setter
    def local_data(self, value: LogPluginData):
        self._local_data.data = value

    @property
    def plugin_formater(self) -> logging.Formatter:
        return self._system_data.plugin_formater

    @property
    def log_path(self) -> str:
        return self._system_data.log_path

    @property
    def plugin_logs(self) -> Mapping[str, Logger]:
        return self._system_data.plugin_logs

    @property
    def system_data(self) -> LogSystemData:
        return self._system_data

    @system_data.setter
    def system_data(self, value: LogSystemData):
        self._system_data = value

    @property
    def default_logger(self):
        return self._system_data.default_logger


class EndpointFilter(logging.Filter):
    def filter(self, record):
        config = LogginConfig()
        record.endpointName = config.endpoint_name
        record.endpoint = config.endpoint
        return True


_bootstrap = threading.Thread._bootstrap


def _dt_bootstrap(t):
    LogginConfig().local_data = t._local_data
    _bootstrap(t)


_start = threading.Thread.start


def _dt_start(t):
    t._local_data = LogginConfig().local_data
    _start(t)


class PluginProxyLogger(Logger):

    def __init__(self, name):
        super().__init__(name)
        self.setLevel(logging.INFO)

    def _log(self, level, msg, args, exc_info=None, extra=None):
        config = LogginConfig()
        if config.is_plugin:
            plugin = config.plugin
            plugin_log = config.plugin_logs.get(plugin)
            if not plugin_log:
                plugin_log = Logger(plugin)
                config.plugin_logs[plugin] = plugin_log
                log_path = os.path.join(config.log_path, config.plugin_path)
                if not os.path.isdir(log_path):
                    os.mkdir(log_path)
                plugin_handler = logging.handlers.RotatingFileHandler(
                    filename=os.path.join(log_path, plugin + '.log'),
                    maxBytes=10000000,
                    backupCount=4)
                plugin_handler.setFormatter(config.plugin_formater)
                plugin_log.addFilter(EndpointFilter())
                plugin_log.addHandler(plugin_handler)
                plugin_log.setLevel(self.getEffectiveLevel())

            plugin_log._log(level, msg, args, exc_info, extra)
        else:
            config.default_logger._log(level, msg, args, exc_info, extra)

    def isEnabledFor(self, level):
        config = LogginConfig()
        if config.is_plugin:
            plugin_level = config.log_levels.get(config.plugin_path, None)
            if plugin_level:
                return level >= plugin_level
        return super().isEnabledFor(level)


def init_logging(handler: Handler, log_path: str, endpoint_support: bool):
    log_format = '%(thread)s(%(threadName)s) - [%(funcName)s] %(message)s'
    handler.setFormatter(logging.Formatter(log_format))
    default_logger = logging.getLogger()
    default_logger.setLevel(logging.INFO)
    default_logger.addHandler(handler)

    if endpoint_support:
        plugin_formater = logging.Formatter(
            '%(asctime)s UTC %(levelname)-7s [Python][%(endpoint)s][%(endpointName)s][%(thread)s][%(threadName)s] - [%(funcName)s] %(message)s')
        LogginConfig().system_data = LogSystemData(plugin_formater, log_path, {}, default_logger)
        logging.setLoggerClass(PluginProxyLogger)
        logging.Formatter.converter = time.gmtime
        logging.Formatter.default_msec_format = '%s.%03d'
        threading.Thread._bootstrap = _dt_bootstrap
        threading.Thread.start = _dt_start


def set_plugin_data(plugin, plugin_name: str, endpoint: str, endpoint_id: str):
    LogginConfig().local_data = LogPluginData(True, plugin.__name__, plugin_name, endpoint, endpoint_id)


def clear_plugin_data():
    LogginConfig().local_data = LogPluginData(False, '', '', '', '')

def get_log_level(plugin: str):
    config = LogginConfig()
    return config.log_levels.get(plugin, None)
    
def set_log_level(plugin: str, level: str):
    config = LogginConfig()
    int_level = logging._nameToLevel.get(level.upper())
    if not int_level:
        config.log_levels.pop(plugin, None)
    else:
        config.log_levels[plugin] = int_level
