# Copyright (C) 2015-2024 by Vd.
# This file is part of Rocketgram, the modern Telegram bot framework.
# Rocketgram is released under the MIT License (see LICENSE).


import asyncio
import logging
from dataclasses import dataclass
from inspect import isclass
from typing import Callable, Coroutine, AsyncGenerator, Union, List

from .filters import FILTERS_ATTR, PRIORITY_ATTR, WAITER_ASSIGNED_ATTR, HANDLER_ASSIGNED_ATTR
from .filters import FilterParams, _check_sig
from ..router import Router

logger = logging.getLogger('rocketgram.dispatcher')


@dataclass
class Handler:
    priority: int
    handler: Union[Callable, Coroutine, AsyncGenerator]
    filters: List[FilterParams]


DEFAULT_PRIORITY = 1024


def _get_function(handler):
    if not isclass(handler):
        return handler

    instance = handler()
    assert callable(instance), "Class handlers must be callable!"
    return instance


async def _call_or_await(func, *args, **kwargs):
    r = func(*args, **kwargs)
    if asyncio.iscoroutine(r):
        return await r
    return r


class BaseDispatcher(Router):
    __slots__ = ('_init', '_shutdown', '_handlers', '_pre', '_post', '_default_priority', '_bots')

    def __init__(self, *, default_priority=DEFAULT_PRIORITY):
        self._init = list()
        self._shutdown = list()
        self._handlers: List[Handler] = list()
        self._pre: List[Handler] = list()
        self._post: List[Handler] = list()
        self._default_priority = default_priority
        self._bots: int = 0

    @property
    def default_priority(self):
        return self._default_priority

    def _resort_handlers(self):
        # sorting handlers by priority
        self._handlers = sorted(self._handlers, key=lambda handler: handler.priority)
        self._pre = sorted(self._pre, key=lambda handler: handler.priority)
        self._post = sorted(self._post, key=lambda handler: handler.priority)

    @property
    def inits(self):
        return self._init

    @property
    def shutdowns(self):
        return self._shutdown

    @property
    def handlers(self):
        return self._handlers

    @property
    def befores(self):
        return self._pre

    @property
    def afters(self):
        return self._post

    def from_dispatcher(self, dispatcher: 'BaseDispatcher'):
        self._init.extend(dispatcher.inits)
        self._shutdown.extend(dispatcher.shutdowns)
        self._handlers.extend(dispatcher.handlers)
        self._pre.extend(dispatcher.befores)
        self._post.extend(dispatcher.afters)

        # if handler added in runtime - resort handlers
        if self._bots:
            self._resort_handlers()

    async def init(self):
        logger.debug('Performing init...')

        if not self._bots:
            self._resort_handlers()

        self._bots += 1

        for func in self._init:
            await _call_or_await(func)

    async def shutdown(self):
        logger.debug('Performing shutdown...')

        for func in reversed(self._shutdown):
            await _call_or_await(func)

        self._bots -= 1

    def on_init(self, func):
        """Registers init"""

        self._init.append(func)
        return func

    def on_shutdown(self, func):
        """Registers shutdown"""

        self._shutdown.append(func)
        return func

    def _register(self, what: List[Handler], handler: Callable[..., None]):
        # Save handler to the given handlers list.

        function = _get_function(handler)

        assert _check_sig(function), \
            'Handler `%s` must not take any arguments!' % handler

        assert not hasattr(handler, HANDLER_ASSIGNED_ATTR), 'Handler already registered!'
        assert not hasattr(handler, WAITER_ASSIGNED_ATTR), 'Already registered as waiter!'

        priority = getattr(handler, PRIORITY_ATTR, self._default_priority)
        assert isinstance(priority, int), 'Handler function has wrong priority!'

        filters = getattr(handler, FILTERS_ATTR, list())
        assert isinstance(filters, list), 'Handler function has wrong filters!'
        assert len(filters), 'Handler must have at least one filter!'

        what.append(Handler(priority, function, filters))

        setattr(function, HANDLER_ASSIGNED_ATTR, True)

        # if handler added in runtime - resort handlers
        if self._bots:
            self._resort_handlers()

    def handler(self, handler: Callable[..., None]):
        """Registers handler"""

        self._register(self._handlers, handler)
        return handler

    def before(self, handler: Callable[..., None]):
        """Registers preprocessor"""

        self._register(self._pre, handler)
        return handler

    def after(self, handler: Callable[..., None]):
        """Registers postprocessor"""

        self._register(self._post, handler)
        return handler

    async def process(self):
        """Process new request."""

        raise NotImplementedError
