package observability

import (
	"context"
	"os"
	"time"

	"github.com/rs/zerolog"
	"go.opentelemetry.io/otel/trace"
)

// Logger is a wrapper around zerolog.Logger to provide a consistent logging interface.
type Logger struct {
	*zerolog.Logger
}

// NewLogger creates a new Logger instance.
// It writes to a human-friendly console in "development" environment,
// and writes JSON to stdout otherwise.
func NewLogger(serviceName, environment string) *Logger {
	var logger zerolog.Logger
	if environment == "development" {
		logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}).With().
			Timestamp().
			Str("service", serviceName).
			Logger()
	} else {
		zerolog.TimeFieldFormat = time.RFC3339
		logger = zerolog.New(os.Stdout).With().
			Timestamp().
			Str("service", serviceName).
			Logger()
	}

	return &Logger{&logger}
}

// Ctx returns a new logger with context-specific fields, such as trace and span IDs.
func (l *Logger) Ctx(ctx context.Context) *Logger {
	log := l.Logger // log is a *zerolog.Logger
	span := trace.SpanFromContext(ctx)
	if span.SpanContext().IsValid() {
		// .Logger() returns a value, not a pointer.
		// We create a new logger value...
		newLogger := log.With().
			Str("trace_id", span.SpanContext().TraceID().String()).
			Str("span_id", span.SpanContext().SpanID().String()).
			Logger()
		// ...and then use its address.
		log = &newLogger
	}
	// `log` is now the correct *zerolog.Logger, so we wrap it.
	return &Logger{log}
}

// With adds a key-value pair to the logger's context.
func (l *Logger) With(key string, value interface{}) *Logger {
	newLogger := l.Logger.With().Interface(key, value).Logger()
	return &Logger{&newLogger}
}