// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package middleware

import (
	"context"
	"net/http"
	"unicode"

	"code.gitea.io/gitea/modules/setting"

	"github.com/google/uuid"
)

type requestIDKeyType struct{}

var requestIDKey = requestIDKeyType{}

// RequestIDHeader is the header name for request ID
const RequestIDHeader = "X-Request-ID"

// maxRequestIDByteLength is the maximum length for a request ID
const maxRequestIDByteLength = 40

// GetRequestID returns the request ID from context
func GetRequestID(ctx context.Context) string {
	if id, ok := ctx.Value(requestIDKey).(string); ok {
		return id
	}
	return ""
}

// isSafeRequestID checks if the request ID contains only printable characters
func isSafeRequestID(id string) bool {
	for _, r := range id {
		if !unicode.IsPrint(r) {
			return false
		}
	}
	return true
}

// parseOrGenerateRequestID extracts request ID from headers or generates a new one
func parseOrGenerateRequestID(req *http.Request) string {
	// Try to get from configured headers
	for _, key := range setting.Log.RequestIDHeaders {
		if id := req.Header.Get(key); id != "" {
			if isSafeRequestID(id) {
				if len(id) > maxRequestIDByteLength {
					return id[:maxRequestIDByteLength]
				}
				return id
			}
		}
	}

	// Also check X-Request-ID header explicitly
	if id := req.Header.Get(RequestIDHeader); id != "" {
		if isSafeRequestID(id) {
			if len(id) > maxRequestIDByteLength {
				return id[:maxRequestIDByteLength]
			}
			return id
		}
	}

	// Generate a new request ID (short form of UUID)
	id := uuid.New()
	return id.String()[:8]
}

// RequestID returns a middleware that sets X-Request-ID header
func RequestID() func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			requestID := parseOrGenerateRequestID(req)

			// Store in context
			ctx := context.WithValue(req.Context(), requestIDKey, requestID)
			req = req.WithContext(ctx)

			// Set response header
			w.Header().Set(RequestIDHeader, requestID)

			next.ServeHTTP(w, req)
		})
	}
}
