// Copyright 2022 PingCAP, Inc.

package locks

import (
	"context"
	"errors"
	"time"

	goredislib "github.com/go-redis/redis/v8"
	"github.com/go-redsync/redsync/v4"
	redsyncredis "github.com/go-redsync/redsync/v4/redis"
	redsyncgoredis "github.com/go-redsync/redsync/v4/redis/goredis/v8"
)

// copy from go1.19 math package
const (
	intSize = 32 << (^uint(0) >> 63) // 32 or 64

	maxInt = 1<<(intSize-1) - 1
)

var ErrFailed = redsync.ErrFailed

type Locks struct {
	redsync.Redsync
}

func NewLocks(lockAddrs ...string) (*Locks, error) {
	pools := make([]redsyncredis.Pool, 0)
	for _, lockAddr := range lockAddrs {
		client := goredislib.NewClient(&goredislib.Options{
			Addr: lockAddr,
		})
		pool := redsyncgoredis.NewPool(client)
		pools = append(pools, pool)
	}
	if len(pools) == 0 {
		return nil, errors.New("no lock addr found")
	}
	locks := redsync.New(pools...)
	return &Locks{*locks}, nil
}

type Lock struct {
	redsync.Mutex
}

func (ls *Locks) LockWaitContext(ctx context.Context, token string, expiry time.Duration) (*Lock, error) {
	lock := ls.NewMutex(token, redsync.WithTries(maxInt), redsync.WithExpiry(expiry))

	err := lock.LockContext(ctx)
	if err != nil {
		if err == redsync.ErrFailed {
			return nil, ErrFailed
		}
		return nil, err
	}
	return &Lock{*lock}, nil
}
