import datetime
import typing

from telegrinder.api.api import API
from telegrinder.bot.cute_types.update import UpdateCute
from telegrinder.bot.dispatch.context import Context
from telegrinder.bot.dispatch.handler.func import FuncHandler
from telegrinder.bot.dispatch.middleware.abc import ABCMiddleware
from telegrinder.bot.dispatch.process import check_rule
from telegrinder.bot.dispatch.waiter_machine.short_state import ShortStateContext
from telegrinder.modules import logger
from telegrinder.types.objects import Update

if typing.TYPE_CHECKING:
    from telegrinder.bot.dispatch.waiter_machine.hasher import Hasher
    from telegrinder.bot.dispatch.waiter_machine.machine import WaiterMachine
    from telegrinder.bot.dispatch.waiter_machine.short_state import ShortState


class WaiterMiddleware(ABCMiddleware):
    def __init__(
        self,
        machine: WaiterMachine,
        hasher: Hasher[typing.Any, typing.Any],
    ) -> None:
        self.machine = machine
        self.hasher = hasher

    async def pre(self, update_cute: UpdateCute, raw_update: Update, api: API, ctx: Context) -> bool:
        if self.hasher not in self.machine.storage:
            return True

        event = update_cute.incoming_update
        key = self.hasher.get_hash_from_data_from_event(event)
        if not key:
            await logger.ainfo("Unable to get hash from event with hasher {!r}", self.hasher)
            return True

        short_state: ShortState | None = self.machine.storage[self.hasher].get(key.unwrap())
        if not short_state:
            return True

        # Just ignore update if state expired, so it will be dropped automatically by WaiterMachine
        if short_state.expiration_date is not None and datetime.datetime.now() >= short_state.expiration_date:
            return True

        preset_context = Context(short_state=short_state)
        if short_state.context is not None:
            preset_context |= short_state.context.context

        if short_state.filter is not None and not await check_rule(short_state.filter, preset_context):
            await logger.adebug("Filter rule {!r} failed!", short_state.filter)
            return True

        handler = FuncHandler(
            function=self.pass_runtime,
            rules=(short_state.release,) if short_state.release is not None else None,
            preset_context=preset_context,
        )

        if not await handler.run(api, raw_update, ctx) and (on_miss := short_state.actions.get("on_miss")) is not None:
            await on_miss.run(api, raw_update, ctx)

        return False

    async def pass_runtime(
        self,
        event: UpdateCute,
        ctx: Context,
        short_state: ShortState,
    ) -> None:
        ctx.initiator = self.hasher
        short_state.context = ShortStateContext(event.incoming_update, ctx)
        short_state.event.set()


__all__ = ("WaiterMiddleware",)
