// Package adapter 提供 HTTP/2 和 HTTP/3 适配器共享组件的测试。
//
// 该文件测试 CommonAdapter 的各项功能，包括：
//   - NewCommonAdapter 构造函数
//   - ResetContext 上下文重置
//   - StreamRequestBody 流式请求体处理
//   - GetContext/PutContext 池操作
//   - 并发安全性
//   - DefaultBodyThreshold 常量
//
// 作者：xfy
package adapter

import (
	"bytes"
	"errors"
	"io"
	"net/http"
	"strings"
	"sync"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/valyala/fasthttp"
)

// TestDefaultBodyThreshold 测试请求体大小阈值常量
func TestDefaultBodyThreshold(t *testing.T) {
	assert.Equal(t, 64*1024, DefaultBodyThreshold, "DefaultBodyThreshold 应该等于 64KB")
}

// TestNewCommonAdapter 测试构造函数
func TestNewCommonAdapter(t *testing.T) {
	a := NewCommonAdapter()

	require.NotNil(t, a, "返回值不应为 nil")
	require.NotNil(t, a.CtxPool, "CtxPool 应该被初始化")

	// 验证 pool 能创建 *fasthttp.RequestCtx
	obj := a.CtxPool.Get()
	ctx, ok := obj.(*fasthttp.RequestCtx)
	assert.True(t, ok, "CtxPool.New 应该返回 *fasthttp.RequestCtx")
	assert.NotNil(t, ctx, "从 pool 获取的 ctx 不应为 nil")
}

// TestResetContext 测试上下文重置逻辑
func TestResetContext(t *testing.T) {
	a := NewCommonAdapter()
	ctx := &fasthttp.RequestCtx{}

	// 模拟使用过的状态
	ctx.Request.SetRequestURI("/test?q=1")
	ctx.Request.Header.Set("Content-Type", "text/plain")
	ctx.Request.SetBody([]byte("hello"))
	ctx.Response.SetBody([]byte("world"))
	ctx.Response.SetStatusCode(200)
	ctx.SetUserValue("key", "value")

	// 重置
	a.ResetContext(ctx)

	// 验证请求状态已清除
	assert.Equal(t, "/", string(ctx.Request.URI().Path()), "请求路径应被重置为 /")
	assert.Equal(t, "", string(ctx.Request.Header.Peek("Content-Type")), "请求头应被清除")
	assert.Equal(t, 0, len(ctx.Request.Body()), "请求体应被清除")

	// 验证响应状态已清除
	assert.Equal(t, 0, len(ctx.Response.Body()), "响应体应被清除")
	assert.Equal(t, 200, ctx.Response.StatusCode(), "响应状态码应被重置为 200")

	// 验证用户值已清除
	assert.Nil(t, ctx.UserValue("key"), "用户值应被清除")
}

// TestResetContext_DisableNormalizing 测试重置时禁用头部规范化
func TestResetContext_DisableNormalizing(t *testing.T) {
	a := NewCommonAdapter()
	ctx := &fasthttp.RequestCtx{}

	a.ResetContext(ctx)

	// 禁用规范化后，设置混合大小写头部应保持原样
	ctx.Request.Header.Set("Content-Type", "text/plain")
	// DisableNormalizing 是通过方法调用的，验证它被调用即可
	// 这里确认 ctx 可以正常使用
	assert.Equal(t, "text/plain", string(ctx.Request.Header.Peek("Content-Type")))
}

// TestStreamRequestBody 测试流式请求体处理的表驱动测试
func TestStreamRequestBody(t *testing.T) {
	a := NewCommonAdapter()

	tests := []struct {
		name          string
		body          io.Reader
		contentLength int64
		wantBody      string
		wantBodySet   bool
	}{
		{
			name:          "nil body 应该跳过处理",
			body:          nil,
			contentLength: 0,
			wantBody:      "",
			wantBodySet:   false,
		},
		{
			name:          "NoBody 应该跳过处理",
			body:          http.NoBody,
			contentLength: 0,
			wantBody:      "",
			wantBodySet:   false,
		},
		{
			name:          "空请求体",
			body:          strings.NewReader(""),
			contentLength: 0,
			wantBody:      "",
			wantBodySet:   false,
		},
		{
			name:          "小请求体直接读取",
			body:          strings.NewReader("hello world"),
			contentLength: 11,
			wantBody:      "hello world",
			wantBodySet:   true,
		},
		{
			name:          "恰好等于阈值",
			body:          bytes.NewReader(make([]byte, DefaultBodyThreshold)),
			contentLength: DefaultBodyThreshold,
			wantBody:      string(make([]byte, DefaultBodyThreshold)),
			wantBodySet:   true,
		},
		{
			name:          "超过阈值走流式路径",
			body:          bytes.NewReader(make([]byte, DefaultBodyThreshold+1)),
			contentLength: DefaultBodyThreshold + 1,
			wantBody:      string(make([]byte, DefaultBodyThreshold+1)),
			wantBodySet:   true,
		},
		{
			name:          "未知 ContentLength 走流式路径",
			body:          strings.NewReader("unknown length body"),
			contentLength: -1,
			wantBody:      "unknown length body",
			wantBodySet:   true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var body io.ReadCloser
			if tt.body != nil {
				body = io.NopCloser(tt.body)
			}

			r := &http.Request{
				Body:          body,
				ContentLength: tt.contentLength,
			}

			ctx := &fasthttp.RequestCtx{}
			a.StreamRequestBody(r, ctx)

			if tt.wantBodySet {
				assert.Equal(t, tt.wantBody, string(ctx.Request.Body()), "请求体内容应匹配")
			} else {
				assert.Equal(t, 0, len(ctx.Request.Body()), "请求体应为空")
			}
		})
	}
}

// TestStreamRequestBody_ReadError 测试读取请求体时的错误处理
func TestStreamRequestBody_ReadError(t *testing.T) {
	a := NewCommonAdapter()

	// 模拟读取错误
	errReader := &errorReader{err: errors.New("read error")}
	r := &http.Request{
		Body:          io.NopCloser(errReader),
		ContentLength: -1, // 走流式路径
	}

	ctx := &fasthttp.RequestCtx{}
	// 不应该 panic
	a.StreamRequestBody(r, ctx)
}

// errorReader 是一个始终返回错误的 io.Reader
type errorReader struct {
	err error
}

func (r *errorReader) Read(_ []byte) (int, error) {
	return 0, r.err
}

// TestStreamRequestBody_PartialReadError 测试部分读取后出错
func TestStreamRequestBody_PartialReadError(t *testing.T) {
	a := NewCommonAdapter()

	// 先读一些数据，然后出错
	pr := &partialErrorReader{data: []byte("partial"), err: errors.New("broken")}
	r := &http.Request{
		Body:          io.NopCloser(pr),
		ContentLength: -1,
	}

	ctx := &fasthttp.RequestCtx{}
	a.StreamRequestBody(r, ctx)

	// 部分读取的数据应该保留
	assert.Equal(t, "partial", string(ctx.Request.Body()), "已读取的部分数据应保留")
}

// partialErrorReader 先返回数据，再返回错误
type partialErrorReader struct {
	data []byte
	err  error
	read bool
}

func (r *partialErrorReader) Read(p []byte) (int, error) {
	if !r.read {
		r.read = true
		n := copy(p, r.data)
		return n, r.err
	}
	return 0, r.err
}

// TestGetContext 测试从 pool 获取上下文
func TestGetContext(t *testing.T) {
	a := NewCommonAdapter()

	ctx, ok := a.GetContext()
	assert.True(t, ok, "首次获取应该成功")
	assert.NotNil(t, ctx, "返回的 ctx 不应为 nil")

	// 获取的类型正确
	_, ok2 := interface{}(ctx).(*fasthttp.RequestCtx)
	assert.True(t, ok2, "返回值类型应该是 *fasthttp.RequestCtx")
}

// TestGetContext_TypeAssertion 测试 pool 类型断言失败的降级
func TestGetContext_TypeAssertion(t *testing.T) {
	a := NewCommonAdapter()

	// 手动往 pool 放入错误类型
	a.CtxPool.Put("wrong type")

	ctx, ok := a.GetContext()
	assert.False(t, ok, "类型断言应该失败")
	assert.NotNil(t, ctx, "即使断言失败，也应该返回新创建的 ctx")
}

// TestPutContext 测试将上下文放回 pool
func TestPutContext(t *testing.T) {
	a := NewCommonAdapter()

	ctx, _ := a.GetContext()
	ctx.Request.SetRequestURI("/test")

	// 放回
	a.PutContext(ctx)

	// 再次获取，应该能拿到（可能是同一个）
	ctx2, _ := a.GetContext()
	assert.NotNil(t, ctx2, "放回后应能重新获取")
}

// TestGetContext_PutAndGet 测试完整的获取-放回-再获取流程
func TestGetContext_PutAndGet(t *testing.T) {
	a := NewCommonAdapter()

	// 获取一个 ctx
	ctx1, ok1 := a.GetContext()
	require.True(t, ok1, "首次获取应该成功")

	// 放回
	a.PutContext(ctx1)

	// 再次获取，可能拿到同一个
	ctx2, ok2 := a.GetContext()
	assert.True(t, ok2, "再次获取应该成功")
	assert.NotNil(t, ctx2)
}

// TestConcurrentPoolAccess 测试并发访问 pool 的安全性
func TestConcurrentPoolAccess(t *testing.T) {
	a := NewCommonAdapter()
	const goroutines = 100

	var wg sync.WaitGroup
	wg.Add(goroutines)

	for i := 0; i < goroutines; i++ {
		go func() {
			defer wg.Done()

			ctx, ok := a.GetContext()
			if !ok {
				ctx = &fasthttp.RequestCtx{}
			}

			// 模拟使用
			ctx.Request.SetRequestURI("/concurrent")
			a.ResetContext(ctx)

			a.PutContext(ctx)
		}()
	}

	wg.Wait()
}

// TestConcurrentStreamRequestBody 测试并发流式请求体处理
func TestConcurrentStreamRequestBody(t *testing.T) {
	a := NewCommonAdapter()
	const goroutines = 50

	var wg sync.WaitGroup
	wg.Add(goroutines)

	for i := 0; i < goroutines; i++ {
		go func() {
			defer wg.Done()

			body := strings.NewReader("concurrent body data")
			r := &http.Request{
				Body:          io.NopCloser(body),
				ContentLength: 19,
			}

			ctx := &fasthttp.RequestCtx{}
			a.StreamRequestBody(r, ctx)
			assert.Equal(t, "concurrent body data", string(ctx.Request.Body()))
		}()
	}

	wg.Wait()
}

// TestStreamRequestBody_ClosesBody 测试请求体是否被正确关闭
func TestStreamRequestBody_ClosesBody(t *testing.T) {
	a := NewCommonAdapter()

	closeTracker := &trackableReader{data: strings.NewReader("data")}
	r := &http.Request{
		Body:          closeTracker,
		ContentLength: 4,
	}

	ctx := &fasthttp.RequestCtx{}
	a.StreamRequestBody(r, ctx)

	assert.True(t, closeTracker.closed, "请求体应该被关闭")
}

// trackableReader 追踪关闭状态的 io.ReadCloser
type trackableReader struct {
	data   io.Reader
	closed bool
}

func (r *trackableReader) Read(p []byte) (int, error) {
	return r.data.Read(p)
}

func (r *trackableReader) Close() error {
	r.closed = true
	return nil
}
