// Package lua 提供 ngx.req API 测试
package lua

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/valyala/fasthttp"
	glua "github.com/yuin/gopher-lua"
	"rua.plus/lolly/internal/testutil"
)

// 创建测试用的 fasthttp.RequestCtx
func createTestRequestCtx(method, uri string, headers map[string]string, body []byte) *fasthttp.RequestCtx {
	ctx := testutil.NewRequestCtxWithHeader(method, uri, headers)

	// 设置请求体
	if len(body) > 0 {
		ctx.Request.SetBody(body)
	}

	return ctx
}

// TestNgxReqGetMethod 测试 ngx.req.get_method()
func TestNgxReqGetMethod(t *testing.T) {
	engine, err := NewEngine(DefaultConfig())
	require.NoError(t, err)
	defer engine.Close()

	// 创建测试请求
	reqCtx := createTestRequestCtx("POST", "/test", nil, nil)

	// 创建协程
	coro, err := engine.NewCoroutine(reqCtx)
	require.NoError(t, err)
	defer coro.Close()

	// 设置沙箱（这会自动注册 ngx API）
	err = coro.SetupSandbox()
	require.NoError(t, err)

	// 测试获取请求方法
	err = coro.Execute(`
		local method = ngx.req.get_method()
		if method ~= "POST" then
			error("expected POST, got " .. tostring(method))
		end
	`)
	assert.NoError(t, err)
}

// TestNgxReqGetURI 测试 ngx.req.get_uri()
func TestNgxReqGetURI(t *testing.T) {
	engine, err := NewEngine(DefaultConfig())
	require.NoError(t, err)
	defer engine.Close()

	reqCtx := createTestRequestCtx("GET", "/path/to/resource?key=value", nil, nil)

	coro, err := engine.NewCoroutine(reqCtx)
	require.NoError(t, err)
	defer coro.Close()

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

	err = coro.Execute(`
		local uri = ngx.req.get_uri()
		if uri ~= "/path/to/resource" then
			error("expected /path/to/resource, got " .. tostring(uri))
		end
	`)
	assert.NoError(t, err)
}

// TestNgxReqSetURI 测试 ngx.req.set_uri()
func TestNgxReqSetURI(t *testing.T) {
	engine, err := NewEngine(DefaultConfig())
	require.NoError(t, err)
	defer engine.Close()

	// 测试设置 URI（不带 jump）
	reqCtx := createTestRequestCtx("GET", "/original", nil, nil)
	coro, err := engine.NewCoroutine(reqCtx)
	require.NoError(t, err)

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

	err = coro.Execute(`
		ngx.req.set_uri("/new/path")
	`)
	assert.NoError(t, err)
	coro.Close()

	// 验证 URI 已修改
	assert.Equal(t, "/new/path", string(reqCtx.URI().Path()))

	// 测试设置 URI（带 jump）
	reqCtx2 := createTestRequestCtx("GET", "/original", nil, nil)
	coro2, err := engine.NewCoroutine(reqCtx2)
	require.NoError(t, err)

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

	err = coro2.Execute(`
		ngx.req.set_uri("/redirect/path", true)
	`)
	assert.NoError(t, err)
	coro2.Close()

	assert.Equal(t, "/redirect/path", string(reqCtx2.URI().Path()))
	// 验证 jump 标记已设置
	jumpFlag := reqCtx2.UserValue("_ngx_req_internal_jump")
	assert.Equal(t, true, jumpFlag)
}

// TestNgxReqGetURIArgs 测试 ngx.req.get_uri_args()
func TestNgxReqGetURIArgs(t *testing.T) {
	engine, err := NewEngine(DefaultConfig())
	require.NoError(t, err)
	defer engine.Close()

	reqCtx := createTestRequestCtx("GET", "/test?foo=bar&baz=qux&arr=1&arr=2", nil, nil)

	coro, err := engine.NewCoroutine(reqCtx)
	require.NoError(t, err)
	defer coro.Close()

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

	err = coro.Execute(`
		local args = ngx.req.get_uri_args()

		if args.foo ~= "bar" then
			error("expected foo=bar, got " .. tostring(args.foo))
		end

		if args.baz ~= "qux" then
			error("expected baz=qux, got " .. tostring(args.baz))
		end

		-- 多值参数应该返回数组
		if type(args.arr) ~= "table" then
			error("expected arr to be table, got " .. type(args.arr))
		end

		if args.arr[1] ~= "1" or args.arr[2] ~= "2" then
			error("expected arr = {1, 2}")
		end
	`)
	assert.NoError(t, err)
}

// TestNgxReqSetURIArgs 测试 ngx.req.set_uri_args()
func TestNgxReqSetURIArgs(t *testing.T) {
	engine, err := NewEngine(DefaultConfig())
	require.NoError(t, err)
	defer engine.Close()

	reqCtx := createTestRequestCtx("GET", "/test", nil, nil)

	coro, err := engine.NewCoroutine(reqCtx)
	require.NoError(t, err)
	defer coro.Close()

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

	// 测试使用 table 设置参数
	err = coro.Execute(`
		ngx.req.set_uri_args({ key = "value", num = 123 })
	`)
	assert.NoError(t, err)

	queryStr := string(reqCtx.URI().QueryString())
	assert.Contains(t, queryStr, "key=value")
	assert.Contains(t, queryStr, "num=123")

	// 测试使用字符串设置参数
	reqCtx2 := createTestRequestCtx("GET", "/test", nil, nil)
	coro2, err := engine.NewCoroutine(reqCtx2)
	require.NoError(t, err)
	defer coro2.Close()

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

	err = coro2.Execute(`
		ngx.req.set_uri_args("foo=bar&baz=qux")
	`)
	assert.NoError(t, err)

	assert.Equal(t, "foo=bar&baz=qux", string(reqCtx2.URI().QueryString()))
}

// TestNgxReqGetHeaders 测试 ngx.req.get_headers()
func TestNgxReqGetHeaders(t *testing.T) {
	engine, err := NewEngine(DefaultConfig())
	require.NoError(t, err)
	defer engine.Close()

	reqCtx := createTestRequestCtx("GET", "/test", map[string]string{
		"Host":         "example.com",
		"X-Custom":     "custom-value",
		"Content-Type": "application/json",
	}, nil)

	coro, err := engine.NewCoroutine(reqCtx)
	require.NoError(t, err)
	defer coro.Close()

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

	err = coro.Execute(`
		local headers = ngx.req.get_headers()

		if headers.Host ~= "example.com" then
			error("expected Host=example.com, got " .. tostring(headers.Host))
		end

		if headers["X-Custom"] ~= "custom-value" then
			error("expected X-Custom=custom-value")
		end

		if headers["Content-Type"] ~= "application/json" then
			error("expected Content-Type=application/json")
		end
	`)
	assert.NoError(t, err)
}

// TestNgxReqSetHeader 测试 ngx.req.set_header()
func TestNgxReqSetHeader(t *testing.T) {
	engine, err := NewEngine(DefaultConfig())
	require.NoError(t, err)
	defer engine.Close()

	// 测试设置请求头
	reqCtx := createTestRequestCtx("GET", "/test", nil, nil)
	coro, err := engine.NewCoroutine(reqCtx)
	require.NoError(t, err)

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

	err = coro.Execute(`
		ngx.req.set_header("X-Custom-Header", "custom-value")
	`)
	assert.NoError(t, err)
	coro.Close()

	assert.Equal(t, "custom-value", string(reqCtx.Request.Header.Peek("X-Custom-Header")))

	// 测试使用 nil 清除请求头
	reqCtx2 := createTestRequestCtx("GET", "/test", map[string]string{
		"X-Custom-Header": "custom-value",
	}, nil)
	coro2, err := engine.NewCoroutine(reqCtx2)
	require.NoError(t, err)

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

	err = coro2.Execute(`
		ngx.req.set_header("X-Custom-Header", nil)
	`)
	assert.NoError(t, err)
	coro2.Close()

	assert.Equal(t, "", string(reqCtx2.Request.Header.Peek("X-Custom-Header")))
}

// TestNgxReqClearHeader 测试 ngx.req.clear_header()
func TestNgxReqClearHeader(t *testing.T) {
	engine, err := NewEngine(DefaultConfig())
	require.NoError(t, err)
	defer engine.Close()

	reqCtx := createTestRequestCtx("GET", "/test", map[string]string{
		"X-To-Clear": "value",
	}, nil)

	coro, err := engine.NewCoroutine(reqCtx)
	require.NoError(t, err)
	defer coro.Close()

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

	// 先验证头存在
	assert.Equal(t, "value", string(reqCtx.Request.Header.Peek("X-To-Clear")))

	// 清除头
	err = coro.Execute(`
		ngx.req.clear_header("X-To-Clear")
	`)
	assert.NoError(t, err)

	assert.Equal(t, "", string(reqCtx.Request.Header.Peek("X-To-Clear")))
}

// TestNgxReqGetBodyData 测试 ngx.req.get_body_data()
func TestNgxReqGetBodyData(t *testing.T) {
	engine, err := NewEngine(DefaultConfig())
	require.NoError(t, err)
	defer engine.Close()

	reqCtx := createTestRequestCtx("POST", "/test", nil, []byte("test body data"))

	coro, err := engine.NewCoroutine(reqCtx)
	require.NoError(t, err)
	defer coro.Close()

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

	err = coro.Execute(`
		local body = ngx.req.get_body_data()
		if body ~= "test body data" then
			error("expected 'test body data', got " .. tostring(body))
		end
	`)
	assert.NoError(t, err)
}

// TestNgxReqGetBodyDataEmpty 测试 ngx.req.get_body_data() 空请求体
func TestNgxReqGetBodyDataEmpty(t *testing.T) {
	engine, err := NewEngine(DefaultConfig())
	require.NoError(t, err)
	defer engine.Close()

	reqCtx := createTestRequestCtx("GET", "/test", nil, nil)

	coro, err := engine.NewCoroutine(reqCtx)
	require.NoError(t, err)
	defer coro.Close()

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

	err = coro.Execute(`
		local body = ngx.req.get_body_data()
		if body ~= nil then
			error("expected nil for empty body, got " .. tostring(body))
		end
	`)
	assert.NoError(t, err)
}

// TestNgxReqReadBody 测试 ngx.req.read_body()
func TestNgxReqReadBody(t *testing.T) {
	engine, err := NewEngine(DefaultConfig())
	require.NoError(t, err)
	defer engine.Close()

	reqCtx := createTestRequestCtx("POST", "/test", map[string]string{
		"Content-Length": "14",
	}, []byte("test body data"))

	coro, err := engine.NewCoroutine(reqCtx)
	require.NoError(t, err)
	defer coro.Close()

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

	// read_body 应该成功执行
	err = coro.Execute(`
		ngx.req.read_body()
	`)
	assert.NoError(t, err)

	// 验证请求体仍可访问
	body := reqCtx.Request.Body()
	assert.Equal(t, "test body data", string(body))
}

// TestNgxReqAPIIntegration 测试 ngx.req API 集成场景
func TestNgxReqAPIIntegration(t *testing.T) {
	engine, err := NewEngine(DefaultConfig())
	require.NoError(t, err)
	defer engine.Close()

	reqCtx := createTestRequestCtx("POST", "/api/users?limit=10&offset=20", map[string]string{
		"Content-Type": "application/json",
		"X-API-Key":    "secret123",
	}, []byte(`{"name":"test"}`))

	coro, err := engine.NewCoroutine(reqCtx)
	require.NoError(t, err)
	defer coro.Close()

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

	// 复杂场景：获取各种请求信息并修改
	err = coro.Execute(`
		-- 获取请求信息
		local method = ngx.req.get_method()
		local uri = ngx.req.get_uri()
		local args = ngx.req.get_uri_args()
		local headers = ngx.req.get_headers()

		-- 验证获取的信息
		if method ~= "POST" then
			error("method should be POST")
		end

		if uri ~= "/api/users" then
			error("uri should be /api/users, got " .. tostring(uri))
		end

		if args.limit ~= "10" or args.offset ~= "20" then
			error("args incorrect")
		end

		-- 注意：fasthttp 会标准化 header 名称，所以需要使用实际的 key
		if headers["Content-Type"] ~= "application/json" and headers["content-type"] ~= "application/json" then
			error("Content-Type header incorrect: " .. tostring(headers["Content-Type"]))
		end

		-- 修改请求
		ngx.req.set_header("X-Request-ID", "req-12345")
		ngx.req.set_uri("/api/v2/users")
	`)
	assert.NoError(t, err)

	// 在 Go 层验证修改
	assert.Equal(t, "/api/v2/users", string(reqCtx.URI().Path()))
	assert.Equal(t, "req-12345", string(reqCtx.Request.Header.Peek("X-Request-ID")))
}

// TestNgxReqMetrics 测试 ngx.req API 性能指标
func TestNgxReqMetrics(t *testing.T) {
	reqCtx := createTestRequestCtx("GET", "/test?a=1&b=2", nil, nil)
	api := newNgxReqAPI(reqCtx)

	L := glua.NewState()
	defer L.Close()

	// 创建 ngx 表
	ngx := L.NewTable()

	// 注册 API
	RegisterNgxReqAPI(L, api, ngx)

	// 将 ngx 设置到全局
	L.SetGlobal("ngx", ngx)

	// 调用各种 API
	L.DoString(`
		ngx.req.get_method()
		ngx.req.get_uri()
		ngx.req.get_uri_args()
	`)

	// 验证指标
	metrics := api.GetMetrics()
	assert.Greater(t, metrics.DirectCallCount, uint64(0), "应该有直接层调用")
	assert.Greater(t, metrics.CompatibleCallCount, uint64(0), "应该有兼容层调用")

	// 验证平均延迟
	directAvg := api.GetDirectLayerAvgNs()
	compatibleAvg := api.GetCompatibleLayerAvgNs()
	assert.GreaterOrEqual(t, directAvg, float64(0))
	assert.GreaterOrEqual(t, compatibleAvg, float64(0))

	// 验证性能比率
	ratio := api.GetPerformanceRatio()
	assert.GreaterOrEqual(t, ratio, float64(0))

	// 重置指标
	api.ResetMetrics()
	metrics = api.GetMetrics()
	assert.Equal(t, uint64(0), metrics.DirectCallCount)
	assert.Equal(t, uint64(0), metrics.CompatibleCallCount)
}

// TestNgxReqGetHeadersWithMaxHeaders 测试 ngx.req.get_headers(max_headers)
func TestNgxReqGetHeadersWithMaxHeaders(t *testing.T) {
	engine, err := NewEngine(DefaultConfig())
	require.NoError(t, err)
	defer engine.Close()

	// 创建带有多个头的请求
	reqCtx := &fasthttp.RequestCtx{}
	reqCtx.Request.Header.SetMethod("GET")
	reqCtx.Request.SetRequestURI("/test")
	reqCtx.Request.Header.Set("Header1", "value1")
	reqCtx.Request.Header.Set("Header2", "value2")
	reqCtx.Request.Header.Set("Header3", "value3")

	coro, err := engine.NewCoroutine(reqCtx)
	require.NoError(t, err)
	defer coro.Close()

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

	// 测试限制头数
	err = coro.Execute(`
		local headers = ngx.req.get_headers(2)
		local count = 0
		for k, v in pairs(headers) do
			count = count + 1
		end
		-- 应该最多返回 2 个头
		if count > 2 then
			error("expected at most 2 headers, got " .. count)
		end
	`)
	assert.NoError(t, err)
}

// TestNgxReqSetURIArgsWithArray 测试 ngx.req.set_uri_args() 使用数组值
func TestNgxReqSetURIArgsWithArray(t *testing.T) {
	engine, err := NewEngine(DefaultConfig())
	require.NoError(t, err)
	defer engine.Close()

	reqCtx := createTestRequestCtx("GET", "/test", nil, nil)

	coro, err := engine.NewCoroutine(reqCtx)
	require.NoError(t, err)
	defer coro.Close()

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

	// 测试使用包含数组的 table
	err = coro.Execute(`
		ngx.req.set_uri_args({ tags = { "a", "b", "c" }, page = 1 })
	`)
	assert.NoError(t, err)

	queryStr := string(reqCtx.URI().QueryString())
	assert.Contains(t, queryStr, "tags=a")
	assert.Contains(t, queryStr, "tags=b")
	assert.Contains(t, queryStr, "tags=c")
	assert.Contains(t, queryStr, "page=1")
}
