import json
import logging
import sys
from functools import partial

import structlog
import util
from pygments import highlight, lexers, formatters

import lib


class SentryAwareJSONRenderer(object):
    """
    rtype: `tuple` of ``(*args, **kw)``

    This should be last processor as its call method returning tuple
    If calls json dumps on the event dict, if any of the value is not json
    serializable, then "repr" on the object is called just like JsonRenderer Processor

    JsonRenderer processor as last processor was breaking the breaking visual sentry
    stack presentation, hence new renderer is created with rtype tuple
    """
    _default_serializer = partial(
        json.dumps,
        sort_keys=True,
        default=lambda x: repr(x)
    )

    def __init__(self, serializer=None, color=False):
        self._color = color
        self._dumps = serializer or self._default_serializer

    def __call__(self, _, __, event_dict):
        data = {}

        # exc_info is required by sentry,
        # but "format_exc_info" processor removes exc_info,
        # hence setting exc_info again
        if 'exception' in event_dict:
            data['exc_info'] = True

        # as structlog will call log.msg, setting 'msg' in dict
        data['msg'] = self._dumps(event_dict)

        if self._color and util.settings.is_log_coloring_enabled():
            data['msg'] = highlight(
                data['msg'], lexers.JsonLexer(), formatters.TerminalFormatter()
            )
        return (), data


def _remove_fields(logger, method_name, event_dict):
    """
    Remove fields from log based on key 'log_request_info'
    By default log_request_info is disabled

    So, if client uses -
    logger.info('test')   # request_info is skipped.
    logger.info('test', log_request_info=True)  # request_info is rendered
    logger.error('test') # request_info is skipped
    logger.error('test', log_request_info=True)

    This will add a key named, "skip_request_info" in log if request_info is skipped
    """
    request_fields_to_remove = ['body', 'query_params', 'headers', 'host']

    def _is_enabled_request_info_log():
        event_log_request_info = event_dict.pop('log_request_info', False)
        return event_log_request_info

    if not _is_enabled_request_info_log():
        for field in request_fields_to_remove:
            event_dict.pop(field, None)

        event_dict["skip_request_info"] = True

    return event_dict


def _add_exc_info(logger, method_name, event_dict):
    """
    Add exc_info by default for error level logs
    """
    if method_name == 'error' and event_dict.get('exc_info') is None:
        event_dict['exc_info'] = True

    return event_dict


def _add_caller_runtime_info(logger, method_name, event_dict):
    '''
    Add caller's file name, line no, function name
    '''
    if not event_dict.get('file'):
        try:
            caller_file_name, caller_line_no, caller_func_name = util.find_caller()
            event_dict['file'] = caller_file_name
            event_dict['line'] = caller_line_no
            event_dict['func'] = caller_func_name
        except:
            pass

    return event_dict


class BaseStructlogConfiguration(object):
    def __init__(self, *extra_processors):
        self.processors = [
            structlog.stdlib.filter_by_level,
            structlog.stdlib.add_logger_name,
            structlog.stdlib.add_log_level,
            structlog.stdlib.PositionalArgumentsFormatter(),
            structlog.processors.TimeStamper(fmt="iso"),
            structlog.processors.StackInfoRenderer(),
            _add_caller_runtime_info,
            _add_exc_info,
            structlog.processors.format_exc_info,
            structlog.processors.UnicodeDecoder(),
            _remove_fields,
        ]

        self.context_class = dict
        self.logger_factory = structlog.stdlib.LoggerFactory()
        self.wrapper_class = structlog.stdlib.BoundLogger
        self.cache_logger_on_first_use = True
        for processor in extra_processors or []:
            self.processors.append(processor)

    def __call__(self, *args, **kwargs):
        self.logger_factory = lib.LoggerFactory(
            level=kwargs.get('level', logging.INFO),
            name=kwargs.get('name'),
            stream=kwargs.get('stream', sys.stdout),
            fmt="%(message)s"
        )
        structlog.configure(
            processors=self.processors,
            context_class=self.context_class,
            logger_factory=self.logger_factory,
            wrapper_class=self.wrapper_class,
            cache_logger_on_first_use=self.cache_logger_on_first_use,
        )


class StructlogDevelopmentConfiguration(BaseStructlogConfiguration):
    def __init__(self):
        super(StructlogDevelopmentConfiguration, self).__init__(
            SentryAwareJSONRenderer(color=True)
        )


class StructlogProductionConfiguration(BaseStructlogConfiguration):
    def __init__(self):
        super(StructlogProductionConfiguration, self).__init__(
            SentryAwareJSONRenderer()
        )


production_cfg = StructlogProductionConfiguration()

development_cfg = StructlogDevelopmentConfiguration()
