package redislock

import (
	"context"
	"fmt"
	"strings"
	"testing"
	"time"
)

type fakeClient struct {
	value string
	ttlMS int64
}

func (f *fakeClient) SetnxExCtx(ctx context.Context, key, value string, seconds int) (bool, error) {
	if err := ctx.Err(); err != nil {
		return false, err
	}
	if f.value != "" {
		return false, nil
	}
	f.value = value
	f.ttlMS = int64(seconds) * 1000
	return true, nil
}

func (f *fakeClient) EvalCtx(ctx context.Context, script string, keys []string, args ...any) (any, error) {
	if err := ctx.Err(); err != nil {
		return nil, err
	}
	owner := fmt.Sprint(args[0])
	if f.value != owner {
		return int64(0), nil
	}
	if script == renewScript {
		f.ttlMS = args[1].(int64)
		return int64(1), nil
	}
	if script == releaseScript {
		f.value = ""
		return int64(1), nil
	}
	return nil, fmt.Errorf("unexpected script")
}

func TestLockUsesUniqueTokenPerAcquisition(t *testing.T) {
	client := &fakeClient{}
	lock := NewWithClient(client, "locks:outbox", "worker-7", time.Minute)

	acquired, err := lock.Acquire(context.Background())
	if err != nil || !acquired {
		t.Fatalf("Acquire() = %v, %v", acquired, err)
	}
	first := client.value
	if !strings.HasPrefix(first, "worker-7:") {
		t.Fatalf("token %q has no worker prefix", first)
	}
	if err := lock.Release(context.Background()); err != nil {
		t.Fatal(err)
	}
	acquired, err = lock.Acquire(context.Background())
	if err != nil || !acquired {
		t.Fatalf("second Acquire() = %v, %v", acquired, err)
	}
	if client.value == first {
		t.Fatal("successful acquisitions reused a token")
	}
}

func TestRenewAndReleaseRequireCurrentOwnership(t *testing.T) {
	client := &fakeClient{}
	lock := NewWithClient(client, "locks:outbox", "worker-7", 3*time.Minute)
	if acquired, err := lock.Acquire(context.Background()); err != nil || !acquired {
		t.Fatalf("Acquire() = %v, %v", acquired, err)
	}

	client.ttlMS = 1
	renewed, err := lock.Renew(context.Background())
	if err != nil || !renewed || client.ttlMS != int64(3*time.Minute/time.Millisecond) {
		t.Fatalf("Renew() = %v, %v, ttl=%d", renewed, err, client.ttlMS)
	}

	client.value = "worker-8:successor"
	renewed, err = lock.Renew(context.Background())
	if err != nil || renewed {
		t.Fatalf("lost-owner Renew() = %v, %v", renewed, err)
	}
	if err := lock.Release(context.Background()); err != nil {
		t.Fatal(err)
	}
	if client.value != "worker-8:successor" {
		t.Fatalf("Release removed successor lease: %q", client.value)
	}
}

func TestLockMethodsHonorCanceledContext(t *testing.T) {
	client := &fakeClient{}
	lock := NewWithClient(client, "locks:outbox", "worker-7", time.Minute)
	ctx, cancel := context.WithCancel(context.Background())
	cancel()
	if _, err := lock.Acquire(ctx); err == nil {
		t.Fatal("Acquire succeeded with canceled context")
	}
}
