// Package logger provides a structured logger built on zap with OpenTelemetry
// trace correlation.
//
// In production (the default) logs are emitted as JSON at Info level. When the
// ENV environment variable is set to "development" the logger switches to a
// console encoder at Debug level for human-readable output.
package logger

import (
	"context"
	"os"

	"go.opentelemetry.io/otel/trace"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

// ctxKey is an unexported type so context values cannot collide with callers.
type ctxKey struct{}

// fallback is returned by FromContext when no logger has been injected into
// the context; it discards all output so callers never receive a nil logger.
var fallback = zap.NewNop()

// New creates a structured logger tagged with serviceName. The encoder and
// level are selected from the ENV environment variable as described in the
// package documentation.
func New(serviceName string) *zap.Logger {
	var cfg zap.Config
	if os.Getenv("ENV") == "development" {
		cfg = zap.NewDevelopmentConfig()
		cfg.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
	} else {
		cfg = zap.NewProductionConfig()
		cfg.Level = zap.NewAtomicLevelAt(zapcore.InfoLevel)
	}

	l, err := cfg.Build(zap.Fields(zap.String("service", serviceName)))
	if err != nil {
		// A logger that cannot be constructed is a fatal misconfiguration;
		// there is no safe way to continue.
		panic(err)
	}
	return l
}

// WithContext returns a copy of ctx that carries l so it can later be
// retrieved via FromContext.
func WithContext(ctx context.Context, l *zap.Logger) context.Context {
	return context.WithValue(ctx, ctxKey{}, l)
}

// FromContext returns the logger stored in ctx. When an active OpenTelemetry
// span is present, the returned logger is decorated with a trace_id field so
// log lines can be correlated to traces. If no logger was stored in ctx a
// no-op logger is returned.
func FromContext(ctx context.Context) *zap.Logger {
	if v, ok := ctx.Value(ctxKey{}).(*zap.Logger); ok && v != nil {
		if sc := trace.SpanContextFromContext(ctx); sc.HasTraceID() {
			return v.With(zap.String("trace_id", sc.TraceID().String()))
		}
		return v
	}
	return fallback
}
