import asyncio
import inspect
import functools
from datetime import datetime, timedelta


def appoint_limit_async(limit: int, interval: int=0, pause: int=0):
    """
       The decorator that limits how often async function is called.

       :param:
       - limit (int): Maximum number of function call attempts in case of an error.
       - interval (int): The time interval in seconds during which function calls should be limited when working correctly.
       - pause (int): Pause between attempts to call a function in seconds in case of an error.

       :return:
       - wrapper (function): A wrapper function that limits how often the original function is called.

       Usage example №1:
       class MyClient:
            @appoint_limit_async(limit=5, interval=10, pause=2)
            async def my_method(self):
                # Your code for making an API request or I/O operation.

       Usage example №2:
       @appoint_limit_async(limit=5, interval=10, pause=2)
       async def my_function():
           # your code for making I/O operation
    """

    def decorator(func):

        @functools.wraps(func)
        async def wrapper(*args, **kwargs):

            is_method = False
            holder = wrapper

            if args:
                instance = args[0]
                if hasattr(instance, '__class__') and not inspect.isclass(instance):
                    class_attr = getattr(instance.__class__, func.__name__, None)
                    if class_attr and (class_attr is wrapper):
                        is_method = True
                        holder = instance.__class__


            if not hasattr(holder, '_last_call'):
                holder._last_call = datetime.min

            _retries = kwargs.pop('_retries', 0)

            now = datetime.now()

            target_time = holder._last_call + timedelta(seconds=interval)

            scheduled_time = max(now, target_time)

            holder._last_call = scheduled_time

            wait_seconds = (scheduled_time - now).total_seconds()
            if wait_seconds > 0:
                await asyncio.sleep(wait_seconds)
            try:
                return await func(*args, **kwargs)
            except Exception as e:
                if _retries < limit:
                    if pause > 0:
                        await asyncio.sleep(pause)
                    return await wrapper(*args, _retries=_retries + 1, **kwargs)
                raise e

        return wrapper

    return decorator


