package redis

import (
	"context"
	"time"

	"forge.cadoles.com/cadoles/bouncer/internal/lock"
	"github.com/bsm/redislock"
	"github.com/pkg/errors"
	"github.com/redis/go-redis/v9"
	"gitlab.com/wpetit/goweb/logger"
)

type Locker struct {
	client     redis.UniversalClient
	maxRetries int
}

// WithLock implements lock.Locker.
func (l *Locker) WithLock(ctx context.Context, key string, timeout time.Duration, fn func(ctx context.Context) error) error {
	locker := redislock.New(l.client)

	backoff := redislock.ExponentialBackoff(time.Second, timeout*2)

	ctx = logger.With(ctx, logger.F("lockTimeout", timeout), logger.F("lockKey", key))

	logger.Debug(ctx, "acquiring lock")

	err := retryWithBackoff(ctx, l.maxRetries, func(ctx context.Context) error {
		lock, err := locker.Obtain(ctx, key, timeout, &redislock.Options{
			RetryStrategy: backoff,
		})
		if err != nil {
			return errors.WithStack(err)
		}

		logger.Debug(ctx, "lock obtained")

		defer func() {
			if err := lock.Release(ctx); err != nil {
				logger.Error(ctx, "could not release lock", logger.E(errors.WithStack(err)))
			}

			logger.Debug(ctx, "lock released")
		}()

		if err := fn(ctx); err != nil {
			return errors.WithStack(err)
		}

		return nil
	})
	if err != nil {
		return errors.WithStack(err)
	}

	return nil
}

func NewLocker(client redis.UniversalClient, maxRetries int) *Locker {
	return &Locker{
		client:     client,
		maxRetries: maxRetries,
	}
}

var _ lock.Locker = &Locker{}