package k8s

import (
	"errors"
	"time"

	"k8s.io/client-go/util/workqueue"
	ctrl "sigs.k8s.io/controller-runtime"
)

var (
	// RequeueOnlyError is a special error to indicate only requeue for re-sync and do not touch the phase.
	RequeueOnlyError       = errors.New("requeue only")
	_                error = &retriableError{}
)

type retriableError struct {
	err    error
	result ctrl.Result
}

func NewRetriableError(err error, result ctrl.Result) (res *retriableError) {
	if result.IsZero() {
		panic("zero value result")
	}

	if err != nil {
		if e, ok := err.(*retriableError); ok {
			e.result = result
			return e
		}
	}

	return &retriableError{
		err:    err,
		result: result,
	}
}

func (e *retriableError) Error() string {
	if e == nil || e.err == nil {
		return ""
	}
	return e.err.Error()
}

func ShouldRequeue(err error) ctrl.Result {
	if e, ok := err.(*retriableError); ok || errors.As(err, &e) {
		return e.result
	}

	return ctrl.Result{}
}

// RequeueOnly indicates only requeue for re-sync, and no need to touch the current phase.
func RequeueOnly(err error) bool {
	if e, ok := err.(*retriableError); ok {
		return e.err == RequeueOnlyError
	}
	return false
}

const (
	controllerInitDelay   = 1 * time.Second
	controllerMaxInterval = 1 * time.Minute
)

// 2 4 8 16 32 64(>1min = controllerMaxInterval)
var RetryLimiter = workqueue.NewTypedMaxOfRateLimiter[ctrl.Request](
	workqueue.NewTypedItemExponentialFailureRateLimiter[ctrl.Request](controllerInitDelay, controllerMaxInterval),
)
