#
# Copyright (C) 2025-present ScyllaDB
#
# SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
#

import inspect
import logging
import time

logger = logging.getLogger(__name__)


class retrying:  # noqa: N801
    """
    Used as a decorator to retry function run that can possibly fail with allowed exceptions list
    """

    def __init__(self, num_attempts=3, sleep_time=1, allowed_exceptions=(Exception,), message=""):
        self.num_attempts = num_attempts  # number of times to retry
        self.sleep_time = sleep_time  # number seconds to sleep between retries
        self.allowed_exceptions = allowed_exceptions  # if Exception is not allowed will raise
        self.message = message  # string that will be printed between retries

    @staticmethod
    def get_func_name(func):
        if hasattr(func, "__name__"):
            return func.__name__
        elif hasattr(func, "func") and hasattr(func.func, "__name__"):
            return func.func.__name__
        else:
            return "lambda"

    def __call__(self, func):
        def inner(*args, **kwargs):
            func_args = inspect.getfullargspec(func)
            num_attempts = self.num_attempts
            sleep_time = self.sleep_time
            func_name = self.get_func_name(func)

            if "num_attempts" in func_args.args:
                num_attempts = kwargs.get("num_attempts")
                if not num_attempts:
                    default_args = func_args.args[-len(func_args.defaults) :]
                    num_attempts_position = default_args.index("num_attempts")
                    num_attempts = func_args.defaults[num_attempts_position]

            if "sleep_time" in func_args.args:
                sleep_time = kwargs.get("sleep_time")
                if not sleep_time:
                    default_args = func_args.args[-len(func_args.defaults) :]
                    sleep_time_position = default_args.index("sleep_time")
                    sleep_time = func_args.defaults[sleep_time_position]

            for i in range(num_attempts - 1):
                try:
                    if self.message:
                        logger.debug(f"trying {func_name} [{i + 1}/{num_attempts}] ({self.message})")
                    return func(*args, **kwargs)
                except self.allowed_exceptions as e:
                    logger.debug(f"{func_name} [{i + 1}/{num_attempts}]: {e}: will retry in {sleep_time} second(s)")
                    time.sleep(sleep_time)
            if self.message:
                logger.debug(f"trying {func_name} [{'last try' if num_attempts > 1 else 'single try'}] ({self.message})")
            return func(*args, **kwargs)

        return inner
