package errors

import (
	stderrors "errors"
	"runtime/debug"
	"time"

	"github.com/gofiber/fiber/v2"
	"go.uber.org/zap"
)

// SafeErrorHandler 返回受保护的 Fiber ErrorHandler
// 使用 defer/recover 防止 ErrorHandler 自身 panic 导致服务崩溃
func SafeErrorHandler(logger *zap.Logger) fiber.ErrorHandler {
	return func(c *fiber.Ctx, err error) error {
		// 使用 defer/recover 保护 ErrorHandler 自身
		defer func() {
			if r := recover(); r != nil {
				// ErrorHandler 自身发生 panic，记录日志并返回空响应
				logger.Error("ErrorHandler panic",
					zap.Any("panic", r),
					zap.String("stack", string(debug.Stack())),
				)
				// 返回 500 空响应体，避免泄露错误信息
				_ = c.Status(500).SendString("")
			}
		}()

		// 调用核心错误处理逻辑
		return handleError(c, err, logger)
	}
}

// handleError 核心错误处理逻辑
func handleError(c *fiber.Ctx, err error, logger *zap.Logger) error {
	// 1. 检查响应是否已发送
	if c.Response().StatusCode() != fiber.StatusOK || len(c.Response().Body()) > 0 {
		// 响应已发送，仅记录日志，不修改响应
		safeLog(logger, "响应已发送后发生错误",
			zap.Error(err),
			zap.Int("status", c.Response().StatusCode()),
			zap.Int("body_size", len(c.Response().Body())),
		)
		return nil
	}

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

	// 3. 错误类型分类和处理
	var code int
	var message string
	var httpStatus int

	var appErr *AppError
	var fiberErr *fiber.Error
	switch {
	case stderrors.As(err, &appErr):
		// 应用自定义错误
		code = appErr.Code
		message = appErr.Message
		httpStatus = GetHTTPStatus(appErr.Code)

		// 记录错误日志（包含完整上下文）
		logFields := append(errCtx.ToLogFields(),
			zap.Int("error_code", code),
			zap.Error(err),
		)

		// 根据错误类型决定日志级别
		logLevel := GetLogLevel(code)
		if logLevel == "error" {
			// 服务端错误 -> Error 级别
			safeLogWithLevel(logger, "error", "服务端错误", logFields...)
		} else {
			// 客户端错误 -> Warn 级别
			safeLogWithLevel(logger, "warn", "客户端错误", logFields...)
		}

	case stderrors.As(err, &fiberErr):
		// Fiber 框架错误
		httpStatus = fiberErr.Code
		code = mapHTTPStatusToCode(httpStatus)
		message = GetMessage(code, "zh")

		safeLog(logger, "Fiber 框架错误",
			append(errCtx.ToLogFields(),
				zap.Int("http_status", httpStatus),
				zap.String("fiber_message", fiberErr.Message),
			)...,
		)

	default:
		// 其他未知错误，默认为内部服务器错误
		code = CodeInternalError
		httpStatus = 500
		message = GetMessage(CodeInternalError, "zh")

		safeLog(logger, "未知错误",
			append(errCtx.ToLogFields(),
				zap.Error(err),
			)...,
		)
	}

	// 4. 敏感信息脱敏：所有 5xx 错误返回通用消息
	if httpStatus >= 500 {
		message = GetMessage(code, "zh")
	}

	// 5. 设置响应 Header X-Request-ID
	if errCtx.RequestID != "" {
		c.Set("X-Request-ID", errCtx.RequestID)
	}

	// 6. 返回统一 JSON 响应
	errResp := c.Status(httpStatus).JSON(fiber.Map{
		"code":      code,
		"data":      nil,
		"msg":       message,
		"timestamp": time.Now().Format(time.RFC3339),
	})
	c.Set(fiber.HeaderContentType, "application/json; charset=utf-8")
	return errResp
}

// safeLog 安全地记录日志，日志失败时静默处理（默认 Error 级别）
func safeLog(logger *zap.Logger, msg string, fields ...zap.Field) {
	safeLogWithLevel(logger, "error", msg, fields...)
}

// safeLogWithLevel 安全地记录指定级别的日志，日志失败时静默处理
func safeLogWithLevel(logger *zap.Logger, level string, msg string, fields ...zap.Field) {
	defer func() {
		if r := recover(); r != nil {
			// 日志系统 panic，静默丢弃，不阻塞响应
			// 不记录到任何地方，避免无限循环
		}
	}()

	switch level {
	case "warn":
		logger.Warn(msg, fields...)
	case "error":
		logger.Error(msg, fields...)
	case "info":
		logger.Info(msg, fields...)
	default:
		logger.Error(msg, fields...)
	}
}

// mapHTTPStatusToCode 将 HTTP 状态码映射为应用错误码
func mapHTTPStatusToCode(status int) int {
	switch status {
	case 400:
		return CodeInvalidParam
	case 401:
		return CodeUnauthorized
	case 403:
		return CodeForbidden
	case 404:
		return CodeNotFound
	case 409:
		return CodeConflict
	case 429:
		return CodeTooManyRequests
	case 503:
		return CodeServiceUnavailable
	case 504:
		return CodeTimeout
	default:
		if status >= 500 {
			return CodeInternalError
		}
		return CodeInvalidParam
	}
}
