// Package cache 提供文件缓存和代理缓存功能，支持 LRU 淘汰和缓存锁防击穿。
//
// 该文件包含缓存相关的核心逻辑，包括：
//   - 文件缓存实现，支持 LRU 淘汰策略
//   - 代理响应缓存，支持缓存锁防止缓存击穿
//   - 缓存统计和生命周期管理
//
// 主要用途：
//
//	用于缓存静态文件内容和代理响应，减少磁盘 I/O 和上游请求，提升服务性能。
//
// 注意事项：
//   - 文件缓存支持按条目数和内存大小双重限制
//   - 代理缓存支持过期缓存复用（stale）机制
//   - 所有公开方法均为并发安全
//
// 作者：xfy
package cache

import (
	"container/list"
	"hash/fnv"
	"slices"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"rua.plus/lolly/internal/utils"
)

// FileEntry 文件缓存条目，存储单个文件的缓存信息。
type FileEntry struct {
	ModTime     time.Time
	CachedAt    time.Time // 缓存时间，用于 TTL 验证（新鲜度）
	LastAccess  time.Time
	element     *list.Element
	Path        string
	Data        []byte
	Size        int64
	ETag        string // 预计算的 ETag，避免每次请求重新计算
	ContentType string // 预计算的 MIME 类型，避免每次请求重新检测
}

// FileCache 文件缓存，支持 LRU 淘汰策略。
//
// 该结构体实现了基于内存的文件缓存，支持按条目数和内存大小限制进行淘汰。
// 使用 LRU（最近最少使用）算法决定淘汰顺序。
//
// 注意事项：
//   - 所有方法均为并发安全
//   - 支持过期时间自动淘汰
//   - 使用 sync.Pool 复用 FileEntry，减少内存分配
type FileCache struct {
	entries     map[string]*FileEntry
	lruList     *list.List
	maxEntries  int64
	maxSize     int64
	inactive    time.Duration
	currentSize int64
	mu          sync.RWMutex
	entryPool   sync.Pool // FileEntry 池，复用条目减少分配
}

// NewFileCache 创建文件缓存实例。
//
// 根据指定的条目数限制、内存大小限制和过期时间创建缓存。
//
// 参数：
//   - maxEntries: 最大缓存条目数，设为 0 表示不限制
//   - maxSize: 内存使用上限（字节），设为 0 表示不限制
//   - inactive: 未访问淘汰时间，超过此时间未访问的条目将被淘汰
//
// 返回值：
//   - *FileCache: 创建的文件缓存实例
func NewFileCache(maxEntries, maxSize int64, inactive time.Duration) *FileCache {
	c := &FileCache{
		maxEntries: maxEntries,
		maxSize:    maxSize,
		inactive:   inactive,
		entries:    make(map[string]*FileEntry),
		lruList:    list.New(),
	}
	// 初始化 entry 池
	c.entryPool = sync.Pool{
		New: func() any {
			return &FileEntry{}
		},
	}
	return c
}

// Get 获取缓存的文件。
//
// 根据文件路径查找缓存条目，如果找到且未过期则返回。
// 访问时会更新条目的访问时间并移动到 LRU 链表头部。
//
// 参数：
//   - path: 文件路径，作为缓存键
//
// 返回值：
//   - *FileEntry: 缓存条目，包含文件内容和元数据
//   - bool: 是否找到有效缓存
func (c *FileCache) Get(path string) (*FileEntry, bool) {
	c.mu.RLock()

	entry, ok := c.entries[path]
	if !ok {
		c.mu.RUnlock()
		return nil, false
	}

	// 只读判断：检查是否过期
	if time.Since(entry.LastAccess) > c.inactive {
		c.mu.RUnlock()
		// 升级为写锁，double-check entry 仍存在且仍过期
		c.mu.Lock()
		if entry, ok = c.entries[path]; ok && time.Since(entry.LastAccess) > c.inactive {
			c.removeEntry(entry)
		}
		c.mu.Unlock()
		return nil, false
	}

	// 只读判断：CachedAt 是否需要迁移
	needMigrate := entry.CachedAt.IsZero()

	c.mu.RUnlock()

	if needMigrate {
		// 升级为写锁，double-check entry 仍存在
		c.mu.Lock()
		entry, ok = c.entries[path]
		if !ok {
			c.mu.Unlock()
			return nil, false
		}
		// double-check 过期（RUnlock 和 Lock 之间可能已过期）
		if time.Since(entry.LastAccess) > c.inactive {
			c.removeEntry(entry)
			c.mu.Unlock()
			return nil, false
		}
		entry.CachedAt = time.Now()
		entry.LastAccess = time.Now()
		c.lruList.MoveToFront(entry.element)
		c.mu.Unlock()
		return entry, true
	}

	// 更新近似 LRU 访问时间（每 5 秒或超过 10% inactive 间隔才写，减少锁竞争）
	const accessUpdateInterval = 5 * time.Second
	if time.Since(entry.LastAccess) > accessUpdateInterval {
		c.mu.Lock()
		if e, ok := c.entries[path]; ok && e == entry {
			e.LastAccess = time.Now()
			c.lruList.MoveToFront(e.element)
		}
		c.mu.Unlock()
	}

	// 近似 LRU: 不更新 LastAccess/LRU 位置，直接返回
	// 精确 LRU 由 Set/Delete/evict 操作保证
	return entry, true
}

// Set 设置缓存条目。
//
// 将文件内容存入缓存，如果缓存已存在则更新。
// 存入后检查是否需要触发 LRU 淘汰。
//
// 参数：
//   - path: 文件路径，作为缓存键
//   - data: 文件内容字节
//   - size: 文件大小（字节）
//   - modTime: 文件最后修改时间
//   - contentType: MIME 类型
//
// 返回值：
//   - error: 当前实现始终返回 nil
func (c *FileCache) Set(path string, data []byte, size int64, modTime time.Time, contentType string) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	// 预计算 ETag
	etag := utils.GenerateETag(modTime, size)

	// 检查是否已存在
	if entry, ok := c.entries[path]; ok {
		c.currentSize -= entry.Size
		entry.Data = data
		entry.Size = size
		entry.ModTime = modTime
		entry.ETag = etag
		entry.ContentType = contentType
		entry.CachedAt = time.Now() // 更新缓存时间
		entry.LastAccess = time.Now()
		c.currentSize += size
		c.lruList.MoveToFront(entry.element)
		c.evictIfNeeded()
		return nil
	}

	// 从池中获取条目并初始化
	entry := c.entryPool.Get().(*FileEntry) //nolint:errcheck // pool always returns valid *FileEntry
	entry.Path = path
	entry.Data = data
	entry.Size = size
	entry.ModTime = modTime
	entry.ETag = etag
	entry.ContentType = contentType
	entry.CachedAt = time.Now()
	entry.LastAccess = time.Now()
	entry.element = c.lruList.PushFront(entry)
	c.entries[path] = entry
	c.currentSize += size

	c.evictIfNeeded()
	return nil
}

// Delete 删除缓存条目。
//
// 根据文件路径删除对应的缓存条目。
//
// 参数：
//   - path: 文件路径，作为缓存键
func (c *FileCache) Delete(path string) {
	c.mu.Lock()
	defer c.mu.Unlock()

	if entry, ok := c.entries[path]; ok {
		c.removeEntry(entry)
	}
}

// RefreshCachedAt 更新 CachedAt 并移动 LRU 位置。
//
// 用于 TTL 过期但文件未修改时刷新缓存时间。
//
// 参数：
//   - path: 文件路径，作为缓存键
func (c *FileCache) RefreshCachedAt(path string) {
	c.mu.Lock()
	defer c.mu.Unlock()

	if entry, ok := c.entries[path]; ok {
		entry.CachedAt = time.Now()
		entry.LastAccess = time.Now()
		c.lruList.MoveToFront(entry.element)
	}
}

// removeEntry 内部删除条目（不加锁）。
//
// 从 LRU 链表和条目映射中移除指定条目，更新当前内存使用量。
// 调用此方法前必须已持有写锁。
//
// 参数：
//   - entry: 要删除的缓存条目
func (c *FileCache) removeEntry(entry *FileEntry) {
	c.lruList.Remove(entry.element)
	delete(c.entries, entry.Path)
	c.currentSize -= entry.Size
	// Reset entry 并放回池中复用
	entry.Path = ""
	entry.Data = nil
	entry.Size = 0
	entry.ModTime = time.Time{}
	entry.CachedAt = time.Time{}
	entry.LastAccess = time.Time{}
	entry.ETag = ""
	entry.ContentType = ""
	entry.element = nil
	c.entryPool.Put(entry)
}

// evictIfNeeded 根据限制淘汰条目。
//
// 检查当前缓存是否超过条目数或内存大小限制，
// 如果超过则调用 evictLRU 淘汰最久未使用的条目。
func (c *FileCache) evictIfNeeded() {
	// 按条目数淘汰
	for c.lruList.Len() > int(c.maxEntries) && c.maxEntries > 0 {
		c.evictLRU()
	}

	// 按内存大小淘汰
	for c.currentSize > c.maxSize && c.maxSize > 0 {
		c.evictLRU()
	}
}

// evictLRU 淘汰最久未使用的条目。
//
// 从 LRU 链表尾部移除条目并删除。
// 如果链表为空则不执行任何操作。
func (c *FileCache) evictLRU() {
	if c.lruList.Len() == 0 {
		return
	}

	element := c.lruList.Back()
	if element == nil {
		return
	}

	entry, ok := element.Value.(*FileEntry)
	if !ok {
		return
	}
	c.removeEntry(entry)
}

// Stats 返回缓存统计信息。
func (c *FileCache) Stats() FileCacheStats {
	c.mu.RLock()
	defer c.mu.RUnlock()

	return FileCacheStats{
		Entries:    int64(len(c.entries)),
		MaxEntries: c.maxEntries,
		Size:       c.currentSize,
		MaxSize:    c.maxSize,
	}
}

// FileCacheStats 文件缓存统计。
type FileCacheStats struct {
	// Entries 当前缓存条目数量
	Entries int64

	// MaxEntries 最大缓存条目数限制
	MaxEntries int64

	// Size 当前缓存使用的内存大小（字节）
	Size int64

	// MaxSize 最大内存使用限制（字节）
	MaxSize int64
}

// ProxyCacheRule 代理缓存规则。
type ProxyCacheRule struct {
	Path     string        // 匹配路径
	Methods  []string      // 可缓存的 HTTP 方法
	Statuses []int         // 可缓存的状态码
	MaxAge   time.Duration // 缓存有效期
}

// ProxyCacheEntry 代理缓存条目。
type ProxyCacheEntry struct {
	Created       time.Time
	Headers       map[string]string
	Key           string
	OrigKey       string
	Data          []byte
	Status        int
	MaxAge        time.Duration
	Uses          atomic.Int32 // 访问计数，用于 min_uses 阈值检查
	Updating      atomic.Bool  // 后台更新标志，表示正在后台刷新
	LastModified  string       // Last-Modified 响应头，用于条件请求
	ETag          string       // ETag 响应头，用于条件请求
	LastValidated time.Time    // 最后验证时间，用于防止验证循环
}

// ProxyCache 代理响应缓存，支持缓存锁防击穿。
type ProxyCache struct {
	entries        map[uint64]*ProxyCacheEntry
	pending        map[uint64]*pendingRequest
	rules          []ProxyCacheRule
	staleTime      time.Duration // StaleWhileRevalidate 窗口
	staleIfError   time.Duration // 错误时使用过期缓存的窗口
	staleIfTimeout time.Duration // 超时时使用过期缓存的窗口
	mu             sync.RWMutex
	cacheLock      bool
}

// pendingRequest 等待中的缓存请求。
type pendingRequest struct {
	done chan struct{} // 完成信号
	err  error         // 生成结果
}

// NewProxyCache 创建代理缓存实例。
//
// 根据指定的缓存规则、锁开关和过期复用时间创建 ProxyCache。
// 启用 cacheLock 可防止缓存击穿，多个并发请求共享同一个生成过程。
//
// 参数：
//   - rules: 代理缓存规则列表，定义可缓存的路径、方法、状态码等
//   - cacheLock: 是否启用缓存生成锁，防止多个请求同时生成缓存
//   - staleTime: StaleWhileRevalidate 窗口，过期后后台刷新期间可复用
//   - staleIfError: 错误时使用过期缓存的窗口（上游 5xx/连接失败）
//   - staleIfTimeout: 超时时使用过期缓存的窗口
//
// 返回值：
//   - *ProxyCache: 初始化的代理缓存实例
func NewProxyCache(rules []ProxyCacheRule, cacheLock bool, staleTime, staleIfError, staleIfTimeout time.Duration) *ProxyCache {
	return &ProxyCache{
		rules:          rules,
		entries:        make(map[uint64]*ProxyCacheEntry),
		cacheLock:      cacheLock,
		pending:        make(map[uint64]*pendingRequest),
		staleTime:      staleTime,
		staleIfError:   staleIfError,
		staleIfTimeout: staleIfTimeout,
	}
}

// Get 获取缓存的代理响应。
// hashKey 是 uint64 哈希值，origKey 是原始 key 用于碰撞验证。
func (c *ProxyCache) Get(hashKey uint64, origKey string) (*ProxyCacheEntry, bool, bool) {
	c.mu.RLock()
	entry, ok := c.entries[hashKey]
	c.mu.RUnlock()

	if !ok {
		return nil, false, false
	}

	// 双重验证：检查原始 key 是否匹配（防止哈希碰撞）
	if entry.OrigKey != origKey {
		return nil, false, false
	}

	// 增加访问计数（原子操作，用于 min_uses 阈值检查）
	entry.Uses.Add(1)

	// 检查是否过期
	now := time.Now()
	expired := now.Sub(entry.Created) > entry.MaxAge

	if expired {
		// 检查是否可以使用过期缓存
		if c.staleTime > 0 && now.Sub(entry.Created) <= entry.MaxAge+c.staleTime {
			return entry, true, true // stale but usable
		}
		return nil, false, false
	}

	return entry, true, false
}

// GetStale 在上游错误时获取可用的过期缓存。
//
// 与 Get 不同，GetStale 只在错误发生时使用，根据错误类型检查对应的 stale 窗口。
// 超时错误检查 staleIfTimeout，其他错误检查 staleIfError。
//
// 参数：
//   - hashKey: 缓存键的哈希值
//   - origKey: 原始缓存键（用于双重验证）
//   - isTimeout: 是否为超时错误
//
// 返回值：
//   - *ProxyCacheEntry: 缓存条目
//   - bool: 是否存在可用的过期缓存
func (c *ProxyCache) GetStale(hashKey uint64, origKey string, isTimeout bool) (*ProxyCacheEntry, bool) {
	c.mu.RLock()
	defer c.mu.RUnlock()

	entry, ok := c.entries[hashKey]
	if !ok {
		return nil, false
	}

	// 双重验证：检查原始 key 是否匹配
	if entry.OrigKey != origKey {
		return nil, false
	}

	now := time.Now()
	expiresAt := entry.Created.Add(entry.MaxAge)

	// 未过期，直接返回
	if !now.After(expiresAt) {
		return entry, true
	}

	// 已过期，检查 stale 窗口
	var staleWindow time.Duration
	if isTimeout {
		staleWindow = c.staleIfTimeout
	} else {
		staleWindow = c.staleIfError
	}

	if staleWindow <= 0 {
		return nil, false
	}

	// 检查是否在 stale 窗口内
	if now.Sub(expiresAt) > staleWindow {
		return nil, false
	}

	return entry, true
}

// Set 设置代理缓存条目。
func (c *ProxyCache) Set(hashKey uint64, origKey string, data []byte, headers map[string]string, status int, maxAge time.Duration) {
	c.mu.Lock()
	defer c.mu.Unlock()

	c.entries[hashKey] = &ProxyCacheEntry{
		Key:     origKey, // 存储原始 key 作为 Key 字段（保持兼容性）
		OrigKey: origKey,
		Data:    data,
		Headers: headers,
		Status:  status,
		Created: time.Now(),
		MaxAge:  maxAge,
	}

	// 如果有等待的请求，通知它们
	if pending, ok := c.pending[hashKey]; ok {
		pending.err = nil
		close(pending.done)
		delete(c.pending, hashKey)
	}
}

// AcquireLockWithTimeout 获取缓存生成锁（带超时）。
// 返回值：
//   - waitCh != nil && timedOut == false: 需要等待其他请求完成
//   - waitCh == nil && timedOut == false: 获得锁，应该生成缓存
//   - timedOut == true: 超时，应该放弃缓存直接请求上游
func (c *ProxyCache) AcquireLockWithTimeout(hashKey uint64, timeout time.Duration) (waitCh <-chan struct{}, timedOut bool) {
	if !c.cacheLock {
		return nil, false // 不使用缓存锁
	}

	c.mu.Lock()
	// 检查是否已有缓存
	if _, ok := c.entries[hashKey]; ok {
		c.mu.Unlock()
		return nil, false
	}

	// 检查是否有 pending 请求
	if pending, ok := c.pending[hashKey]; ok {
		c.mu.Unlock()
		// 有其他请求正在生成，需要等待
		if timeout > 0 {
			// 带超时等待
			select {
			case <-pending.done:
				// 刚刚完成，重新检查缓存
				return nil, false
			case <-time.After(timeout):
				// 超时
				return nil, true
			}
		}
		return pending.done, false // 无限等待
	}

	// 创建新的 pending 请求
	pending := &pendingRequest{
		done: make(chan struct{}),
	}
	c.pending[hashKey] = pending
	c.mu.Unlock()
	return nil, false // 获得锁，应该生成缓存
}

// ReleaseLock 释放缓存生成锁。
func (c *ProxyCache) ReleaseLock(hashKey uint64, err error) {
	if !c.cacheLock {
		return
	}

	c.mu.Lock()
	defer c.mu.Unlock()

	if pending, ok := c.pending[hashKey]; ok {
		pending.err = err
		close(pending.done)
		delete(c.pending, hashKey)
	}
}

// MatchRule 检查请求是否匹配缓存规则。
// 路径匹配规则：
//   - 以 "/" 结尾：前缀匹配（如 "/api/" 匹配 "/api/users"）
//   - 包含 "*"：通配符匹配（如 "/static/*" 匹配 "/static/css/style.css"）
//   - 其他：精确匹配或带分隔符的前缀匹配（如 "/api" 匹配 "/api"、"/api/users"、"/api?query"）
func (c *ProxyCache) MatchRule(path, method string, status int) *ProxyCacheRule {
	for _, rule := range c.rules {
		if !c.matchPath(rule.Path, path) {
			continue
		}
		if !c.matchMethod(rule.Methods, method) {
			continue
		}
		if !c.matchStatus(rule.Statuses, status) {
			continue
		}
		return &rule
	}
	return nil
}

// matchPath 检查路径是否匹配规则路径。
func (c *ProxyCache) matchPath(rulePath, path string) bool {
	if rulePath == "" {
		return true
	}

	switch {
	case strings.HasSuffix(rulePath, "/"):
		// 前缀匹配："/api/" 匹配 "/api/users"
		return strings.HasPrefix(path, rulePath)
	case strings.Contains(rulePath, "*"):
		// 通配符匹配："/static/*" 匹配 "/static/css/style.css"
		return MatchPattern(rulePath, path)
	default:
		// 精确匹配或带分隔符的前缀匹配
		// "/api" 匹配 "/api"、"/api/users"、"/api?query"
		// 但不匹配 "/apiother"
		if path == rulePath {
			return true
		}
		return strings.HasPrefix(path, rulePath+"/") || strings.HasPrefix(path, rulePath+"?")
	}
}

// matchMethod 检查方法是否在允许列表中。
func (c *ProxyCache) matchMethod(methods []string, method string) bool {
	if len(methods) == 0 {
		return true
	}
	return slices.Contains(methods, method)
}

// matchStatus 检查状态码是否在允许列表中。
func (c *ProxyCache) matchStatus(statuses []int, status int) bool {
	if len(statuses) == 0 {
		return true
	}
	return slices.Contains(statuses, status)
}

// Delete 删除缓存条目。
func (c *ProxyCache) Delete(hashKey uint64) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	delete(c.entries, hashKey)
	return nil
}

// DeleteByPatternWithMethod 按通配符模式删除缓存条目。
// method 过滤：检查 entry.OrigKey 是否以 "method:" 前缀开头。
// 空 method 匹配所有条目。
func (c *ProxyCache) DeleteByPatternWithMethod(pattern string, method string) int {
	c.mu.Lock()
	defer c.mu.Unlock()
	deleted := 0
	for hashKey, entry := range c.entries {
		if MatchPattern(pattern, entry.OrigKey) {
			if method == "" || strings.HasPrefix(entry.OrigKey, method+":") {
				delete(c.entries, hashKey)
				deleted++
			}
		}
	}
	return deleted
}

// RefreshTTL 刷新缓存条目的 TTL（用于 304 响应处理）。
// 不替换缓存内容，只更新验证时间和验证头。
// 返回是否成功（条目可能已被驱逐）。
func (c *ProxyCache) RefreshTTL(hashKey uint64, origKey string, newHeaders map[string]string) bool {
	c.mu.Lock()
	defer c.mu.Unlock()

	entry, ok := c.entries[hashKey]
	if !ok || entry.OrigKey != origKey {
		return false // 条目已被驱逐
	}

	// 更新验证时间（不更新 Created，保持 LRU 顺序）
	entry.LastValidated = time.Now()

	// 更新验证头（如果提供）
	if newHeaders != nil {
		if lm, ok := newHeaders["Last-Modified"]; ok {
			entry.LastModified = lm
		}
		if et, ok := newHeaders["ETag"]; ok {
			entry.ETag = et
		}
	}

	return true
}

// SetValidationHeaders 设置缓存条目的验证头（Last-Modified 和 ETag）。
func (c *ProxyCache) SetValidationHeaders(hashKey uint64, origKey string, lastModified, etag string) bool {
	c.mu.Lock()
	defer c.mu.Unlock()

	entry, ok := c.entries[hashKey]
	if !ok || entry.OrigKey != origKey {
		return false
	}

	entry.LastModified = lastModified
	entry.ETag = etag
	return true
}

// Stats 返回代理缓存统计。
func (c *ProxyCache) Stats() ProxyCacheStats {
	c.mu.RLock()
	defer c.mu.RUnlock()

	return ProxyCacheStats{
		Entries: len(c.entries),
		Pending: len(c.pending),
	}
}

// ProxyCacheStats 代理缓存统计。
type ProxyCacheStats struct {
	// Entries 当前缓存条目数量
	Entries int

	// Pending 正在等待缓存生成的请求数量
	Pending int
}

// HashPathWithMethod 使用 FNV-64a 计算路径和方法的哈希值。
func HashPathWithMethod(path string, method string) uint64 {
	if method == "" {
		method = "GET"
	}
	key := method + ":" + path
	h := fnv.New64a()
	h.Write([]byte(key))
	return h.Sum64()
}

// MatchPattern 检查路径是否匹配通配符模式。
//
// 支持以下匹配模式：
//   - "*"：匹配所有路径
//   - 以 "*" 结尾：前缀匹配（如 "/api/*" 匹配 "/api/xxx"）
//   - 以 "/" 结尾：目录前缀匹配
//   - 中间通配符："/api/*/users" 匹配 "/api/v1/users"
//   - 其他：精确匹配
func MatchPattern(pattern, path string) bool {
	// 特殊情况：* 匹配所有
	if pattern == "*" {
		return true
	}

	// 目录前缀匹配（pattern 以 / 结尾）
	if strings.HasSuffix(pattern, "/") {
		return strings.HasPrefix(path, pattern)
	}

	// 检查是否有通配符
	if !strings.Contains(pattern, "*") {
		return path == pattern
	}

	// 简单的前缀匹配：/api/users/* 匹配 /api/users/123
	if prefix, ok := strings.CutSuffix(pattern, "*"); ok {
		return strings.HasPrefix(path, prefix)
	}

	// 中间通配符：/api/*/users 匹配 /api/v1/users
	parts := strings.Split(pattern, "*")
	if len(parts) == 2 {
		return strings.HasPrefix(path, parts[0]) && strings.HasSuffix(path, parts[1])
	}

	// 复杂模式不支持，返回 false
	return false
}

// PurgeRequest 缓存清理请求结构。
type PurgeRequest struct {
	// Path 精确路径
	Path string `json:"path,omitempty"`

	// Pattern 通配符模式（支持 * 通配符）
	Pattern string `json:"pattern,omitempty"`

	// Method HTTP 方法，默认 "GET"
	Method string `json:"method,omitempty"`
}

// PurgeResponse 缓存清理响应结构。
type PurgeResponse struct {
	// Deleted 被删除的缓存条目数
	Deleted int `json:"deleted"`
}
