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

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	glua "github.com/yuin/gopher-lua"
)

// TestNewEngine 测试引擎创建
func TestNewEngine(t *testing.T) {
	engine, err := NewEngine(DefaultConfig())
	require.NoError(t, err)
	require.NotNil(t, engine)
	defer engine.Close()

	assert.NotNil(t, engine.lstatePool)
	assert.NotNil(t, engine.codeCache)
	assert.Equal(t, int32(0), engine.ActiveCoroutines())
}

// TestNewCoroutine 测试协程创建
func TestNewCoroutine(t *testing.T) {
	engine, err := NewEngine(DefaultConfig())
	require.NoError(t, err)
	defer engine.Close()

	coro, err := engine.NewCoroutine(nil)
	require.NoError(t, err)
	require.NotNil(t, coro)
	require.NotNil(t, coro.Co)

	defer coro.Close()

	assert.Equal(t, int32(1), engine.ActiveCoroutines())
}

// TestCoroutineDeadAfterResumeOK 验证协程 ResumeOK 后变成 dead 不能复用
// 注意：gopher-lua 的 Resume 对 dead coroutine 会 panic，无法安全测试
// 此测试验证 ResumeOK 正常完成，证明协程生命周期正确
func TestCoroutineDeadAfterResumeOK(t *testing.T) {
	L := glua.NewState()
	defer L.Close()

	// 创建协程
	co, cocancel := L.NewThread()
	require.NotNil(t, co)
	if cocancel != nil {
		defer cocancel()
	}

	// 编译简单脚本
	proto, err := engineCodeToProto("return 42")
	require.NoError(t, err)

	fn := L.NewFunctionFromProto(proto)

	// 执行协程，应该正常完成
	st, err, values := L.Resume(co, fn)
	assert.Equal(t, glua.ResumeOK, st)
	assert.NoError(t, err)
	assert.Len(t, values, 1)
	assert.Equal(t, glua.LNumber(42), values[0])

	// 协程完成后变成 dead 状态
	// 注意：再次调用 Resume(co, fn) 会 panic
	// 实际使用中必须确保每个协程只使用一次
}

// TestLFunctionCannotCrossLState 验证 LFunction 不能跨 LState 使用
// 注意：FunctionProto 可以跨 LState 使用，但 LFunction 绑定到特定 LState
// 这个测试验证的是 FunctionProto 共享的正确性
func TestLFunctionCannotCrossLState(t *testing.T) {
	L1 := glua.NewState()
	defer L1.Close()

	// 在 L1 中编译脚本并执行
	proto, err := engineCodeToProto("return 42")
	require.NoError(t, err)

	fn := L1.NewFunctionFromProto(proto)
	L1.Push(fn)
	err = L1.PCall(0, 1, nil)
	require.NoError(t, err)
	assert.Equal(t, glua.LNumber(42), L1.Get(-1))
	L1.Pop(1)

	// FunctionProto 可以在不同 LState 使用（这是缓存的核心假设）
	L2 := glua.NewState()
	defer L2.Close()

	fn2 := L2.NewFunctionFromProto(proto) // 从同一个 proto 创建新的函数
	L2.Push(fn2)
	err = L2.PCall(0, 1, nil)
	require.NoError(t, err)
	assert.Equal(t, glua.LNumber(42), L2.Get(-1))
	L2.Pop(1)
}

// TestNewThreadInheritsGlobals 验证 NewThread 继承全局环境
func TestNewThreadInheritsGlobals(t *testing.T) {
	L := glua.NewState()
	defer L.Close()

	// 在主 LState 设置全局变量
	L.SetGlobal("test_global", glua.LString("shared_value"))

	// 创建协程
	co, cocancel := L.NewThread()
	require.NotNil(t, co)
	if cocancel != nil {
		defer cocancel()
	}

	// 协程应该能访问主 LState 的全局变量
	proto, err := engineCodeToProto("return test_global")
	require.NoError(t, err)

	fn := L.NewFunctionFromProto(proto)
	st, err, values := L.Resume(co, fn)
	assert.Equal(t, glua.ResumeOK, st)
	assert.NoError(t, err)
	assert.Len(t, values, 1)
	assert.Equal(t, glua.LString("shared_value"), values[0])
}

// TestPerRequestEnvSandbox 验证 _ENV 沙箱隔离
func TestPerRequestEnvSandbox(t *testing.T) {
	engine, err := NewEngine(DefaultConfig())
	require.NoError(t, err)
	defer engine.Close()

	// 创建第一个协程并设置沙箱
	coro1, err := engine.NewCoroutine(nil)
	require.NoError(t, err)
	require.NotNil(t, coro1)

	err = coro1.SetupSandbox()
	require.NoError(t, err)

	// 在沙箱中设置变量
	err = coro1.Execute("local x = 1")
	assert.NoError(t, err)

	// 创建第二个协程
	coro2, err := engine.NewCoroutine(nil)
	require.NoError(t, err)
	require.NotNil(t, coro2)

	err = coro2.SetupSandbox()
	require.NoError(t, err)

	// 第二个协程不应该看到第一个协程的变量
	// 由于我们使用了 _ENV 沙箱，局部变量是隔离的
	coro1.Close()
	coro2.Close()
}

// TestCodeCache 测试字节码缓存
func TestCodeCache(t *testing.T) {
	cache := NewCodeCache(100, 0, false)

	script := "return 1 + 1"

	// 第一次编译
	proto1, err := cache.GetOrCompileInline(script)
	require.NoError(t, err)
	require.NotNil(t, proto1)

	// 第二次应该命中缓存
	proto2, err := cache.GetOrCompileInline(script)
	require.NoError(t, err)
	require.NotNil(t, proto2)

	// 相同的脚本应该返回相同的字节码
	assert.Equal(t, proto1, proto2)

	// 检查命中率
	hits, misses, _ := cache.Stats()
	assert.Equal(t, uint64(1), hits)
	assert.Equal(t, uint64(1), misses)
}

// TestCodeCacheDifferentScripts 测试不同脚本的缓存
func TestCodeCacheDifferentScripts(t *testing.T) {
	cache := NewCodeCache(100, 0, false)

	proto1, err := cache.GetOrCompileInline("return 1")
	require.NoError(t, err)

	proto2, err := cache.GetOrCompileInline("return 2")
	require.NoError(t, err)

	// 不同脚本应该产生不同的字节码
	assert.NotEqual(t, proto1, proto2)

	hits, misses, _ := cache.Stats()
	assert.Equal(t, uint64(0), hits) // 都是 miss
	assert.Equal(t, uint64(2), misses)
}

// Helper function: compile Lua code to FunctionProto
func engineCodeToProto(src string) (*glua.FunctionProto, error) {
	return cacheGetOrCompile(src)
}

// Package-level helper for testing
var cacheGetOrCompile = NewCodeCache(100, 0, false).GetOrCompileInline
