import logging
import sys
import threading
import time
from contextlib import contextmanager

from parallels.core.thirdparties.colorama import Style
from parallels.core.utils.colors.log_colors import FORE_COLORS, BACK_COLORS, STYLES
from parallels.core.utils.common import default

thread_local = threading.local()


@contextmanager
def log_context(name):
    init_thread()
    if sys.version_info.major >= 3:
        thread_local.current_context.append(name)
    else:
        thread_local.current_context.append(unicode(name))
    thread_local.current_context_str = '/'.join(
        thread_local.current_context
    )
    try:
        yield
    except Exception as e:
        if not hasattr(e, 'context'):
            e.context = thread_local.current_context_str
        raise
    finally:
        thread_local.current_context.pop()
        thread_local.current_context_str = '/'.join(
            thread_local.current_context
        )


@contextmanager
def subscription_context(subscription_name):
    """Context manager inside which it is considered that we are working with specified subscription"""
    init_thread()
    if thread_local.current_subscription_name == subscription_name:
        yield
    else:
        previous_subscription_name = thread_local.current_subscription_name
        thread_local.current_subscription_name = subscription_name
        try:
            yield
        finally:
            thread_local.current_subscription_name = previous_subscription_name


def get_current_subscription_name():
    init_thread()
    return thread_local.current_subscription_name


class DebugLogFormatter(logging.Formatter):
    """Log formatter for migrator's debug log"""

    def __init__(self):
        super(DebugLogFormatter, self).__init__(
            fmt=(
                "%(asctime)s|%(levelname_brief)s|%(thread_name_brief)s|"
                "%(name_brief)s|%(subscription)s|%(context)s|%(message)s"
            ),
            datefmt='%Y-%m-%d_%H:%M:%S,%03d'
        )

    def format(self, record):
        result = logging.Formatter.format(self, record)
        if record.message:
            # dirty way to split formatted message by
            # head (time, level, thread name, etc) and tail (message text)
            parts = result.split(record.message)
            if len(parts) == 2:
                head = parts[0]
                result = result.replace('\n', '\n' + head)

        lines = result.split('\n')
        # add '[+]' for each start line, '[=]' for each continuation line
        result = '\n'.join(['+|' + lines[0]] + ['=|' + line for line in lines[1:]])

        return result

    def formatTime(self, record, datefmt=None):
        ct = self.converter(record.created)
        # use '_' as a delimiter between time and date so we could use cut/awk
        # without complex expressions, just splitting log line by spaces
        t = time.strftime("%Y-%m-%d_%H:%M:%S", ct)
        s = "%s,%03d" % (t, record.msecs)
        return s


class InfoLogFormatter(logging.Formatter):
    """Log formatter for migrator's info log and console output"""

    def __init__(self):
        fmt = "[%(asctime)s][%(levelname)s]%(subscription_context)s %(message)s"
        super(InfoLogFormatter, self).__init__(fmt)

    def format(self, record):
        # include subscription name if we are in subscription context,
        # otherwise do not include anything else,
        # even empty '[]', to keep info log and console output brief
        if record.subscription != '':
            record.subscription_context = ' [%s]' % record.subscription
        else:
            record.subscription_context = ''
        result = logging.Formatter.format(self, record)
        return result

    def formatTime(self, record, datefmt=None):
        ct = self.converter(record.created)
        return time.strftime("%Y-%m-%d %H:%M:%S", ct)


class ConsoleLogFormatter(logging.Formatter):
    """Log formatter for nice colored output to console

    Colored output is supported for both Windows and Linux are supported - thanks to "colorama" library.
    Colors configuration is available at the following module:
    parallels.core.utils.log_colors
    """
    def __init__(self):
        fmt = "[%(asctime)s][%(levelname)s]%(subscription_context)s %(message)s"
        super(ConsoleLogFormatter, self).__init__(fmt)

    def format(self, record):
        parts = {
            'time': "[%s]" % self.formatTime(record),
            'level': "[%s]" % record.levelname,
            'context': record.subscription_context,
            'message': record.message if not hasattr(record, 'colored') else record.colored
        }

        for part_name, part_value in parts.items():
            fore_color = self._get_fore_color(part_name, record.levelname)
            back_color = self._get_back_color(part_name, record.levelname)
            style = self._get_style(part_name, record.levelname)
            parts[part_name] = self._colorize(part_value, fore_color, back_color, style)

        return u"{time}{level}{context} {message}".format(**parts)

    @classmethod
    def _get_fore_color(cls, part_name, level):
        return cls._get_param(FORE_COLORS, part_name, level)

    @classmethod
    def _get_back_color(cls, part_name, level):
        return cls._get_param(BACK_COLORS, part_name, level)

    @classmethod
    def _get_style(cls, part_name, level):
        return cls._get_param(STYLES, part_name, level)

    @staticmethod
    def _get_param(param_values, part_name, level):
        if part_name not in param_values:
            return None
        else:
            if level.lower() in param_values[part_name]:
                return param_values[part_name][level.lower()]
            else:
                return param_values[part_name].get('default')

    @staticmethod
    def _colorize(text, fore_color, back_color, style):
        result = ""
        if style is not None:
            result += style
        if fore_color is not None:
            result += fore_color
        if back_color is not None:
            result += back_color
        result += text
        if fore_color is not None or back_color is not None or style is not None:
            result += Style.RESET_ALL
        return result

    def formatTime(self, record, datefmt=None):
        return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(record.created))


class IndentFilter(logging.Filter):
    """Add additional "context" information to every log record"""
    def filter(self, record):
        init_thread()
        record.context = thread_local.current_context_str
        record.subscription = default(thread_local.current_subscription_name, '')
        record.indent = len(thread_local.current_context) * '  '

        if record.threadName == 'MainThread':
            record.thread_name_brief = 'MT'
        elif record.threadName.startswith('SubscriptionThread'):
            record.thread_name_brief = 'ST%s' % (record.threadName[len('SubscriptionThread'):])
        else:
            record.thread_name_brief = record.threadName

        if record.levelname is not None and len(record.levelname) > 0:
            record.levelname_brief = record.levelname[0]
        else:
            record.levelname_brief = ''

        if record.name is not None and record.name.startswith('parallels.'):
            record.name_brief = record.name[len('parallels.'):]
        else:
            record.name_brief = record.name

        return True


def init_thread():
    current_context = getattr(thread_local, 'current_context', None)
    if current_context is None:
        thread_local.current_context = []
        thread_local.current_subscription_name = None

    current_context_str = getattr(thread_local, 'current_context_str', None)
    if current_context_str is None:
        thread_local.current_context_str = ''
