package cache

import (
	"errors"
	"sync"
	"sync/atomic"
	"testing"
	"time"
)

func TestGetSetExpiry(t *testing.T) {
	c := New(10)
	if _, ok := c.Get("k"); ok {
		t.Fatal("空缓存不应命中")
	}
	c.Set("k", "v", 50*time.Millisecond)
	if v, ok := c.Get("k"); !ok || v.(string) != "v" {
		t.Fatalf("Get = %v %v, want v true", v, ok)
	}
	// jitter ≤ TTL/10,60ms 覆盖 50+5ms 上限
	time.Sleep(60 * time.Millisecond)
	if _, ok := c.Get("k"); ok {
		t.Error("过期后不应命中")
	}
}

func TestDeletePrefix(t *testing.T) {
	c := New(10)
	c.Set("t1|a", 1, time.Minute)
	c.Set("t1|b", 2, time.Minute)
	c.Set("t2|a", 3, time.Minute)
	c.DeletePrefix("t1|")
	if _, ok := c.Get("t1|a"); ok {
		t.Error("t1|a 应被前缀删除")
	}
	if _, ok := c.Get("t2|a"); !ok {
		t.Error("t2|a 不应被删除")
	}
}

func TestEvictAtMax(t *testing.T) {
	c := New(2)
	c.Set("a", 1, time.Minute)
	c.Set("b", 2, time.Minute)
	c.Set("c", 3, time.Minute)
	if n := len(c.items); n > 2 {
		t.Errorf("键数 = %d, 应不超过上限 2", n)
	}
	if _, ok := c.Get("c"); !ok {
		t.Error("最新写入的键应存在")
	}
}

func TestDoSingleflight(t *testing.T) {
	c := New(10)
	var calls atomic.Int32
	var wg sync.WaitGroup
	for i := 0; i < 8; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			v, err := Do(c, "k", time.Minute, func() (int, error) {
				calls.Add(1)
				time.Sleep(30 * time.Millisecond)
				return 42, nil
			})
			if err != nil || v != 42 {
				t.Errorf("Do = %d, %v", v, err)
			}
		}()
	}
	wg.Wait()
	if n := calls.Load(); n != 1 {
		t.Errorf("并发 miss 回源 %d 次, want 1", n)
	}
	// 命中缓存不再回源
	if _, err := Do(c, "k", time.Minute, func() (int, error) { calls.Add(1); return 0, nil }); err != nil {
		t.Fatal(err)
	}
	if n := calls.Load(); n != 1 {
		t.Errorf("命中后回源 %d 次, want 1", n)
	}
}

func TestDoErrorNotCached(t *testing.T) {
	c := New(10)
	boom := errors.New("boom")
	if _, err := Do(c, "k", time.Minute, func() (int, error) { return 0, boom }); !errors.Is(err, boom) {
		t.Fatalf("err = %v, want boom", err)
	}
	v, err := Do(c, "k", time.Minute, func() (int, error) { return 7, nil })
	if err != nil || v != 7 {
		t.Errorf("错误不应入缓存,重试 = %d, %v", v, err)
	}
}
