try:
    from inspect import getfullargspec as get_args
except ImportError:
    from inspect import getargspec as get_args

from typing import (
    Any,
    Optional,
    List,
    Dict,
    Callable,
)
from messagebus import MessageBus, Message, Event

from ._MessageProcessor import MessageProcessor
from ._MessageProcessorCallback import MessageProcessorCallback


class DefaultMessageProcessorExecutor(object):
    def __init__(self, event_processor, callbacks=None):
        # type: (MessageProcessor, Optional[List[MessageProcessorCallback]]) -> None
        self.event_processor = event_processor
        self.callbacks = callbacks or []

    def execute_processor(self, message_bus, message, event, data):
        # type: (MessageBus, Optional[Message], Optional[Event], Any) -> None
        event_name = self.event_processor.get_processor_name()
        request_id = self.event_processor.generate_unique_id()

        base_params = dict(
            data=data,
            event_type=event.event_type if event is not None else None,
            event=event,
            message=message,
        )
        self._process_begun(event_name, request_id, **base_params)
        input_obj = None
        data_parsed = False
        try:
            kwargs = self._get_function_necessary_parameters(
                func=self.event_processor.parse_data,
                **base_params
            )

            input_obj = self.event_processor.parse_data(**kwargs)
            data_parsed = True

            self._input_parsed(event_name, request_id, input_obj)
            event_name = self._reevaluate_event_name_after_input_is_parsed()

            output_obj = self.event_processor.call_business(input_obj)
            self._business_called(event_name, request_id, input_obj, output_obj)
            self.event_processor.format_response(message_bus, message, event, input_obj, output_obj)
            self._process_finished(event_name, request_id, input_obj, output_obj)
        except BaseException as ex: # noqa
            if data_parsed:
                self.event_processor.format_exception(message_bus, message, event, input_obj, ex)
                self._unhandled_exception(event_name, request_id, input_obj, ex)
            else:
                self.event_processor.format_parse_exception(message_bus, message, event, data, ex)
                self._parse_exception(event_name, request_id, data, ex)

    def _get_function_necessary_parameters(
        self,
        data,               # type: bytes
        event,              # type: Optional[Event]
        event_type,         # type: Optional[str]
        message,            # type: Optional[Message]
        func,               # type: Callable
    ):
        # type: (...) -> Dict[str, Any]

        full_kw_args = {
            "data": data,
            "event_type": event_type,
            "event": event,
            "message": message,
        }

        args_spec = get_args(func)

        if hasattr(args_spec, "varkw") and args_spec.varkw is not None:
            return full_kw_args
        if hasattr(args_spec, "varargs") and args_spec.varargs is not None:
            return full_kw_args

        kwargs = {}
        keys = ("data", "event", "event_type", "message")
        for key in keys:
            if key in args_spec.args:
                kwargs[key] = full_kw_args[key]
        return kwargs

    def _reevaluate_event_name_after_input_is_parsed(self,):
        return self.event_processor.get_processor_name()

    def _process_begun(self, event_name, request_id, **kwargs):
        # type: (str, str, Dict[str, Any]) -> None
        for callback in self.callbacks:
            func_kwargs = self._get_function_necessary_parameters(
                func=callback.process_begun,
                **kwargs
            )
            callback.process_begun(event_name, request_id, **func_kwargs)

    def _input_parsed(self, event_name, request_id, input_obj):
        # type: (str, str, Any) -> None
        for callback in self.callbacks:
            callback.input_parsed(event_name, request_id, input_obj)

    def _business_called(self, event_name, request_id, input_obj, output_obj):
        # type: (str, str, Any, Any) -> None
        for callback in self.callbacks:
            callback.business_called(event_name, request_id, input_obj, output_obj)

    def _process_finished(self, event_name, request_id, input_obj, output_obj):
        # type: (str, str, Any, Any) -> None
        for callback in self.callbacks:
            callback.process_finished(event_name, request_id, input_obj, output_obj)

    def _parse_exception(self, event_name, request_id, data, exception):
        # type: (str, str, bytes, BaseException) -> None
        for callback in self.callbacks:
            callback.parse_exception(event_name, request_id, data, exception)

    def _unhandled_exception(self, event_name, request_id, input_obj, exception):
        # type: (str, str, Any, BaseException) -> None
        for callback in self.callbacks:
            callback.unhandled_exception(event_name, request_id, input_obj, exception)
