from collections import OrderedDict
from typing import Callable, Dict, Set

from .commands import parse_command_docstring
from .hookspec import deltabot_hookimpl

_filters: Set[tuple] = set()


class Filters:
    def __init__(self, bot) -> None:
        self.logger = bot.logger
        self._filter_defs: Dict[str, FilterDef] = OrderedDict()
        bot.plugins.add_module("filters", self)

    def register(
        self,
        func: Callable,
        name: str = None,
        help: str = None,  # noqa
        tryfirst: bool = False,
        trylast: bool = False,
        admin: bool = False,
    ) -> None:
        """register a filter function that acts on each incoming non-system message.
        :param func: function can accept 'bot', 'message' and 'replies' arguments.
        :param name: name of the filter, if not provided it is autogenerated from function name.
        :param help: filter's description, it will be extracted from the function docstring if not provided.
        :param tryfirst: Set to True if the filter should be executed as
                         soon as possible.
        :param trylast: Set to True if the filter should be executed as
                        late as possible.
        :param admin: if True the filter will activate for bot administrators only.
        """
        name = name or f"{func.__module__}.{func.__name__}"
        if help is not None:
            func.__doc__ = help
        short, long, args = parse_command_docstring(
            func, args=["message", "replies", "bot"]
        )
        prio = 0 - tryfirst + trylast
        filter_def = FilterDef(
            name,
            short=short,
            long=long,
            func=func,
            args=args,
            priority=prio,
            admin=admin,
        )
        if name in self._filter_defs:
            raise ValueError(f"filter {name!r} already registered")
        self._filter_defs[name] = filter_def
        self.logger.debug(f"registered new filter {name!r}")

    def unregister(self, name: str) -> Callable:
        """unregister a filter function."""
        return self._filter_defs.pop(name)

    def dict(self) -> dict:
        return self._filter_defs.copy()

    @deltabot_hookimpl(trylast=True)
    def deltabot_incoming_message(self, bot, message, replies) -> None:
        is_admin = bot.is_admin(message.get_sender_contact().addr)
        for name, filter_def in sorted(
            self._filter_defs.items(), key=lambda e: e[1].priority
        ):
            if filter_def.admin and not is_admin:
                continue
            self.logger.debug(f"calling filter {name!r} on message id={message.id}")
            if filter_def(message=message, replies=replies, bot=bot):
                return


class FilterDef:
    """Definition of a Filter that acts on incoming messages."""

    def __init__(self, name, short, long, func, args, priority, admin) -> None:
        self.name = name
        self.short = short
        self.long = long
        self.func = func
        self.args = args
        self.priority = priority
        self.admin = admin

    def __eq__(self, c) -> bool:
        return c.__dict__ == self.__dict__

    def __call__(self, **kwargs):
        for key in list(kwargs.keys()):
            if key not in self.args:
                del kwargs[key]
        return self.func(**kwargs)


def filter_decorator(func: Callable = None, **kwargs) -> Callable:
    """Register decorated function as bot filter.

    Check documentation of method `simplebot.filters.Filters.register` to
    see all parameters the decorated function can accept.
    """

    def _decorator(func) -> Callable:
        kwargs["func"] = func
        _filters.add(tuple(sorted(kwargs.items())))
        return func

    if func is None:
        return _decorator
    return _decorator(func)
