// Package cache 提供文件缓存和代理缓存功能，支持 LRU 淘汰和缓存锁防击穿。
//
// 该文件实现了缓存清理 API，用于主动清理代理缓存。
//
// 主要功能：
//   - 精确路径清理：删除指定路径的缓存条目
//   - 通配符模式清理：按模式批量删除缓存条目
//   - IP 白名单访问控制
//   - Token 认证支持
//
// 作者：xfy
package cache

import (
	"encoding/json"
	"hash/fnv"
	"net"
	"strings"

	"github.com/valyala/fasthttp"
	"rua.plus/lolly/internal/config"
	"rua.plus/lolly/internal/utils"
)

// PurgeAPI 缓存清理 API 处理器。
//
// 提供 HTTP API 用于主动清理代理缓存，支持精确路径和通配符模式清理。
//
// 注意事项：
//   - 所有方法均为并发安全
//   - 支持 IP 白名单和 Token 认证
//   - 仅处理 POST 请求
type PurgeAPI struct {
	cache   *ProxyCache
	auth    config.CacheAPIAuthConfig
	path    string
	allowed []net.IPNet
}

// 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"`
}

// PurgeErrorResponse 错误响应结构。
type PurgeErrorResponse struct {
	// Error 错误信息
	Error string `json:"error"`
}

// NewPurgeAPI 创建缓存清理 API 处理器。
//
// 参数：
//   - cache: 代理缓存实例
//   - cfg: 缓存 API 配置
//
// 返回值：
//   - *PurgeAPI: 配置好的处理器
//   - error: IP 解析失败时返回非 nil 错误
func NewPurgeAPI(cache *ProxyCache, cfg *config.CacheAPIConfig) (*PurgeAPI, error) {
	p := &PurgeAPI{
		cache: cache,
		auth:  cfg.Auth,
		path:  cfg.Path,
	}

	// 解析允许的 IP 列表
	for _, cidr := range cfg.Allow {
		_, network, err := net.ParseCIDR(cidr)
		if err != nil {
			// 尝试作为单个 IP 解析
			ip := net.ParseIP(cidr)
			if ip == nil {
				return nil, err
			}
			// 转换为 CIDR 格式
			if ip.To4() != nil {
				_, network, _ = net.ParseCIDR(cidr + "/32")
			} else {
				_, network, _ = net.ParseCIDR(cidr + "/128")
			}
		}
		if network != nil {
			p.allowed = append(p.allowed, *network)
		}
	}

	return p, nil
}

// Path 返回 API 端点路径。
func (p *PurgeAPI) Path() string {
	if p.path == "" {
		return "/_cache/purge"
	}
	return p.path
}

// ServeHTTP 处理缓存清理请求。
//
// 仅处理 POST 请求，支持精确路径和通配符模式清理。
// 返回 JSON 格式的响应。
func (p *PurgeAPI) ServeHTTP(ctx *fasthttp.RequestCtx) {
	// 仅允许 POST 方法
	if string(ctx.Method()) != "POST" {
		utils.SendJSONError(ctx, fasthttp.StatusMethodNotAllowed, "method not allowed")
		return
	}

	// 检查 IP 访问权限
	if !utils.CheckIPAccess(ctx, p.allowed) {
		utils.SendJSONError(ctx, fasthttp.StatusForbidden, "forbidden")
		return
	}

	// 检查认证
	if !utils.CheckTokenAuth(ctx, p.auth) {
		utils.SendJSONError(ctx, fasthttp.StatusUnauthorized, "unauthorized")
		return
	}

	// 解析请求体
	var req PurgeRequest
	if err := json.Unmarshal(ctx.PostBody(), &req); err != nil {
		utils.SendJSONError(ctx, fasthttp.StatusBadRequest, "invalid request body")
		return
	}

	// 执行清理
	deleted := 0
	if req.Path != "" {
		deleted = p.purgeByPath(req.Path)
	} else if req.Pattern != "" {
		deleted = p.purgeByPattern(req.Pattern)
	} else {
		utils.SendJSONError(ctx, fasthttp.StatusBadRequest, "missing path or pattern")
		return
	}

	// 返回响应
	ctx.SetContentType("application/json; charset=utf-8")
	ctx.SetStatusCode(fasthttp.StatusOK)
	_ = json.NewEncoder(ctx).Encode(PurgeResponse{Deleted: deleted})
}

// purgeByPath 按精确路径清理缓存。
func (p *PurgeAPI) purgeByPath(path string) int {
	if p.cache == nil {
		return 0
	}

	// 计算缓存键的哈希值
	hashKey := hashPath(path)

	// 尝试删除
	p.cache.mu.Lock()
	defer p.cache.mu.Unlock()

	if _, ok := p.cache.entries[hashKey]; ok {
		delete(p.cache.entries, hashKey)
		return 1
	}

	return 0
}

// purgeByPattern 按通配符模式清理缓存。
func (p *PurgeAPI) purgeByPattern(pattern string) int {
	if p.cache == nil {
		return 0
	}

	p.cache.mu.Lock()
	defer p.cache.mu.Unlock()

	deleted := 0
	for hashKey, entry := range p.cache.entries {
		if matchPattern(pattern, entry.OrigKey) {
			delete(p.cache.entries, hashKey)
			deleted++
		}
	}

	return deleted
}

// HashPathWithMethod 使用 FNV-64a 计算缓存键的哈希值。
// method 为空时默认使用 "GET"。
func HashPathWithMethod(path string, method string) uint64 {
	if method == "" {
		method = "GET"
	}
	key := method + ":" + path
	h := fnv.New64a()
	h.Write([]byte(key))
	return h.Sum64()
}

// hashPath 使用 FNV-64a 计算路径的哈希值。
// 与代理层 buildCacheKeyHash 使用相同的算法，确保一致性。
// 注意：代理层的 key 格式为 "METHOD:URI"，purge 时默认使用 GET 方法。
func hashPath(path string) uint64 {
	return HashPathWithMethod(path, "GET")
}

// MatchPattern 检查路径是否匹配通配符模式。
//
// 支持以下匹配模式：
//   - "*"：匹配所有路径
//   - 以 "*" 结尾：前缀匹配（如 "/api/*" 匹配 "/api/xxx"）
//   - 以 "/" 结尾：目录前缀匹配
//   - 中间通配符："/api/*/users" 匹配 "/api/v1/users"
//   - 其他：精确匹配
//
// 参数：
//   - pattern: 匹配模式，支持通配符
//   - path: 待检查的路径
//
// 返回值：
//   - bool: true 表示匹配，false 表示不匹配
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
}

// matchPattern 是 MatchPattern 的内部别名，保持向后兼容。
func matchPattern(pattern, path string) bool {
	return MatchPattern(pattern, path)
}
