"""

    AUTHOR:

        Khushal P Soonderji

    DATE:

        Saturday, 7th Jun., 2025

    OBJECTIVE:

        To have a mechanism where multiple instances of a cron script can be running and can have a mechanism to call
        dibs on the right to execute a particular activity by announcing their availability.

    REFERENCES:

        N/A

    DOWNLOADS:

        N/A

"""


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                   IMPORT                                                      ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# My utils:
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache

 # To make a decorator:
from functools import wraps

# For hashing and shortening the hash:
import hashlib
import base64

# for random values:
import random

# For asynchronous activities:
import asyncio

# To work with datatypes:
from typing import Callable


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                           MACROS / ONE-TIME INIT                                              ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# --- Nothing Yet


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                VARIABLES                                                      ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# --- Nothing Yet


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                  CLASSES                                                      ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# --- Nothing Yet


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                  FUNCTIONS                                                    ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


def call_dibs_through_redis(
        cache: AsyncRedisCache | Callable,
        max_dibs: int = 1,
        min_random_delay: float = 0.0,
        max_random_delay: float = 0.0,
        expiry: float = 5.0
):

    """
    This decorator factory will be used to decorate functions in cron scripts such that each instance can claim dibs
    over the execution of the function. In case other instance(s) have already claimed dibs, this instance's claim will
    be rejected.
    :param cache: The instance of 'AsyncRedisCache'.
    :param max_dibs: The maximum number of instances that should be allowed to execute the wrapped function.
    :param min_random_delay: The minimum delay in seconds to sleep before claiming dibs.
    :param max_random_delay: The maximum delay in seconds to sleep before claiming dibs.
    :param expiry: The time in seconds after which the cached data must be cleared.
    :return: The decorator that automatically caches your data.
    """

    def decorator(func):

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

            # Fetch the cache from a callable if needed:
            _cache = cache() if isinstance(cache, Callable) else cache
            if not isinstance(_cache, AsyncRedisCache): return None

            # In case a random delay has been requested:
            if max_random_delay > 0.0:
                await asyncio.sleep(min_random_delay + (random.random() * (max_random_delay - min_random_delay)))

            # We first use the name of the function and the inputs given to it to generate a key for Redis with the
            # simple hashing and shortening by way of base64 strings:
            inputs_given = "cron_dibs_" + func.__name__ + str([_ for _ in args]) + str(kwargs)
            sha256_hash = hashlib.sha256()
            sha256_hash.update(inputs_given.encode("utf-8"))
            hashed_key = sha256_hash.digest()
            base64_key = base64.b64encode(hashed_key).decode("utf-8")

            # We increment a counter in Redis to check what our claim no. is:
            claim_no = await _cache.count(key = base64_key, value = 1, expiry = expiry, raise_exception = False)

            # If the claim no. is within the limit defined by max. dibs,
            # We execute the wrapped function:
            if claim_no <= max_dibs: response = await func(*args, **kwargs)

            # Otherwise, we don't execute the function:
            else: response = f"Other instances claimed the dibs. Claim No: {claim_no}"

            # Return the response from the wrapped function.
            return response

        return wrapper

    return decorator


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                               MAIN PROGRAM                                                    ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


if __name__ == "__main__":

    pass
