# coding=utf-8

import json
import logging
import os
import traceback
import sys

from datetime import datetime

from logging import Handler
from logging import Formatter

from redis import StrictRedis

from sherlog.config import SherlogClientConfig


LVL_MAP = {
    'debug': logging.DEBUG,
    'info': logging.INFO,
    'warning': logging.WARNING,
    'error': logging.ERROR,
    'critical': logging.CRITICAL
}


class SherlogHandler(Handler):
    """Handler meant to use REDIS lpush rpop pattern
    """
    def __init__(self, redis, key):
        self.redis = redis
        self.key = key
        super(SherlogHandler, self).__init__()

    def emit(self, record):
        try:
            log_entry = self.format(record)
            json_entry = json.dumps(log_entry)
            self.redis.lpush(self.key, json_entry)
        except Exception as e:
            print(e, traceback.format_exc())  # TODO logging


class SherlogFormatter(Formatter):
    """Sherlog sherlog formatter. Performs log event pre processing.
    """
    def __init__(self, app):
        self.app = app
        super(SherlogFormatter, self).__init__()

    @staticmethod
    def format_message(record):
        try:
            # TODO this might be an overhead (compare Python 2 / 3 logging)
            return str(record.getMessage())  # % record.args
        except UnicodeEncodeError:
            return record.getMessage()  # % record.args

    def format(self, record):
        ts = datetime.fromtimestamp(record.created).isoformat()

        data = {
            'lvl': record.levelname.lower(),
            'ts': ts,
            'ts_rel': round(record.relativeCreated / 1000, 3),  # seconds
            'app': self.app,
            'pid': record.process,
            'pname': record.processName,
            'tid': record.thread,
            'tname': record.threadName,
            'file': record.filename,
            'path': record.pathname,
            'module': record.module,
            'logger': record.name,
            'function': record.funcName,
            'line': record.lineno,
            'message': self.format_message(record),
            'ex_type': None,
            'ex_repr': None,
            'stack': None
        }

        if record.exc_info:
            data['ex_type'] = record.exc_info[0].__name__,  # type of exception (class name)
            data['ex_repr'] = self.formatException(record.exc_info)
        if getattr(record, 'stack_info', None):  # python 2.7 LogRecord has no stack_info attr
            data['stack'] = self.formatStack(record.stack_info)

        return data


def set_logger(config, name=None, extra_handlers=None):  # TODO more styles
    """Initializes sherlog logger configuration"""
    logger = logging.getLogger(name)

    if [h for h in logger.handlers if type(h) == SherlogHandler]:
        raise ValueError('Logger with name {} is already initialized by Sherlog.'.format(name))

    if isinstance(config, dict):
        config = SherlogClientConfig(dict_config=config)
    elif isinstance(config, str):
        ext = os.path.splitext(config)[1]
        if ext.lower() == '.yaml':
            config = SherlogClientConfig.from_yaml(config)
        elif ext.lower() == '.json':
            config = SherlogClientConfig.from_json(config)
        else:
            raise ValueError('Unsupported config file extension: {}'.format(ext))
    else:
        raise ValueError('Sherlog config should be either Python Dict, .yaml file or .json file')

    app = config.app

    logger_level = LVL_MAP[config.level.lower()]
    logger.setLevel(logger_level)

    if not config.dummy:
        redis = StrictRedis(host=config.redis.host, port=config.redis.port)
        handler = SherlogHandler(redis, config.redis.key)
        formatter = SherlogFormatter(app=app)
        handler.setFormatter(formatter)
        logger.addHandler(handler)

    if config.stdout:
        # enables stdout logging
        stdout_handler = logging.StreamHandler(sys.stdout)
        stdout_handler.setLevel(logger_level)
        stdout_handler.setFormatter(
            Formatter('%(asctime)s - %(module)s - %(levelname)s - %(message)s')  # TODO allow opt.
        )
        logger.addHandler(stdout_handler)

    if isinstance(extra_handlers, list):
        for h in extra_handlers:
            logger.addHandler(h)

    return logger


def get_logger(name=None):
    """Returns configured sherlog instance
    """
    logger = logging.getLogger(name)

    if not [h for h in logger.handlers if type(h) == SherlogHandler]:
        raise LookupError('Could not find Sherlog logger with name {}'.format(name))

    return logger
