import inspect
import sys
from typing import Any, Dict

from ._SpyCall import SpyCall
from ._SimpleStub import SimpleStub


class SimpleSpy(SimpleStub):
    def __init__(self, interface, ret, deep_clone: bool = False):
        super(SimpleSpy, self).__init__(interface, ret)
        self.deep_clone = deep_clone
        self.calls = {}  # type: Dict[Any, SpyCall]

    def called(self, method):
        if method not in self.calls:
            return False
        return self.calls[method].called

    def called_with(self, method, *args, **kwargs):
        if method not in self.calls:
            return False

        arg_spec = self._get_arg_spec(method)

        all_kwargs = self._transform_args_and_kwargs_in_dict(arg_spec.args, *args, **kwargs)
        if len(args) + len(kwargs) > len(arg_spec.args) - 1:
            return False

        invoked_args = self.calls[method].args
        invoked_kwargs = self.calls[method].kwargs
        all_invoked_kwargs = self._transform_args_and_kwargs_in_dict(arg_spec.args, *invoked_args, **invoked_kwargs)

        for key in all_kwargs:
            if all_kwargs[key] != all_invoked_kwargs[key]:
                return False
        return True

    def call_count(self, method):
        if method not in self.calls:
            return 0
        return len(self.calls[method].all_args)

    def get_args(self, method, index=None):
        if index is None:
            return self.calls[method].args
        else:
            return self.calls[method].all_args[index]

    def get_kwargs(self, method, index=None):
        if index is None:
            return self.calls[method].kwargs
        else:
            return self.calls[method].all_kwargs[index]

    def get_arg(self, method, name, index=None):
        call = self.calls[method]

        if index is None:
            kwargs_to_use = call.kwargs
            args_to_use = call.args
        else:
            kwargs_to_use = call.all_kwargs[index]
            args_to_use = call.all_args[index]

        if name in kwargs_to_use:
            return kwargs_to_use[name]
        else:
            arg_spec = self._get_arg_spec(method)
            index = arg_spec.args.index(name)
            return args_to_use[index - 1]

    def _get_arg_spec(self, method):
        if sys.version.startswith("3"):
            # noinspection PyUnresolvedReferences
            arg_spec = inspect.getfullargspec(method)
        else:
            # noinspection PyDeprecation
            arg_spec = inspect.getargspec(method)
        return arg_spec

    def __getattribute__(self, name):
        try:
            return super(SimpleStub, self).__getattribute__(name)
        except AttributeError:
            pass

        interface = super(SimpleSpy, self).__getattribute__("interface")
        if not hasattr(interface, name):
            raise AttributeError("Interface has no {} method".format(name))
        method = getattr(interface, name)

        stub_func = super(SimpleSpy, self).__getattribute__(name)

        def inner_func(*args, **kwargs):
            if method not in self.calls:
                self.calls[method] = SpyCall(self.deep_clone)
            self.calls[method].register(*args, **kwargs)
            return stub_func(*args, **kwargs)

        return inner_func

    def _transform_args_and_kwargs_in_dict(self, arg_names, *args, **kwargs):
        ret = {}
        count = 0
        for name in arg_names:
            if name == "self":
                continue
            if name in kwargs:
                ret[name] = kwargs[name]
            else:
                try:
                    ret[name] = args[count]
                except IndexError:
                    pass
                count += 1
        return ret
