package logging

import (
	"context"
	"log/slog"
)

type contextKey int

const (
	loggerKey contextKey = iota
	requestIDKey
	userIDKey
	traceIDKey
)

// NewContext returns a new context with the logger attached.
func NewContext(ctx context.Context, logger *Logger) context.Context {
	return context.WithValue(ctx, loggerKey, logger)
}

// FromContext extracts the logger from the context.
// Returns a no-op logger if none is found.
func FromContext(ctx context.Context) *Logger {
	if logger, ok := ctx.Value(loggerKey).(*Logger); ok {
		return logger
	}
	return Nop()
}

// WithRequestID adds a request ID to the context.
func WithRequestID(ctx context.Context, requestID string) context.Context {
	return context.WithValue(ctx, requestIDKey, requestID)
}

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

// WithUserID adds a user ID to the context.
func WithUserID(ctx context.Context, userID string) context.Context {
	return context.WithValue(ctx, userIDKey, userID)
}

// UserIDFromContext extracts the user ID from the context.
func UserIDFromContext(ctx context.Context) string {
	if id, ok := ctx.Value(userIDKey).(string); ok {
		return id
	}
	return ""
}

// WithTraceID adds a trace ID to the context.
func WithTraceID(ctx context.Context, traceID string) context.Context {
	return context.WithValue(ctx, traceIDKey, traceID)
}

// TraceIDFromContext extracts the trace ID from the context.
func TraceIDFromContext(ctx context.Context) string {
	if id, ok := ctx.Value(traceIDKey).(string); ok {
		return id
	}
	return ""
}

// ContextAttrs returns slog attributes from context values.
func ContextAttrs(ctx context.Context) []slog.Attr {
	var attrs []slog.Attr

	if id := RequestIDFromContext(ctx); id != "" {
		attrs = append(attrs, slog.String("request_id", id))
	}
	if id := UserIDFromContext(ctx); id != "" {
		attrs = append(attrs, slog.String("user_id", id))
	}
	if id := TraceIDFromContext(ctx); id != "" {
		attrs = append(attrs, slog.String("trace_id", id))
	}

	return attrs
}

// LoggerWithContext returns a logger enriched with context attributes.
func LoggerWithContext(ctx context.Context, logger *Logger) *Logger {
	attrs := ContextAttrs(ctx)
	if len(attrs) == 0 {
		return logger
	}

	args := make([]any, 0, len(attrs)*2)
	for _, attr := range attrs {
		args = append(args, attr.Key, attr.Value.Any())
	}
	return logger.With(args...)
}
