package main

import (
	"context"
	"crypto/rand"
	"encoding/hex"
)

// trace.go threads a per-request correlation id (and the small request facts the logger
// and the body-logging gate need) through context — the userver / OpenTelemetry idiom:
// mint once at the top of a request, and every log line below it (down to the HTTP call
// to the model) carries the same trace_id without passing a logger by hand. ctx is
// already plumbed through the whole request path (handleEvent → respond → generate →
// LLMClient.Complete → the transport), so a value placed here surfaces everywhere.
//
// The id is 16 random bytes rendered as 32 hex chars — the W3C Trace-Context / OTel
// trace-id shape — so the trace_id field maps straight onto an OpenTelemetry trace id if
// an exporter is added later (no log/field rename). Today this is a correlation key, not
// a full SpanContext: real distributed tracing would still add a span_id and traceparent
// propagation across services.

type ctxKey int

const reqInfoKey ctxKey = iota

// reqInfo is the per-request data carried in context: the trace id stamped on every log
// line, the sender (so the body-log lines stay filterable by user), and verbose —
// whether this sender is on the LOG_BODIES_USERS allowlist. verbose is decided once, at
// admission, so the deep transport never re-checks the allowlist; it just reads the flag.
type reqInfo struct {
	traceID string
	sender  string
	verbose bool
}

// withRequestTrace stamps the request's trace id + sender + body-logging decision onto
// ctx. Call it once per handled event; the value flows down through the per-room
// goroutine, the per-request deadline ctx (WithTimeout preserves values), and into the
// model transport.
func withRequestTrace(ctx context.Context, traceID, sender string, verbose bool) context.Context {
	return context.WithValue(ctx, reqInfoKey, reqInfo{traceID: traceID, sender: sender, verbose: verbose})
}

func reqInfoFromContext(ctx context.Context) (reqInfo, bool) {
	ri, ok := ctx.Value(reqInfoKey).(reqInfo)
	return ri, ok
}

// traceFromContext returns the request trace id, or "" when ctx carries none (startup,
// the appservice transaction handler) — the slog handler then simply omits trace_id.
func traceFromContext(ctx context.Context) string {
	if ri, ok := reqInfoFromContext(ctx); ok {
		return ri.traceID
	}
	return ""
}

// newTraceID mints a random 16-byte id as 32 hex chars (the OTel trace-id shape).
// crypto/rand.Read never returns an error and always fills the buffer (Go 1.24+: on an
// entropy failure it crashes the process rather than returning a short read), so ignoring
// the error is safe — the id is always fully random.
func newTraceID() string {
	var b [16]byte
	_, _ = rand.Read(b[:])
	return hex.EncodeToString(b[:])
}
