package theorycrafter.utils

import eve.esi.infrastructure.ServerException
import kotlinx.coroutines.delay
import java.io.IOException
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds


/**
 * Repeatedly calls [request] until it returns without throwing an exception that can be normally thrown during an ESI
 * request, and returns its result.
 *
 * If [request] throws an unexpected exception, it will be propagated to the caller.
 */
suspend fun <T> sendEsiRequestWithRetry(
    delay: Duration = 30.seconds,
    onFirstException: (Exception) -> Unit = {},
    request: () -> T,
): T {
    return sendRequestWithRetry(
        delay = delay,
        retryWhenThrown = { (it is IOException) || (it is ServerException) },
        onFirstException = onFirstException,
        request = request,
    )
}


/**
 * Repeatedly calls [request] until it returns without throwing an [IOException], and returns the result.
 *
 * If [request] throws a different exception, it will be propagated to the caller.
 */
suspend fun <T> sendRequestWithRetry(
    delay: Duration = 30.seconds,
    onFirstException: (Exception) -> Unit = {},
    request: () -> T,
): T {
    return sendRequestWithRetry(
        delay = delay,
        retryWhenThrown = { it is IOException },
        onFirstException = onFirstException,
        request = request,
    )
}


/**
 * Repeatedly calls [request] until it returns without throwing an exception, and returns the result
 *
 * Whenever an exception is thrown, [retryWhenThrown] is called, and if it returns `false`, the exception is propagated
 * to the caller.
 */
suspend fun <T> sendRequestWithRetry(
    delay: Duration = 10.seconds,
    retryWhenThrown: (Exception) -> Boolean,
    onFirstException: (Exception) -> Unit,
    request: () -> T,
): T {
    var isFirstTime = true
    while (true) {
        try {
            return request()
        } catch (e: Exception) {
            if (!retryWhenThrown(e))
                throw e

            if (isFirstTime) {
                isFirstTime = false
                onFirstException(e)
            }
            delay(delay)
        }
    }
}