// Package lua 提供 Lua 脚本嵌入能力
package lua

import (
	"bufio"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"os"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	glua "github.com/yuin/gopher-lua"
	"github.com/yuin/gopher-lua/parse"
)

// CacheKeyType 缓存键类型
type CacheKeyType int

// 缓存键类型常量：内联脚本和文件脚本
const (
	// CacheKeyInline 内联脚本缓存键
	CacheKeyInline CacheKeyType = iota // 内联脚本
	// CacheKeyFile 文件脚本缓存键
	CacheKeyFile // 文件脚本
)

// CachedProto 缓存的字节码
type CachedProto struct {
	ModTime    time.Time
	CachedAt   time.Time
	AccessAt   atomic.Value
	Proto      *glua.FunctionProto
	SourcePath string
	SourceType CacheKeyType
}

// CodeCache 字节码缓存
type CodeCache struct {
	protos    map[string]*CachedProto
	order     []string
	maxSize   int
	ttl       time.Duration
	hits      uint64
	misses    uint64
	mu        sync.RWMutex
	fileWatch bool
}

// NewCodeCache 创建字节码缓存
func NewCodeCache(maxSize int, ttl time.Duration, fileWatch bool) *CodeCache {
	return &CodeCache{
		protos:    make(map[string]*CachedProto),
		order:     make([]string, 0, maxSize),
		maxSize:   maxSize,
		ttl:       ttl,
		fileWatch: fileWatch,
	}
}

// generateInlineKey 生成内联脚本缓存键
func (c *CodeCache) generateInlineKey(src string) string {
	hash := sha256.Sum256([]byte(src))
	return "nhli_" + hex.EncodeToString(hash[:])
}

// generateFileKey 生成文件脚本缓存键
func (c *CodeCache) generateFileKey(path string) string {
	hash := sha256.Sum256([]byte(path))
	return "nhlf_" + hex.EncodeToString(hash[:])
}

// GetOrCompileInline 获取或编译内联脚本
func (c *CodeCache) GetOrCompileInline(src string) (*glua.FunctionProto, error) {
	key := c.generateInlineKey(src)

	c.mu.RLock()
	cached, ok := c.protos[key]
	c.mu.RUnlock()

	if ok && !c.isExpired(cached) {
		atomic.AddUint64(&c.hits, 1)
		cached.AccessAt.Store(time.Now())
		return cached.Proto, nil
	}

	atomic.AddUint64(&c.misses, 1)

	// 编译脚本
	chunk, err := parse.Parse(strings.NewReader(src), "<inline>")
	if err != nil {
		return nil, fmt.Errorf("parse inline script: %w", err)
	}
	proto, err := glua.Compile(chunk, "<inline>")
	if err != nil {
		return nil, fmt.Errorf("compile inline script: %w", err)
	}

	// 存入缓存
	cached = &CachedProto{
		Proto:      proto,
		SourceType: CacheKeyInline,
		CachedAt:   time.Now(),
	}
	cached.AccessAt.Store(time.Now())

	c.mu.Lock()
	c.storeLocked(key, cached)
	c.mu.Unlock()

	return proto, nil
}

// GetOrCompileFile 获取或编译文件脚本
func (c *CodeCache) GetOrCompileFile(path string) (*glua.FunctionProto, error) {
	key := c.generateFileKey(path)

	c.mu.RLock()
	cached, ok := c.protos[key]
	c.mu.RUnlock()

	// 检查是否需要重新加载
	if ok && !c.isExpired(cached) && !c.isFileChanged(cached) {
		atomic.AddUint64(&c.hits, 1)
		cached.AccessAt.Store(time.Now())
		return cached.Proto, nil
	}

	atomic.AddUint64(&c.misses, 1)

	// 读取文件
	content, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("read file %s: %w", path, err)
	}

	// 获取文件信息
	info, err := os.Stat(path)
	if err != nil {
		return nil, fmt.Errorf("stat file %s: %w", path, err)
	}

	// 编译脚本
	reader := bufio.NewReader(strings.NewReader(string(content)))
	chunk, err := parse.Parse(reader, path)
	if err != nil {
		return nil, fmt.Errorf("parse file %s: %w", path, err)
	}
	proto, err := glua.Compile(chunk, path)
	if err != nil {
		return nil, fmt.Errorf("compile file %s: %w", path, err)
	}

	// 存入缓存
	cached = &CachedProto{
		Proto:      proto,
		SourceType: CacheKeyFile,
		SourcePath: path,
		ModTime:    info.ModTime(),
		CachedAt:   time.Now(),
	}
	cached.AccessAt.Store(time.Now())

	c.mu.Lock()
	c.storeLocked(key, cached)
	c.mu.Unlock()

	return proto, nil
}

// storeLocked 存入缓存（需持有锁）
func (c *CodeCache) storeLocked(key string, cached *CachedProto) {
	// 如果已存在，更新
	if _, ok := c.protos[key]; ok {
		c.protos[key] = cached
		return
	}

	// LRU 淘汰
	if len(c.protos) >= c.maxSize {
		c.evictLocked()
	}

	c.protos[key] = cached
	c.order = append(c.order, key)
}

// evictLocked 淘汰最久未使用的缓存（需持有锁）
func (c *CodeCache) evictLocked() {
	if len(c.order) == 0 {
		return
	}

	// 找到最久未访问的
	oldestKey := c.order[0]
	oldestTime := time.Now()

	for _, key := range c.order {
		cached := c.protos[key]
		if t, ok := cached.AccessAt.Load().(time.Time); ok && t.Before(oldestTime) {
			oldestTime = t
			oldestKey = key
		}
	}

	// 删除
	delete(c.protos, oldestKey)
	for i, k := range c.order {
		if k == oldestKey {
			c.order = append(c.order[:i], c.order[i+1:]...)
			break
		}
	}
}

// isExpired 检查缓存是否过期
func (c *CodeCache) isExpired(cached *CachedProto) bool {
	if c.ttl <= 0 {
		return false
	}
	return time.Since(cached.CachedAt) > c.ttl
}

// isFileChanged 检查文件是否变更
func (c *CodeCache) isFileChanged(cached *CachedProto) bool {
	if !c.fileWatch || cached.SourceType != CacheKeyFile {
		return false
	}

	info, err := os.Stat(cached.SourcePath)
	if err != nil {
		return true // 文件不存在，视为变更
	}

	return info.ModTime().After(cached.ModTime)
}

// Stats 返回缓存统计
func (c *CodeCache) Stats() (hits, misses uint64, size int) {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return atomic.LoadUint64(&c.hits), atomic.LoadUint64(&c.misses), len(c.protos)
}

// HitRate 返回缓存命中率
func (c *CodeCache) HitRate() float64 {
	hits := atomic.LoadUint64(&c.hits)
	misses := atomic.LoadUint64(&c.misses)
	total := hits + misses
	if total == 0 {
		return 0
	}
	return float64(hits) / float64(total)
}

// Clear 清空缓存
func (c *CodeCache) Clear() {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.protos = make(map[string]*CachedProto)
	c.order = c.order[:0]
}
