// Package requestid carries a per-request identifier across the gin handler
// chain.
//
// The middleware reads the inbound `X-Request-ID` header, generates a UUIDv4
// when absent, stores the value on the gin context, and reflects it on the
// response. Downstream code retrieves the identifier through FromContext.
package requestid

import (
	"context"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
)

// Header is the canonical case-correct header name carrying the request id.
const Header = "X-Request-ID"

// ginContextKey is the gin.Context key under which the resolved request id is
// stored. The value is a string. The key is exported in lowercase form so that
// it never collides with handler-level keys; consumers should prefer
// FromContext rather than reading the gin context directly.
const ginContextKey = "backend.request_id"

// requestIDContextKey is the unexported context.Context key used when the
// resolved request id is propagated outside gin (background goroutines,
// downstream client calls). The unexported value type prevents accidental
// collisions across packages.
type requestIDContextKey struct{}

// Middleware returns the gin middleware that resolves and propagates the
// request id.
func Middleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		requestID := strings.TrimSpace(c.GetHeader(Header))
		if requestID == "" {
			requestID = uuid.NewString()
		}

		c.Set(ginContextKey, requestID)
		c.Writer.Header().Set(Header, requestID)
		c.Request = c.Request.WithContext(WithValue(c.Request.Context(), requestID))
		c.Next()
	}
}

// FromContext returns the request id stored on ctx by Middleware. The boolean
// reports whether an id was found. Consumers must always check the boolean
// before using the returned string.
func FromContext(ctx context.Context) (string, bool) {
	if ctx == nil {
		return "", false
	}
	value, ok := ctx.Value(requestIDContextKey{}).(string)
	if !ok || value == "" {
		return "", false
	}
	return value, true
}

// FromGin returns the request id stored on the gin context by Middleware. The
// boolean reports whether an id was found.
func FromGin(c *gin.Context) (string, bool) {
	if c == nil {
		return "", false
	}
	raw, ok := c.Get(ginContextKey)
	if !ok {
		return "", false
	}
	value, ok := raw.(string)
	if !ok || value == "" {
		return "", false
	}
	return value, true
}

// WithValue stores requestID on ctx under the package-private context key.
// Exposed primarily for tests that build a context outside the middleware.
func WithValue(ctx context.Context, requestID string) context.Context {
	return context.WithValue(ctx, requestIDContextKey{}, requestID)
}
