
import logging
from random import randrange
from sqlite3 import OperationalError
from time import sleep
from typing import Any, Callable

from requests.exceptions import HTTPError, ReadTimeout

retry_logger = logging.getLogger('auto_retry')

def continue_on_revert(func: Callable) -> Any:
    '''
    Decorates a call-making function. If the call reverts, returns `None`. Raises any other exceptions.
    '''
    from y.exceptions import continue_if_call_reverted

    def continue_on_revert_wrap(*args: Any, **kwargs: Any) -> Callable:
        try:
            return func(*args,**kwargs)
        except Exception as e:
            continue_if_call_reverted(e)
    
    return continue_on_revert_wrap


def log(logger: logging.Logger):
    """
    Decorates a function so both the inputs and outputs are logged with logger level DEBUG.
    For convenience, also decorates the function with @auto_retry.
    """

    def log_decorator(func: Callable) -> Callable:
        assert logger, 'To use @debug_logging decorator, you must pass in a logger.'

        def logging_wrap(*args: Any, **kwargs: Any) -> Any:
            fn_name = func.__name__

            if len(kwargs) == 0:
                describer_string = f'{fn_name}{tuple([*args])}'
            else:
                describer_string = f'{fn_name}{tuple([*args])}, kwargs: {[*kwargs.items()]}'
            
            logger.debug(f'Fetching {describer_string}')
            func_returns = retry_superwrap(*args,**kwargs)
            logger.debug(f'{describer_string} returns: {func_returns}')
            return func_returns
        
        @auto_retry
        def retry_superwrap(*args: Any, **kwargs: Any) -> Callable:
            return func(*args, **kwargs)

        return logging_wrap
    return log_decorator

def auto_retry(func):
    '''
    Decorator that will retry the function on:
    - ConnectionError
    - HTTPError
    - TimeoutError
    - ReadTimeout
    
    It will also retry on specific ValueError exceptions:
    - Max rate limit reached
    - please use API Key for higher rate limit
    - execution aborted (timeout = 5s)
    
    On repeat errors, will retry in increasing intervals.
    '''

    def retry_wrap(*args, **kwargs):
        i = 0
        sleep_time = randrange(10,20)
        while True:
            try:
                return func(*args, **kwargs)
            except ValueError as e:
                retry_on_errs = (
                    # Occurs on any chain when making computationally intensive calls. Just retry.
                    'execution aborted (timeout = 5s)',

                    # Usually from block explorer while fetching contract source code. Just retry.
                    'Max rate limit reached',
                    'please use API Key for higher rate limit',

                    # Occurs occasionally on AVAX when node is slow to sync. Just retry.
                    'after last accepted block',
                )
                if 1 > 10 or not any([err in str(e) for err in retry_on_errs]):
                    raise
                retry_logger.warning(f'{str(e)} [{i}]')
            except (ConnectionError, HTTPError, TimeoutError, ReadTimeout) as e:
                # This happens when we pass too large of a request to the node. Do not retry.
                if 'Too Large' in str(e):
                    raise
                retry_logger.warning(f'{str(e)} [{i}]')
            except OperationalError as e:
                # This happens when brownie's deployments.db gets locked. Just retry.
                if 'database is locked' not in str(e):
                    raise
                retry_logger.warning(f'{str(e)} [{i}]')
            i += 1
            sleep(i * sleep_time)

    return retry_wrap
