package keyspacepoolconfig

import (
	"context"
	"testing"
)

type recordingWaitLimiter struct {
	waits int
}

func (l *recordingWaitLimiter) Wait(context.Context) error {
	l.waits++
	return nil
}

func TestCreateRateLimiterWaitReusesLimiter(t *testing.T) {
	ctx := context.Background()

	// The goal here is to prove that a stable rate keeps using the cached
	// limiter instead of rebuilding it on every call.
	var created []*recordingWaitLimiter
	limiter := newCreateRateLimiter()
	limiter.newLimiter = func(limit float64) waitLimiter {
		r := &recordingWaitLimiter{}
		created = append(created, r)
		return r
	}

	// First call seeds the cache.
	if err := limiter.wait(ctx, 2.5); err != nil {
		t.Fatalf("first wait() error = %v", err)
	}
	if len(created) != 1 {
		t.Fatalf("first wait() created %d limiters, want 1", len(created))
	}
	if created[0].waits != 1 {
		t.Fatalf("first wait() called Wait %d times, want 1", created[0].waits)
	}

	// Second call with the same rate should reuse the same limiter.
	if err := limiter.wait(ctx, 2.5); err != nil {
		t.Fatalf("second wait() error = %v", err)
	}
	if len(created) != 1 {
		t.Fatalf("second wait() created %d limiters, want 1", len(created))
	}
	if created[0].waits != 2 {
		t.Fatalf("second wait() called Wait %d times, want 2", created[0].waits)
	}
}

func TestCreateRateLimiterWaitReplacesLimiterWhenRateChanges(t *testing.T) {
	ctx := context.Background()

	// A rate change must rebuild the underlying limiter so the new speed takes
	// effect immediately.
	var created []*recordingWaitLimiter
	limiter := newCreateRateLimiter()
	limiter.newLimiter = func(limit float64) waitLimiter {
		r := &recordingWaitLimiter{}
		created = append(created, r)
		return r
	}

	if err := limiter.wait(ctx, 2.5); err != nil {
		t.Fatalf("first wait() error = %v", err)
	}
	if err := limiter.wait(ctx, 1.0); err != nil {
		t.Fatalf("second wait() error = %v", err)
	}

	if len(created) != 2 {
		t.Fatalf("wait() created %d limiters, want 2", len(created))
	}
	if created[0].waits != 1 {
		t.Fatalf("first limiter waited %d times, want 1", created[0].waits)
	}
	if created[1].waits != 1 {
		t.Fatalf("second limiter waited %d times, want 1", created[1].waits)
	}
}

func TestCreateRateLimiterWaitClearsCacheWhenDisabled(t *testing.T) {
	ctx := context.Background()

	// The unlimited path should both skip waiting and clear cached state so a
	// later positive rate does not accidentally reuse stale limiter settings.
	var created []*recordingWaitLimiter
	limiter := newCreateRateLimiter()
	limiter.newLimiter = func(limit float64) waitLimiter {
		r := &recordingWaitLimiter{}
		created = append(created, r)
		return r
	}

	if err := limiter.wait(ctx, 2.5); err != nil {
		t.Fatalf("seed wait() error = %v", err)
	}
	if err := limiter.wait(ctx, 0); err != nil {
		t.Fatalf("disabled wait() error = %v", err)
	}

	if limiter.limiter != nil {
		t.Fatalf("disabled wait() left limiter cached")
	}
	if limiter.rateLimit != 0 {
		t.Fatalf("disabled wait() left rateLimit = %v, want 0", limiter.rateLimit)
	}

	if err := limiter.wait(ctx, 2.5); err != nil {
		t.Fatalf("post-disable wait() error = %v", err)
	}
	if len(created) != 2 {
		t.Fatalf("post-disable wait() created %d limiters, want 2", len(created))
	}
}

func TestCreateRateLimiterWaitNilReceiver(t *testing.T) {
	// Nil receivers are allowed so callers can treat a missing limiter as a
	// no-op without adding extra nil checks.
	var limiter *createRateLimiter
	if err := limiter.wait(context.Background(), 1); err != nil {
		t.Fatalf("nil receiver wait() error = %v", err)
	}
}
