package integration

import (
	"encoding/json"
	"io"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/requestid"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/break/junhong_cmp_fiber/internal/middleware"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
	"github.com/break/junhong_cmp_fiber/pkg/logger"
)

// setupTestApp 创建用于测试的 Fiber 应用
func setupTestApp() *fiber.App {
	// 初始化日志器
	_ = logger.InitLoggers(
		"debug",
		true,
		logger.LogRotationConfig{
			Filename:   "tests/integration/logs/error_handler_test.log",
			MaxSize:    10,
			MaxBackups: 3,
			MaxAge:     7,
			Compress:   false,
		},
		logger.LogRotationConfig{
			Filename:   "tests/integration/logs/access_test.log",
			MaxSize:    10,
			MaxBackups: 3,
			MaxAge:     7,
			Compress:   false,
		},
	)

	appLogger := logger.GetAppLogger()

	app := fiber.New(fiber.Config{
		ErrorHandler: errors.SafeErrorHandler(appLogger),
	})

	// 注册中间件
	app.Use(middleware.Recover(appLogger))
	app.Use(requestid.New(requestid.Config{
		Generator: func() string {
			return uuid.NewString()
		},
	}))

	return app
}

// ErrorResponse 定义统一的错误响应结构
type ErrorResponse struct {
	Code      interface{} `json:"code"`
	Data      interface{} `json:"data"`
	Msg       string      `json:"msg"`
	Timestamp interface{} `json:"timestamp"`
}

// T029: 测试参数验证失败 -> 400 错误响应
func TestErrorHandler_ValidationError_Returns400(t *testing.T) {
	app := setupTestApp()

	// 创建测试路由：触发参数验证失败
	app.Post("/api/test/validation", func(c *fiber.Ctx) error {
		// 模拟参数验证失败场景
		return errors.New(
			errors.CodeInvalidParam, // 参数验证失败
			"用户名长度必须在 3-20 个字符之间",
		)
	})

	// 发起请求
	req := httptest.NewRequest("POST", "/api/test/validation", nil)
	req.Header.Set("Content-Type", "application/json")

	resp, err := app.Test(req, -1)
	require.NoError(t, err)
	defer func() { _ = resp.Body.Close() }()

	// 验证 HTTP 状态码
	assert.Equal(t, 400, resp.StatusCode, "参数验证失败应返回 400 状态码")

	// 解析响应 body
	body, err := io.ReadAll(resp.Body)
	require.NoError(t, err)

	var errResp ErrorResponse
	err = json.Unmarshal(body, &errResp)
	require.NoError(t, err, "响应应为有效的 JSON 格式")

	// 验证响应字段
	assert.NotEmpty(t, errResp.Code, "错误码不应为空")
	assert.Contains(t, errResp.Msg, "用户名长度", "错误消息应包含验证失败信息")
	assert.NotEmpty(t, errResp.Timestamp, "时间戳不应为空")

	// 验证响应头包含 Request ID
	requestID := resp.Header.Get("X-Request-ID")
	assert.NotEmpty(t, requestID, "响应头应包含 X-Request-ID")

	t.Logf("✓ 验证失败测试通过 - Code: %s, Msg: %s, RequestID: %s",
		errResp.Code, errResp.Msg, requestID)
}

// T030: 测试资源未找到 -> 404 错误响应
func TestErrorHandler_ResourceNotFound_Returns404(t *testing.T) {
	app := setupTestApp()

	// 创建测试路由：触发资源未找到
	app.Get("/api/test/users/:id", func(c *fiber.Ctx) error {
		userID := c.Params("id")
		// 模拟资源不存在场景
		return errors.New(
			errors.CodeNotFound, // 资源不存在
			"用户 ID "+userID+" 不存在",
		)
	})

	// 发起请求
	req := httptest.NewRequest("GET", "/api/test/users/99999", nil)
	resp, err := app.Test(req, -1)
	require.NoError(t, err)
	defer func() { _ = resp.Body.Close() }()

	// 验证 HTTP 状态码
	assert.Equal(t, 404, resp.StatusCode, "资源未找到应返回 404 状态码")

	// 解析响应 body
	body, err := io.ReadAll(resp.Body)
	require.NoError(t, err)

	var errResp ErrorResponse
	err = json.Unmarshal(body, &errResp)
	require.NoError(t, err, "响应应为有效的 JSON 格式")

	// 验证响应字段
	assert.NotEmpty(t, errResp.Code, "错误码不应为空")
	assert.Contains(t, errResp.Msg, "不存在", "错误消息应包含资源不存在信息")
	assert.NotEmpty(t, errResp.Timestamp, "时间戳不应为空")

	// 验证响应头包含 Request ID
	requestID := resp.Header.Get("X-Request-ID")
	assert.NotEmpty(t, requestID, "响应头应包含 X-Request-ID")

	t.Logf("✓ 资源未找到测试通过 - Code: %s, Msg: %s, RequestID: %s",
		errResp.Code, errResp.Msg, requestID)
}

// T031: 测试认证失败 -> 401 错误响应
func TestErrorHandler_AuthenticationFailed_Returns401(t *testing.T) {
	app := setupTestApp()

	// 创建测试路由：触发认证失败
	app.Get("/api/test/protected", func(c *fiber.Ctx) error {
		// 模拟 Token 无效场景
		return errors.New(
			errors.CodeUnauthorized, // 认证失败
			"Token 已过期或无效",
		)
	})

	// 发起请求（无 Token）
	req := httptest.NewRequest("GET", "/api/test/protected", nil)
	resp, err := app.Test(req, -1)
	require.NoError(t, err)
	defer func() { _ = resp.Body.Close() }()

	// 验证 HTTP 状态码
	assert.Equal(t, 401, resp.StatusCode, "认证失败应返回 401 状态码")

	// 解析响应 body
	body, err := io.ReadAll(resp.Body)
	require.NoError(t, err)

	var errResp ErrorResponse
	err = json.Unmarshal(body, &errResp)
	require.NoError(t, err, "响应应为有效的 JSON 格式")

	// 验证响应字段
	assert.NotEmpty(t, errResp.Code, "错误码不应为空")
	assert.Contains(t, errResp.Msg, "Token", "错误消息应包含认证失败信息")
	assert.NotEmpty(t, errResp.Timestamp, "时间戳不应为空")

	// 验证响应头包含 Request ID
	requestID := resp.Header.Get("X-Request-ID")
	assert.NotEmpty(t, requestID, "响应头应包含 X-Request-ID")

	t.Logf("✓ 认证失败测试通过 - Code: %s, Msg: %s, RequestID: %s",
		errResp.Code, errResp.Msg, requestID)
}

// T032: 验证所有错误响应格式一致性
func TestErrorHandler_ResponseFormatConsistency(t *testing.T) {
	app := setupTestApp()

	// 注册多种错误场景的测试路由
	testCases := []struct {
		name         string
		path         string
		method       string
		errorCode    int
		errorMsg     string
		expectedHTTP int
	}{
		{
			name:         "参数验证失败",
			path:         "/api/test/validation-error",
			method:       "POST",
			errorCode:    errors.CodeInvalidParam,
			errorMsg:     "参数验证失败",
			expectedHTTP: 400,
		},
		{
			name:         "数据格式错误",
			path:         "/api/test/format-error",
			method:       "POST",
			errorCode:    errors.CodeInvalidParam,
			errorMsg:     "JSON 格式错误",
			expectedHTTP: 400,
		},
		{
			name:         "资源不存在",
			path:         "/api/test/not-found",
			method:       "GET",
			errorCode:    errors.CodeNotFound,
			errorMsg:     "资源不存在",
			expectedHTTP: 404,
		},
		{
			name:         "认证失败",
			path:         "/api/test/auth-error",
			method:       "GET",
			errorCode:    errors.CodeUnauthorized,
			errorMsg:     "认证失败",
			expectedHTTP: 401,
		},
		{
			name:         "权限不足",
			path:         "/api/test/permission-error",
			method:       "GET",
			errorCode:    errors.CodeForbidden,
			errorMsg:     "权限不足",
			expectedHTTP: 403,
		},
		{
			name:         "数据库错误",
			path:         "/api/test/tx-error",
			method:       "GET",
			errorCode:    errors.CodeDatabaseError,
			errorMsg:     "数据库连接失败",
			expectedHTTP: 500,
		},
		{
			name:         "外部服务错误",
			path:         "/api/test/external-error",
			method:       "POST",
			errorCode:    errors.CodeServiceUnavailable,
			errorMsg:     "外部 API 调用失败",
			expectedHTTP: 503,
		},
	}

	// 为每个测试场景注册路由
	for _, tc := range testCases {
		tc := tc // 捕获循环变量
		switch tc.method {
		case "GET":
			app.Get(tc.path, func(c *fiber.Ctx) error {
				return errors.New(tc.errorCode, tc.errorMsg)
			})
		case "POST":
			app.Post(tc.path, func(c *fiber.Ctx) error {
				return errors.New(tc.errorCode, tc.errorMsg)
			})
		}
	}

	// 测试每个场景的响应格式
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// 发起请求
			req := httptest.NewRequest(tc.method, tc.path, nil)
			req.Header.Set("Content-Type", "application/json")

			resp, err := app.Test(req, -1)
			require.NoError(t, err)
			defer func() { _ = resp.Body.Close() }()

			// 验证 HTTP 状态码
			assert.Equal(t, tc.expectedHTTP, resp.StatusCode,
				"%s 应返回 %d 状态码", tc.name, tc.expectedHTTP)

			// 解析响应 body
			body, err := io.ReadAll(resp.Body)
			require.NoError(t, err)

			var errResp ErrorResponse
			err = json.Unmarshal(body, &errResp)
			require.NoError(t, err, "响应应为有效的 JSON 格式")

			// 验证响应字段完整性
			assert.NotEmpty(t, errResp.Code, "响应必须包含 code 字段")
			assert.NotEmpty(t, errResp.Msg, "响应必须包含 msg 字段")
			assert.NotEmpty(t, errResp.Timestamp, "响应必须包含 timestamp 字段")

			// 验证时间戳格式（RFC3339）
			_, err = time.Parse(time.RFC3339, errResp.Timestamp.(string))
			assert.NoError(t, err, "时间戳应为有效的 RFC3339 格式")

			// 验证响应头
			requestID := resp.Header.Get("X-Request-ID")
			assert.NotEmpty(t, requestID, "响应头应包含 X-Request-ID")

			contentType := resp.Header.Get("Content-Type")
			assert.Contains(t, contentType, "application/json",
				"Content-Type 应为 application/json")

			// 5xx 错误应该脱敏 - 不暴露原始错误消息,返回错误码对应的标准消息
			if tc.expectedHTTP >= 500 {
				// 验证不包含自定义错误消息(如 "数据库连接失败", "外部 API 调用失败")
				assert.NotContains(t, errResp.Msg, tc.errorMsg,
					"5xx 错误不应暴露自定义错误详情,应返回标准消息")
				// 验证返回的是标准错误消息(从 GetMessage 获取)
				assert.NotEmpty(t, errResp.Msg, "5xx 错误应返回标准消息")
			}

			t.Logf("✓ %s - 格式一致性验证通过 - Code: %s, Status: %d, RequestID: %s",
				tc.name, errResp.Code, resp.StatusCode, requestID)
		})
	}

	t.Log("✓ 所有错误响应格式一致性测试通过")
}

// T039: 创建测试端点触发 panic
func TestPanic_BasicPanicRecovery(t *testing.T) {
	app := setupTestApp()

	// 创建会触发 panic 的路由
	app.Get("/api/test/panic", func(c *fiber.Ctx) error {
		panic("模拟业务逻辑 panic")
	})

	// 发起请求
	req := httptest.NewRequest("GET", "/api/test/panic", nil)
	resp, err := app.Test(req, -1)
	require.NoError(t, err)
	defer func() { _ = resp.Body.Close() }()

	// 验证 panic 被捕获并转换为 500 错误
	assert.Equal(t, 500, resp.StatusCode, "panic 应返回 500 状态码")

	// 解析响应 body
	body, err := io.ReadAll(resp.Body)
	require.NoError(t, err)

	var errResp ErrorResponse
	err = json.Unmarshal(body, &errResp)
	require.NoError(t, err, "panic 响应应为有效的 JSON 格式")

	// 验证响应字段
	assert.NotEmpty(t, errResp.Code, "错误码不应为空")
	assert.NotEmpty(t, errResp.Msg, "错误消息不应为空")
	assert.NotEmpty(t, errResp.Timestamp, "时间戳不应为空")

	// 验证响应头包含 Request ID
	requestID := resp.Header.Get("X-Request-ID")
	assert.NotEmpty(t, requestID, "响应头应包含 X-Request-ID")

	t.Logf("✓ Panic 恢复测试通过 - Code: %s, Msg: %s, RequestID: %s",
		errResp.Code, errResp.Msg, requestID)
}

// T040: 测试 panic 恢复后服务继续运行
func TestPanic_ServiceContinuesAfterRecovery(t *testing.T) {
	app := setupTestApp()

	// 创建会触发 panic 的路由
	app.Get("/api/test/panic-endpoint", func(c *fiber.Ctx) error {
		panic("触发 panic")
	})

	// 创建正常的路由
	app.Get("/api/test/normal-endpoint", func(c *fiber.Ctx) error {
		return c.JSON(fiber.Map{
			"status":  "ok",
			"message": "服务正常运行",
		})
	})

	// 第一次请求：触发 panic
	panicReq := httptest.NewRequest("GET", "/api/test/panic-endpoint", nil)
	panicResp, err := app.Test(panicReq, -1)
	require.NoError(t, err)
	_ = panicResp.Body.Close()
	assert.Equal(t, 500, panicResp.StatusCode, "panic 应返回 500")

	// 第二次请求：验证服务仍然正常运行
	normalReq := httptest.NewRequest("GET", "/api/test/normal-endpoint", nil)
	normalResp, err := app.Test(normalReq, -1)
	require.NoError(t, err)
	defer func() { _ = normalResp.Body.Close() }()

	// 验证正常请求仍然成功
	assert.Equal(t, 200, normalResp.StatusCode, "panic 后正常请求应成功")

	// 验证响应内容
	body, err := io.ReadAll(normalResp.Body)
	require.NoError(t, err)

	var response map[string]interface{}
	err = json.Unmarshal(body, &response)
	require.NoError(t, err)

	assert.Equal(t, "ok", response["status"], "服务应正常运行")

	t.Log("✓ Panic 后服务继续运行测试通过")
}

// T041: 测试并发场景下的 panic 处理
func TestPanic_ConcurrentPanicHandling(t *testing.T) {
	app := setupTestApp()

	// 创建会随机 panic 的路由
	app.Get("/api/test/concurrent-panic/:id", func(c *fiber.Ctx) error {
		id := c.Params("id")
		// 奇数 ID 触发 panic，偶数 ID 正常返回
		if id == "1" || id == "3" || id == "5" || id == "7" || id == "9" {
			panic("并发 panic 测试")
		}
		return c.JSON(fiber.Map{"id": id, "status": "ok"})
	})

	// 并发发送 10 个请求
	const numRequests = 10
	results := make(chan int, numRequests)

	for i := 1; i <= numRequests; i++ {
		go func(id int) {
			req := httptest.NewRequest("GET", "/api/test/concurrent-panic/"+string(rune(id+'0')), nil)
			resp, err := app.Test(req, -1)
			if err != nil {
				results <- 0
				return
			}
			defer func() { _ = resp.Body.Close() }()
			results <- resp.StatusCode
		}(i)
	}

	// 收集结果
	var successCount, errorCount int
	for i := 0; i < numRequests; i++ {
		statusCode := <-results
		if statusCode == 200 {
			successCount++
		} else if statusCode == 500 {
			errorCount++
		}
	}

	// 验证结果：5 个成功（偶数 ID），5 个 panic 恢复（奇数 ID）
	assert.Equal(t, 5, successCount, "应有 5 个请求成功")
	assert.Equal(t, 5, errorCount, "应有 5 个 panic 被恢复")

	t.Logf("✓ 并发 Panic 处理测试通过 - 成功: %d, 错误: %d", successCount, errorCount)
}

// T042: 验证 panic 时的堆栈跟踪记录
func TestPanic_StackTraceLogging(t *testing.T) {
	app := setupTestApp()

	// 创建会触发 panic 的路由（在特定函数中）
	app.Get("/api/test/panic-with-stack", func(c *fiber.Ctx) error {
		// 调用一个会 panic 的函数以生成堆栈跟踪
		triggerPanicInNestedFunction()
		return nil
	})

	// 发起请求
	req := httptest.NewRequest("GET", "/api/test/panic-with-stack", nil)
	resp, err := app.Test(req, -1)
	require.NoError(t, err)
	defer func() { _ = resp.Body.Close() }()

	// 验证 panic 被捕获
	assert.Equal(t, 500, resp.StatusCode, "panic 应返回 500 状态码")

	// 验证响应格式
	body, err := io.ReadAll(resp.Body)
	require.NoError(t, err)

	var errResp ErrorResponse
	err = json.Unmarshal(body, &errResp)
	require.NoError(t, err)

	assert.NotEmpty(t, errResp.Code, "错误码不应为空")
	assert.NotEmpty(t, errResp.Msg, "错误消息不应为空")

	// 注意：堆栈跟踪会被记录到日志中，而不是返回给客户端
	// 这里我们验证错误消息已被脱敏，不包含内部实现细节
	assert.NotContains(t, errResp.Msg, "triggerPanicInNestedFunction",
		"5xx 错误消息不应暴露内部函数名")

	t.Log("✓ 堆栈跟踪记录测试通过 - 错误已脱敏，堆栈已记录到日志")
}

// triggerPanicInNestedFunction 是一个辅助函数，用于生成嵌套的堆栈跟踪
func triggerPanicInNestedFunction() {
	anotherNestedFunction()
}

func anotherNestedFunction() {
	panic("嵌套函数中的 panic")
}

// TestPanic_NilPointerDereference 测试空指针解引用 panic
func TestPanic_NilPointerDereference(t *testing.T) {
	app := setupTestApp()

	app.Get("/api/test/nil-pointer", func(c *fiber.Ctx) error {
		var ptr *string
		_ = *ptr // 触发空指针 panic
		return nil
	})

	req := httptest.NewRequest("GET", "/api/test/nil-pointer", nil)
	resp, err := app.Test(req, -1)
	require.NoError(t, err)
	defer func() { _ = resp.Body.Close() }()

	assert.Equal(t, 500, resp.StatusCode, "空指针 panic 应返回 500")

	t.Log("✓ 空指针 Panic 测试通过")
}

// TestPanic_ArrayOutOfBounds 测试数组越界 panic
func TestPanic_ArrayOutOfBounds(t *testing.T) {
	app := setupTestApp()

	app.Get("/api/test/out-of-bounds", func(c *fiber.Ctx) error {
		arr := []int{1, 2, 3}
		_ = arr[10] // 触发数组越界 panic
		return nil
	})

	req := httptest.NewRequest("GET", "/api/test/out-of-bounds", nil)
	resp, err := app.Test(req, -1)
	require.NoError(t, err)
	defer func() { _ = resp.Body.Close() }()

	assert.Equal(t, 500, resp.StatusCode, "数组越界 panic 应返回 500")

	t.Log("✓ 数组越界 Panic 测试通过")
}

// T045: 测试参数验证失败 -> Warn 级别日志
func TestErrorClassification_ValidationError_WarnLevel(t *testing.T) {
	app := setupTestApp()

	app.Post("/api/test/validation-warn", func(c *fiber.Ctx) error {
		// 模拟参数验证失败 (客户端错误 1xxx -> Warn 级别)
		return errors.New(
			errors.CodeInvalidParam,
			"用户名格式不正确",
		)
	})

	req := httptest.NewRequest("POST", "/api/test/validation-warn", nil)
	req.Header.Set("Content-Type", "application/json")

	resp, err := app.Test(req, -1)
	require.NoError(t, err)
	defer func() { _ = resp.Body.Close() }()

	// 验证 HTTP 状态码
	assert.Equal(t, 400, resp.StatusCode, "参数验证失败应返回 400")

	// 验证响应格式
	body, err := io.ReadAll(resp.Body)
	require.NoError(t, err)

	var errResp ErrorResponse
	err = json.Unmarshal(body, &errResp)
	require.NoError(t, err)

	assert.NotEmpty(t, errResp.Code, "错误码不应为空")
	assert.NotEmpty(t, errResp.Msg, "错误消息不应为空")

	t.Logf("✓ 参数验证失败测试通过 (Warn 级别) - Code: %s, Msg: %s", errResp.Code, errResp.Msg)
}

// T046: 测试权限不足 -> Warn 级别日志
func TestErrorClassification_PermissionDenied_WarnLevel(t *testing.T) {
	app := setupTestApp()

	app.Get("/api/test/permission-warn", func(c *fiber.Ctx) error {
		// 模拟权限不足 (客户端错误 1xxx -> Warn 级别)
		return errors.New(
			errors.CodeForbidden,
			"您没有权限访问此资源",
		)
	})

	req := httptest.NewRequest("GET", "/api/test/permission-warn", nil)
	resp, err := app.Test(req, -1)
	require.NoError(t, err)
	defer func() { _ = resp.Body.Close() }()

	// 验证 HTTP 状态码
	assert.Equal(t, 403, resp.StatusCode, "权限不足应返回 403")

	// 验证响应格式
	body, err := io.ReadAll(resp.Body)
	require.NoError(t, err)

	var errResp ErrorResponse
	err = json.Unmarshal(body, &errResp)
	require.NoError(t, err)

	assert.NotEmpty(t, errResp.Code, "错误码不应为空")
	assert.NotEmpty(t, errResp.Msg, "错误消息不应为空")
	// 客户端错误可以保留自定义消息,验证包含权限相关提示
	assert.Contains(t, errResp.Msg, "权限", "错误消息应包含权限相关提示")

	t.Logf("✓ 权限不足测试通过 (Warn 级别) - Code: %s, Msg: %s", errResp.Code, errResp.Msg)
}

// T047: 测试数据库错误 -> Error 级别日志
func TestErrorClassification_DatabaseError_ErrorLevel(t *testing.T) {
	app := setupTestApp()

	app.Get("/api/test/database-error", func(c *fiber.Ctx) error {
		// 模拟数据库错误 (服务端错误 2xxx -> Error 级别)
		return errors.New(
			errors.CodeDatabaseError,
			"pq: relation 'users' does not exist", // 敏感的数据库错误信息
		)
	})

	req := httptest.NewRequest("GET", "/api/test/database-error", nil)
	resp, err := app.Test(req, -1)
	require.NoError(t, err)
	defer func() { _ = resp.Body.Close() }()

	// 验证 HTTP 状态码
	assert.Equal(t, 500, resp.StatusCode, "数据库错误应返回 500")

	// 验证响应格式
	body, err := io.ReadAll(resp.Body)
	require.NoError(t, err)

	var errResp ErrorResponse
	err = json.Unmarshal(body, &errResp)
	require.NoError(t, err)

	assert.NotEmpty(t, errResp.Code, "错误码不应为空")
	assert.Equal(t, "数据库错误", errResp.Msg, "5xx 错误应返回标准消息")
	// 验证敏感信息已被隐藏
	assert.NotContains(t, errResp.Msg, "pq:", "不应暴露数据库驱动信息")
	assert.NotContains(t, errResp.Msg, "relation", "不应暴露数据库表结构")

	t.Logf("✓ 数据库错误测试通过 (Error 级别) - Code: %s, Msg: %s", errResp.Code, errResp.Msg)
}

// T048: 验证敏感信息隐藏 (数据库错误不暴露 SQL)
func TestErrorClassification_SensitiveInfoHidden(t *testing.T) {
	app := setupTestApp()

	testCases := []struct {
		name             string
		path             string
		errorCode        int
		sensitiveMsg     string
		expectedStatus   int
		expectedMsg      string
		shouldNotContain []string
	}{
		{
			name:             "数据库连接错误",
			path:             "/api/test/tx-connection",
			errorCode:        errors.CodeDatabaseError,
			sensitiveMsg:     "connection refused: tcp 192.168.1.100:5432",
			expectedStatus:   500,
			expectedMsg:      "数据库错误",
			shouldNotContain: []string{"192.168.1.100", "5432", "connection refused"},
		},
		{
			name:             "SQL 语法错误",
			path:             "/api/test/sql-syntax",
			errorCode:        errors.CodeDatabaseError,
			sensitiveMsg:     "syntax error at or near 'SELECT * FROM users WHERE id='",
			expectedStatus:   500,
			expectedMsg:      "数据库错误",
			shouldNotContain: []string{"SELECT", "FROM users", "syntax error"},
		},
		{
			name:             "Redis 连接错误",
			path:             "/api/test/redis-error",
			errorCode:        errors.CodeRedisError,
			sensitiveMsg:     "dial tcp 127.0.0.1:6379: connect: connection refused",
			expectedStatus:   500,
			expectedMsg:      "缓存服务错误",
			shouldNotContain: []string{"127.0.0.1", "6379", "dial tcp"},
		},
		{
			name:             "任务队列错误",
			path:             "/api/test/queue-error",
			errorCode:        errors.CodeTaskQueueError,
			sensitiveMsg:     "failed to enqueue task: redis: nil",
			expectedStatus:   500,
			expectedMsg:      "任务队列错误",
			shouldNotContain: []string{"enqueue", "redis: nil"},
		},
	}

	for _, tc := range testCases {
		tc := tc // 捕获循环变量
		app.Get(tc.path, func(c *fiber.Ctx) error {
			return errors.New(tc.errorCode, tc.sensitiveMsg)
		})
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			req := httptest.NewRequest("GET", tc.path, nil)
			resp, err := app.Test(req, -1)
			require.NoError(t, err)
			defer func() { _ = resp.Body.Close() }()

			assert.Equal(t, tc.expectedStatus, resp.StatusCode, "HTTP 状态码应正确")

			body, err := io.ReadAll(resp.Body)
			require.NoError(t, err)

			var errResp ErrorResponse
			err = json.Unmarshal(body, &errResp)
			require.NoError(t, err)

			// 验证返回的是标准消息,不是自定义的敏感消息
			assert.Equal(t, tc.expectedMsg, errResp.Msg, "应返回标准错误消息")

			// 验证敏感信息已被隐藏
			for _, sensitive := range tc.shouldNotContain {
				assert.NotContains(t, errResp.Msg, sensitive,
					"错误消息不应包含敏感信息: %s", sensitive)
			}

			t.Logf("✓ %s - 敏感信息已隐藏", tc.name)
		})
	}

	t.Log("✓ 所有敏感信息隐藏测试通过")
}

// T049: 测试限流错误 -> 429 响应
func TestErrorClassification_RateLimitExceeded_Returns429(t *testing.T) {
	app := setupTestApp()

	app.Get("/api/test/rate-limit", func(c *fiber.Ctx) error {
		// 模拟触发限流
		return errors.New(
			errors.CodeTooManyRequests,
			"您的请求过于频繁,请稍后重试",
		)
	})

	req := httptest.NewRequest("GET", "/api/test/rate-limit", nil)
	resp, err := app.Test(req, -1)
	require.NoError(t, err)
	defer func() { _ = resp.Body.Close() }()

	// 验证 HTTP 状态码
	assert.Equal(t, 429, resp.StatusCode, "限流应返回 429 状态码")

	// 验证响应格式
	body, err := io.ReadAll(resp.Body)
	require.NoError(t, err)

	var errResp ErrorResponse
	err = json.Unmarshal(body, &errResp)
	require.NoError(t, err)

	assert.NotEmpty(t, errResp.Code, "错误码不应为空")
	// 客户端错误可以保留自定义消息,验证包含限流相关提示
	assert.True(t,
		contains(errResp.Msg, "请求过多") || contains(errResp.Msg, "请求过于频繁"),
		"错误消息应包含限流提示")

	// 验证响应头
	requestID := resp.Header.Get("X-Request-ID")
	assert.NotEmpty(t, requestID, "响应头应包含 X-Request-ID")

	t.Logf("✓ 限流错误测试通过 - Code: %s, Status: %d, Msg: %s",
		errResp.Code, resp.StatusCode, errResp.Msg)
}

// contains 辅助函数用于字符串包含检查
func contains(s, substr string) bool {
	return len(s) >= len(substr) && (s == substr || len(substr) == 0 ||
		(len(s) > 0 && len(substr) > 0 && stringContains(s, substr)))
}

func stringContains(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}

// T050: 测试服务不可用 -> 503 响应
func TestErrorClassification_ServiceUnavailable_Returns503(t *testing.T) {
	app := setupTestApp()

	app.Get("/api/test/service-unavailable", func(c *fiber.Ctx) error {
		// 模拟服务不可用 (如外部 API 不可用)
		return errors.New(
			errors.CodeServiceUnavailable,
			"外部认证服务暂时不可用",
		)
	})

	req := httptest.NewRequest("GET", "/api/test/service-unavailable", nil)
	resp, err := app.Test(req, -1)
	require.NoError(t, err)
	defer func() { _ = resp.Body.Close() }()

	// 验证 HTTP 状态码
	assert.Equal(t, 503, resp.StatusCode, "服务不可用应返回 503 状态码")

	// 验证响应格式
	body, err := io.ReadAll(resp.Body)
	require.NoError(t, err)

	var errResp ErrorResponse
	err = json.Unmarshal(body, &errResp)
	require.NoError(t, err)

	assert.NotEmpty(t, errResp.Code, "错误码不应为空")
	// 5xx 错误应返回标准消息,而不是自定义消息
	assert.Equal(t, "服务暂时不可用", errResp.Msg, "应返回标准错误消息")
	assert.NotContains(t, errResp.Msg, "外部认证服务", "不应暴露内部服务细节")

	t.Logf("✓ 服务不可用错误测试通过 - Code: %s, Status: %d, Msg: %s",
		errResp.Code, resp.StatusCode, errResp.Msg)
}

// ===== Phase 6: User Story 4 - 错误追踪和调试支持 =====

// T054: 测试错误日志完整性（包含 Request ID）
func TestErrorTracking_LogCompleteness_IncludesRequestID(t *testing.T) {
	app := setupTestApp()

	app.Post("/api/test/error-log-completeness", func(c *fiber.Ctx) error {
		// 触发一个错误，验证日志包含 Request ID
		return errors.New(
			errors.CodeInvalidParam,
			"测试错误日志完整性",
		)
	})

	// 发起请求（带自定义 Request ID）
	req := httptest.NewRequest("POST", "/api/test/error-log-completeness", nil)
	customRequestID := "test-request-id-12345"
	req.Header.Set("X-Request-ID", customRequestID)
	req.Header.Set("Content-Type", "application/json")

	resp, err := app.Test(req, -1)
	require.NoError(t, err)
	defer func() { _ = resp.Body.Close() }()

	// 验证响应包含 Request ID
	responseRequestID := resp.Header.Get("X-Request-ID")
	assert.NotEmpty(t, responseRequestID, "响应头应包含 X-Request-ID")

	// 验证响应成功
	assert.Equal(t, 400, resp.StatusCode, "应返回 400 错误")

	body, err := io.ReadAll(resp.Body)
	require.NoError(t, err)

	var errResp ErrorResponse
	err = json.Unmarshal(body, &errResp)
	require.NoError(t, err)

	assert.NotEmpty(t, errResp.Code, "错误码不应为空")
	assert.NotEmpty(t, errResp.Msg, "错误消息不应为空")

	t.Logf("✓ 错误日志完整性测试通过 - RequestID: %s", responseRequestID)
	t.Log("  注意：实际的日志完整性需要检查日志文件，确认包含 request_id, method, path, ip 等字段")
}

// T055: 测试请求上下文记录（路径、方法、参数）
func TestErrorTracking_RequestContext_AllFields(t *testing.T) {
	app := setupTestApp()

	app.Post("/api/test/context-logging", func(c *fiber.Ctx) error {
		// 触发一个错误，验证日志包含完整的请求上下文
		return errors.New(
			errors.CodeInvalidParam,
			"测试请求上下文记录",
		)
	})

	// 构造带有 Query 参数和 Body 的请求
	requestBody := `{"username": "testuser", "email": "test@example.com"}`
	req := httptest.NewRequest("POST", "/api/test/context-logging?page=1&size=10",
		strings.NewReader(requestBody))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("User-Agent", "TestClient/1.0")

	resp, err := app.Test(req, -1)
	require.NoError(t, err)
	defer func() { _ = resp.Body.Close() }()

	// 验证响应
	assert.Equal(t, 400, resp.StatusCode, "应返回 400 错误")

	requestID := resp.Header.Get("X-Request-ID")
	assert.NotEmpty(t, requestID, "响应头应包含 X-Request-ID")

	t.Logf("✓ 请求上下文记录测试通过 - RequestID: %s", requestID)
	t.Log("  注意：需要检查日志文件，确认包含：")
	t.Log("    - method: POST")
	t.Log("    - path: /api/test/context-logging")
	t.Log("    - query: page=1&size=10")
	t.Log("    - body: 请求体内容（限制 50KB）")
	t.Log("    - user_agent: TestClient/1.0")
	t.Log("    - ip: 客户端 IP")
}

// T056: 测试 panic 堆栈跟踪记录（指明 panic 位置）
func TestErrorTracking_PanicStackTrace_IncludesLocation(t *testing.T) {
	app := setupTestApp()

	app.Get("/api/test/panic-stack-trace", func(c *fiber.Ctx) error {
		// 在一个可识别的函数中触发 panic
		panicInSpecificLocation()
		return nil
	})

	req := httptest.NewRequest("GET", "/api/test/panic-stack-trace", nil)
	resp, err := app.Test(req, -1)
	require.NoError(t, err)
	defer func() { _ = resp.Body.Close() }()

	// 验证 panic 被捕获
	assert.Equal(t, 500, resp.StatusCode, "panic 应返回 500 状态码")

	// 验证响应格式
	body, err := io.ReadAll(resp.Body)
	require.NoError(t, err)

	var errResp ErrorResponse
	err = json.Unmarshal(body, &errResp)
	require.NoError(t, err)

	assert.NotEmpty(t, errResp.Code, "错误码不应为空")
	assert.NotEmpty(t, errResp.Msg, "错误消息不应为空")

	// 验证响应中不包含堆栈跟踪（敏感信息已脱敏）
	assert.NotContains(t, errResp.Msg, "panicInSpecificLocation",
		"5xx 错误消息不应暴露内部函数名")

	requestID := resp.Header.Get("X-Request-ID")
	assert.NotEmpty(t, requestID, "响应头应包含 X-Request-ID")

	t.Logf("✓ Panic 堆栈跟踪测试通过 - RequestID: %s", requestID)
	t.Log("  注意：需要检查日志文件，确认包含：")
	t.Log("    - 完整的堆栈跟踪（runtime/debug.Stack()）")
	t.Log("    - 文件名和行号")
	t.Log("    - 函数名（panicInSpecificLocation）")
	t.Log("    - 从 panic 发生点到 recover 捕获点的完整调用链")
}

// panicInSpecificLocation 辅助函数，用于触发可追踪的 panic
func panicInSpecificLocation() {
	panic("这是一个特定位置的 panic，用于测试堆栈跟踪")
}

// T057: 测试使用 Request ID 追踪请求流程
func TestErrorTracking_RequestIDTracing_EndToEnd(t *testing.T) {
	app := setupTestApp()

	// 创建多个端点模拟完整的请求流程
	app.Post("/api/test/trace/step1", func(c *fiber.Ctx) error {
		// 第一步：参数验证失败
		return errors.New(
			errors.CodeInvalidParam,
			"步骤 1: 参数验证失败",
		)
	})

	app.Post("/api/test/trace/step2", func(c *fiber.Ctx) error {
		// 第二步：业务逻辑错误
		return errors.New(
			errors.CodeDatabaseError,
			"步骤 2: 数据库操作失败",
		)
	})

	app.Post("/api/test/trace/step3", func(c *fiber.Ctx) error {
		// 第三步：触发 panic
		panic("步骤 3: 发生 panic")
	})

	// 使用相同的 Request ID 追踪整个流程
	traceID := "trace-test-" + time.Now().Format("20060102-150405")

	testCases := []struct {
		name           string
		path           string
		expectedStatus int
		stepDesc       string
	}{
		{
			name:           "步骤1-参数验证",
			path:           "/api/test/trace/step1",
			expectedStatus: 400,
			stepDesc:       "参数验证失败应记录 Warn 级别日志",
		},
		{
			name:           "步骤2-数据库错误",
			path:           "/api/test/trace/step2",
			expectedStatus: 500,
			stepDesc:       "数据库错误应记录 Error 级别日志",
		},
		{
			name:           "步骤3-Panic恢复",
			path:           "/api/test/trace/step3",
			expectedStatus: 500,
			stepDesc:       "Panic 应记录 Error 级别日志和堆栈跟踪",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			req := httptest.NewRequest("POST", tc.path, nil)
			req.Header.Set("X-Request-ID", traceID)
			req.Header.Set("Content-Type", "application/json")

			resp, err := app.Test(req, -1)
			require.NoError(t, err)
			defer func() { _ = resp.Body.Close() }()

			// 验证 HTTP 状态码
			assert.Equal(t, tc.expectedStatus, resp.StatusCode,
				"%s 应返回 %d 状态码", tc.name, tc.expectedStatus)

			// 验证响应包含相同的 Request ID
			responseRequestID := resp.Header.Get("X-Request-ID")
			assert.NotEmpty(t, responseRequestID, "响应头应包含 X-Request-ID")

			body, err := io.ReadAll(resp.Body)
			require.NoError(t, err)

			var errResp ErrorResponse
			err = json.Unmarshal(body, &errResp)
			require.NoError(t, err)

			assert.NotEmpty(t, errResp.Code, "错误码不应为空")
			assert.NotEmpty(t, errResp.Msg, "错误消息不应为空")

			t.Logf("  ✓ %s 完成 - RequestID: %s, Status: %d",
				tc.name, responseRequestID, resp.StatusCode)
			t.Logf("    %s", tc.stepDesc)
		})
	}

	t.Logf("✓ Request ID 追踪测试通过 - TraceID: %s", traceID)
	t.Log("  注意：可以在日志文件中搜索 TraceID，追踪完整的请求流程：")
	t.Logf("    grep '%s' tests/integration/logs/error_handler_test.log", traceID)
	t.Log("  应该能看到 3 个步骤的完整日志记录")
}
