import logging
import threading
from logging import Logger
from typing import Optional, List, Type, Dict

from messageprocessor import MessageProcessorCallback

default_logger = logging.getLogger("MessageProcessor.LoggingProcessorCallback")


class LoggingProcessorCallback(MessageProcessorCallback):
    def __init__(self, logger, ignored_exceptions=None, log_level_exceptions=None):
        # type: (Logger, Optional[List[Type[Exception]]], Optional[Dict[Type[BaseException], int]]) -> None
        self.logger = logger if logger is not None else default_logger
        self.ignored_exceptions = {}
        if ignored_exceptions is not None:
            for ex in ignored_exceptions:
                self.ignored_exceptions[ex] = ex
        self.local_storage = threading.local()
        self.log_level_exceptions = log_level_exceptions or {}

    def process_begun(self, event_name, request_id, data):
        # type: (str, str, bytes) -> None
        self.local_storage.original_thread_name = threading.current_thread().name
        self._set_thread_name(event_name, request_id)
        self.logger.debug(b"Start handling. Data: {}" + data)

    def input_parsed(self, event_name, request_id, input_obj):
        self._set_thread_name(event_name, request_id)
        self.logger.debug(u"Input correctly parsed")

    def business_called(self, event_name, request_id, input_obj, output_obj):
        return

    def process_finished(self, event_name, request_id, input_obj, output_obj):
        self.logger.info(u"Successfully processed")
        self._restore_thread_name()

    def parse_exception(self, event_name, request_id, data, exception):
        self._log_exception(exception, u"Parse exception handling")
        self._restore_thread_name()

    def unhandled_exception(self, event_name, request_id, input_obj, exception):
        self._log_exception(exception, u"Unhandled exception handling")
        self._restore_thread_name()

    @staticmethod
    def _set_thread_name(event_name, request_id):
        threading.current_thread().name = "MessageProcessorThread${}${}".format(event_name, request_id)

    def _restore_thread_name(self):
        if hasattr(self.local_storage, "original_thread_name"):
            threading.current_thread().name = self.local_storage.original_thread_name

    def _log_exception(self, exception, message):
        type_exception = type(exception)

        if type_exception in self.ignored_exceptions:
            return

        level = self.log_level_exceptions.get(type_exception, logging.ERROR)
        self.logger.log(level, message, exc_info=True)
