package errors

import (
	"testing"

	"github.com/gofiber/fiber/v2"
	"github.com/valyala/fasthttp"
)

// TestFromFiberContext 测试从 Fiber Context 提取错误上下文
func TestFromFiberContext(t *testing.T) {
	app := fiber.New()

	tests := []struct {
		name           string
		setupRequest   func(*fasthttp.RequestCtx)
		expectedMethod string
		expectedPath   string
		hasRequestID   bool
	}{
		{
			name: "GET 请求",
			setupRequest: func(ctx *fasthttp.RequestCtx) {
				ctx.Request.Header.SetMethod("GET")
				ctx.Request.SetRequestURI("/api/v1/users")
				ctx.Request.Header.Set("X-Request-ID", "test-request-id-123")
			},
			expectedMethod: "GET",
			expectedPath:   "/api/v1/users",
			hasRequestID:   true,
		},
		{
			name: "POST 请求带查询参数",
			setupRequest: func(ctx *fasthttp.RequestCtx) {
				ctx.Request.Header.SetMethod("POST")
				ctx.Request.SetRequestURI("/api/v1/orders?status=pending")
				ctx.Request.Header.Set("X-Request-ID", "post-request-456")
			},
			expectedMethod: "POST",
			expectedPath:   "/api/v1/orders",
			hasRequestID:   true,
		},
		{
			name: "无 Request ID",
			setupRequest: func(ctx *fasthttp.RequestCtx) {
				ctx.Request.Header.SetMethod("DELETE")
				ctx.Request.SetRequestURI("/api/v1/tasks/123")
			},
			expectedMethod: "DELETE",
			expectedPath:   "/api/v1/tasks/123",
			hasRequestID:   false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// 创建 fasthttp 请求上下文
			fctx := &fasthttp.RequestCtx{}
			tt.setupRequest(fctx)

			// 创建 Fiber 上下文
			c := app.AcquireCtx(fctx)
			defer app.ReleaseCtx(c)

			// 提取错误上下文
			errCtx := FromFiberContext(c)

			// 验证方法
			if errCtx.Method != tt.expectedMethod {
				t.Errorf("Method = %q, expected %q", errCtx.Method, tt.expectedMethod)
			}

			// 验证路径
			if errCtx.Path != tt.expectedPath {
				t.Errorf("Path = %q, expected %q", errCtx.Path, tt.expectedPath)
			}

			// 验证 Request ID
			if tt.hasRequestID && errCtx.RequestID == "" {
				t.Error("Expected Request ID, but got empty string")
			}
			if !tt.hasRequestID && errCtx.RequestID != "" {
				t.Errorf("Expected no Request ID, but got %q", errCtx.RequestID)
			}

			// 验证 IP 地址不为空
			if errCtx.IP == "" {
				t.Error("Expected IP address, but got empty string")
			}
		})
	}
}

// TestErrorContextToLogFields 测试错误上下文转换为日志字段
func TestErrorContextToLogFields(t *testing.T) {
	tests := []struct {
		name           string
		ctx            *ErrorContext
		expectedFields int // 期望的字段数量
		hasQuery       bool
		hasUserAgent   bool
		hasUserID      bool
	}{
		{
			name: "完整的错误上下文",
			ctx: &ErrorContext{
				RequestID: "test-123",
				Method:    "POST",
				Path:      "/api/v1/users",
				IP:        "192.168.1.100",
				Query:     "status=active",
				UserAgent: "Mozilla/5.0",
				UserID:    "user-456",
			},
			expectedFields: 7, // request_id, method, path, ip, query, user_agent, user_id
			hasQuery:       true,
			hasUserAgent:   true,
			hasUserID:      true,
		},
		{
			name: "无查询参数",
			ctx: &ErrorContext{
				RequestID: "test-456",
				Method:    "GET",
				Path:      "/api/v1/orders",
				IP:        "10.0.0.1",
				Query:     "",
			},
			expectedFields: 4, // request_id, method, path, ip
			hasQuery:       false,
			hasUserAgent:   false,
			hasUserID:      false,
		},
		{
			name: "空 Request ID",
			ctx: &ErrorContext{
				RequestID: "",
				Method:    "DELETE",
				Path:      "/api/v1/tasks/123",
				IP:        "127.0.0.1",
				Query:     "",
			},
			expectedFields: 4, // request_id (空字符串), method, path, ip
			hasQuery:       false,
			hasUserAgent:   false,
			hasUserID:      false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			fields := tt.ctx.ToLogFields()

			// 验证字段数量
			if len(fields) != tt.expectedFields {
				t.Errorf("Field count = %d, expected %d", len(fields), tt.expectedFields)
			}

			// 验证必需字段存在
			if len(fields) < 4 {
				t.Error("Expected at least 4 required fields (request_id, method, path, ip)")
			}
		})
	}
}

// TestFromFiberContextWithUserAgent 测试带 User-Agent 的错误上下文提取
func TestFromFiberContextWithUserAgent(t *testing.T) {
	app := fiber.New()

	tests := []struct {
		name              string
		method            string
		path              string
		userAgent         string
		expectedUserAgent bool
	}{
		{
			name:              "有 User-Agent",
			method:            "GET",
			path:              "/api/v1/users",
			userAgent:         "Mozilla/5.0",
			expectedUserAgent: true,
		},
		{
			name:              "无 User-Agent",
			method:            "GET",
			path:              "/api/v1/users/123",
			userAgent:         "",
			expectedUserAgent: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// 创建 fasthttp 请求上下文
			fctx := &fasthttp.RequestCtx{}
			fctx.Request.Header.SetMethod(tt.method)
			fctx.Request.SetRequestURI(tt.path)
			if tt.userAgent != "" {
				fctx.Request.Header.Set("User-Agent", tt.userAgent)
			}

			// 创建 Fiber 上下文
			c := app.AcquireCtx(fctx)
			defer app.ReleaseCtx(c)

			// 提取错误上下文
			errCtx := FromFiberContext(c)

			// 验证 User-Agent
			if tt.expectedUserAgent && errCtx.UserAgent == "" {
				t.Error("Expected User-Agent, but got empty")
			}
			if !tt.expectedUserAgent && errCtx.UserAgent != "" {
				t.Errorf("Expected no User-Agent, but got %q", errCtx.UserAgent)
			}
		})
	}
}

// BenchmarkFromFiberContext 基准测试错误上下文提取性能
func BenchmarkFromFiberContext(b *testing.B) {
	app := fiber.New()

	// 创建测试请求
	fctx := &fasthttp.RequestCtx{}
	fctx.Request.Header.SetMethod("POST")
	fctx.Request.SetRequestURI("/api/v1/users?status=active&limit=10")
	fctx.Request.Header.Set("X-Request-ID", "benchmark-request-id")
	fctx.Request.SetBodyString(`{"username":"test","email":"test@example.com"}`)

	c := app.AcquireCtx(fctx)
	defer app.ReleaseCtx(c)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = FromFiberContext(c)
	}
}

// BenchmarkErrorContextToLogFields 基准测试日志字段转换性能
func BenchmarkErrorContextToLogFields(b *testing.B) {
	ctx := &ErrorContext{
		RequestID: "benchmark-123",
		Method:    "POST",
		Path:      "/api/v1/users",
		IP:        "192.168.1.100",
		Query:     "status=active&limit=10",
		UserAgent: "Mozilla/5.0",
		UserID:    "user-456",
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = ctx.ToLogFields()
	}
}
