// Package cache 提供进程内 TTL 缓存与 singleflight 合并,服务 OCI 读放大
// 与审计原始事件暂存等场景;单进程部署,无跨进程共享需求(调研①方案 A)。
package cache

import (
	"math/rand/v2"
	"strings"
	"sync"
	"time"
)

type entry struct {
	val any
	exp time.Time
}

// flight 是一次进行中的回源;后到的同 key 调用等待它完成。
type flight struct {
	wg  sync.WaitGroup
	val any
	err error
}

// Cache 是带键数上限的进程内 TTL 缓存;并发 miss 同 key 只回源一次。
type Cache struct {
	mu      sync.Mutex
	items   map[string]entry
	flights map[string]*flight
	max     int
}

// New 创建缓存;max 为键数上限,到限时先清过期项、仍满则任意驱逐。
func New(max int) *Cache {
	return &Cache{items: map[string]entry{}, flights: map[string]*flight{}, max: max}
}

// Get 返回未过期的缓存值。
func (c *Cache) Get(key string) (any, bool) {
	c.mu.Lock()
	defer c.mu.Unlock()
	e, ok := c.items[key]
	if !ok || time.Now().After(e.exp) {
		return nil, false
	}
	return e.val, true
}

// Set 写入缓存;TTL 附加至多 1/10 的随机抖动,避免整批同时过期。
func (c *Cache) Set(key string, val any, ttl time.Duration) {
	c.mu.Lock()
	defer c.mu.Unlock()
	if len(c.items) >= c.max {
		c.evictLocked()
	}
	jitter := time.Duration(rand.Int64N(int64(ttl)/10 + 1))
	c.items[key] = entry{val: val, exp: time.Now().Add(ttl + jitter)}
}

// evictLocked 先清全部过期项,仍到上限则任意删除腾出空位。
func (c *Cache) evictLocked() {
	now := time.Now()
	for k, e := range c.items {
		if now.After(e.exp) {
			delete(c.items, k)
		}
	}
	for k := range c.items {
		if len(c.items) < c.max {
			return
		}
		delete(c.items, k)
	}
}

// DeletePrefix 删除全部以 prefix 开头的键(写操作后按租户前缀失效)。
func (c *Cache) DeletePrefix(prefix string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	for k := range c.items {
		if strings.HasPrefix(k, prefix) {
			delete(c.items, k)
		}
	}
}

// join 登记或加入 key 的进行中回源;返回 true 表示已有同 key 回源在跑。
func (c *Cache) join(key string) (*flight, bool) {
	c.mu.Lock()
	defer c.mu.Unlock()
	if f, ok := c.flights[key]; ok {
		return f, true
	}
	f := &flight{}
	f.wg.Add(1)
	c.flights[key] = f
	return f, false
}

// settle 结束回源登记并唤醒等待者。
func (c *Cache) settle(key string, f *flight) {
	c.mu.Lock()
	delete(c.flights, key)
	c.mu.Unlock()
	f.wg.Done()
}

// Do 返回缓存值;miss 时经 singleflight 回源,成功写缓存,错误不缓存
// (等待者共享同一错误,下次调用重新回源)。
func Do[T any](c *Cache, key string, ttl time.Duration, fn func() (T, error)) (T, error) {
	var zero T
	if v, ok := c.Get(key); ok {
		return v.(T), nil
	}
	f, joined := c.join(key)
	if joined {
		f.wg.Wait()
		if f.err != nil {
			return zero, f.err
		}
		return f.val.(T), nil
	}
	f.val, f.err = fn()
	c.settle(key, f)
	if f.err != nil {
		return zero, f.err
	}
	c.Set(key, f.val, ttl)
	return f.val.(T), nil
}
