import inspect
import sys
from abc import ABCMeta


class SimpleStub(object):
    def __init__(
        self,
        interface,
        ret=None,
    ):

        if (
                not issubclass(type(interface), ABCMeta) and
                (not hasattr(interface, "__metaclass__") or interface.__metaclass__ != ABCMeta)
        ):
            raise ValueError("interface parameter is not an interface")

        for attr in dir(interface):
            prop = getattr(interface, attr)
            if not attr.startswith("__") and callable(prop):
                if not hasattr(prop, "__isabstractmethod__") and not self._is_prop_an_exception(prop):
                    raise ValueError("interface parameter have {} that is not abstract".format(attr))
        self.interface = interface
        self.ret = ret if ret is not None else {}

    def __getattribute__(self, name):
        try:
            return super(SimpleStub, self).__getattribute__(name)
        except AttributeError:
            pass
        interface = super(SimpleStub, self).__getattribute__("interface")
        if not hasattr(interface, name):
            raise AttributeError("Interface has no {} method".format(name))
        method = getattr(interface, name)

        def inner_func(*args, **kwargs):
            super(SimpleStub, self).__getattribute__("_check_parameters")(name, method, *args, **kwargs)
            return super(SimpleStub, self).__getattribute__("_stub_method")(method)

        return inner_func

    def _stub_method(self, method):
        ret = super(SimpleStub, self).__getattribute__("ret")

        if method not in ret:
            raise SimpleStub.UndefinedReturnValue()
        ret = ret[method]
        if inspect.isclass(ret) and issubclass(ret, Exception):
            raise ret()
        if isinstance(ret, Exception):
            raise ret
        return ret

    @staticmethod
    def _check_parameters(name, method, *args, **kwargs):
        if sys.version.startswith("3"):
            # noinspection PyUnresolvedReferences
            arg_spec = inspect.getfullargspec(method)
        else:
            # noinspection PyDeprecation
            arg_spec = inspect.getargspec(method)
        if len(arg_spec.args) - 1 != (len(args) + len(kwargs)):
            raise AttributeError("Interface method {} called with invalid number of parameters".format(name))
        for key in kwargs:
            if key not in arg_spec.args:
                message = "Interface method {} called with parameter {} not found in method".format(name, key)
                raise AttributeError(message)

    @staticmethod
    def _is_prop_an_exception(prop):
        return inspect.isclass(prop) and issubclass(prop, Exception)

    class UndefinedReturnValue(Exception):
        pass
