package retry

import (
	"math"
	"math/rand"
	"sync"
	"time"
)

type service struct {
	mu  sync.Mutex
	rnd *rand.Rand
}

// New creates retry policy service.
func New() Policy {
	//nolint:gosec // Backoff jitter is non-cryptographic and only needs pseudo-random distribution.
	return &service{rnd: rand.New(rand.NewSource(time.Now().UnixNano()))}
}

func (s *service) NextAttempt(attempt int, now time.Time, minDelay, maxDelay time.Duration, jitterRatio float64) time.Time {
	if attempt < 1 {
		attempt = 1
	}
	if minDelay <= 0 {
		minDelay = time.Second
	}
	if maxDelay < minDelay {
		maxDelay = minDelay
	}

	base := float64(minDelay)
	delay := time.Duration(base * math.Pow(2, float64(attempt-1)))
	if delay > maxDelay {
		delay = maxDelay
	}

	if jitterRatio > 0 {
		if jitterRatio > 1 {
			jitterRatio = 1
		}
		maxJitter := int64(float64(delay) * jitterRatio)
		if maxJitter > 0 {
			s.mu.Lock()
			jitter := s.rnd.Int63n((maxJitter * 2) + 1)
			s.mu.Unlock()
			delta := jitter - maxJitter
			delay += time.Duration(delta)
		}
	}

	if delay < minDelay {
		delay = minDelay
	}
	if delay > maxDelay {
		delay = maxDelay
	}

	return now.UTC().Add(delay)
}
