import inspect
import typing
from contextlib import contextmanager

from mubble.api import API
from mubble.bot.cute_types.update import UpdateCute
from mubble.bot.dispatch.context import Context
from mubble.bot.dispatch.middleware.abc import ABCMiddleware
from mubble.bot.rules.abc import ABCRule, check_rule
from mubble.node import IsNode, compose_nodes
from mubble.tools.adapter.abc import ABCAdapter
from mubble.tools.adapter.raw_update import RawUpdateAdapter
from mubble.types import Update


class GlobalMiddleware(ABCMiddleware):
    adapter = RawUpdateAdapter()

    def __init__(self):
        self.filters: set[ABCRule] = set()
        self.source_filters: dict[ABCAdapter | IsNode, dict[typing.Any, ABCRule]] = {}

    async def pre(self, event: UpdateCute, ctx: Context) -> bool:
        for filter in self.filters:
            if not await check_rule(event.api, filter, event, ctx):
                return False

        # Simple implication.... Grouped by source categories
        for source, identifiers in self.source_filters.items():
            if isinstance(source, ABCAdapter):
                result = source.adapt(event.api, event, ctx)
                if inspect.isawaitable(result):
                    result = await result

                result = result.unwrap_or_none()
                if result is None:
                    return True

            else:
                result = await compose_nodes({"value": source}, ctx, {Update: event, API: event.api})
                if result := result.unwrap():
                    result = result.values["value"]
                else:
                    return True

            if result in identifiers:
                return await check_rule(event.api, identifiers[result], event, ctx)

        return True

    @contextmanager
    def apply_filters(
        self,
        *filters: ABCRule,
        source_filter: tuple[ABCAdapter | IsNode, typing.Any, ABCRule] | None = None,
    ):
        if source_filter is not None:
            self.source_filters.setdefault(source_filter[0], {})
            self.source_filters[source_filter[0]].update({source_filter[1]: source_filter[2]})

        self.filters |= set(filters)
        yield
        self.filters.difference_update(filters)

        if source_filter is not None:  # noqa: SIM102
            if identifiers := self.source_filters.get(source_filter[0]):
                identifiers.pop(source_filter[1], None)


__all__ = ("GlobalMiddleware",)
