package tracing

import (
	"context"
	"fmt"
	"strings"
	"time"

	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/trace"

	"cdr.dev/slog"
)

type SlogSink struct{}

var _ slog.Sink = SlogSink{}

// LogEntry implements slog.Sink. All entries are added as events to the span
// in the context. If no span is present, the entry is dropped.
func (SlogSink) LogEntry(ctx context.Context, e slog.SinkEntry) {
	span := trace.SpanFromContext(ctx)
	if !span.IsRecording() {
		// If the span is a noopSpan or isn't recording, we don't want to
		// compute the attributes (which is expensive) below.
		return
	}

	attributes := []attribute.KeyValue{
		attribute.String("slog.time", e.Time.Format(time.RFC3339Nano)),
		attribute.String("slog.logger", strings.Join(e.LoggerNames, ".")),
		attribute.String("slog.level", e.Level.String()),
		attribute.String("slog.message", e.Message),
		attribute.String("slog.func", e.Func),
		attribute.String("slog.file", e.File),
		attribute.Int64("slog.line", int64(e.Line)),
	}
	attributes = append(attributes, slogFieldsToAttributes(e.Fields)...)

	name := fmt.Sprintf("log: %s: %s", e.Level, e.Message)
	span.AddEvent(name, trace.WithAttributes(attributes...))
}

// Sync implements slog.Sink. No-op as syncing is handled externally by otel.
func (SlogSink) Sync() {}

func slogFieldsToAttributes(m slog.Map) []attribute.KeyValue {
	attrs := make([]attribute.KeyValue, 0, len(m))
	for _, f := range m {
		var value attribute.Value
		switch v := f.Value.(type) {
		case bool:
			value = attribute.BoolValue(v)
		case []bool:
			value = attribute.BoolSliceValue(v)
		case float32:
			value = attribute.Float64Value(float64(v))
		// no float32 slice method
		case float64:
			value = attribute.Float64Value(v)
		case []float64:
			value = attribute.Float64SliceValue(v)
		case int:
			value = attribute.Int64Value(int64(v))
		case []int:
			value = attribute.IntSliceValue(v)
		case int8:
			value = attribute.Int64Value(int64(v))
		// no int8 slice method
		case int16:
			value = attribute.Int64Value(int64(v))
		// no int16 slice method
		case int32:
			value = attribute.Int64Value(int64(v))
		// no int32 slice method
		case int64:
			value = attribute.Int64Value(v)
		case []int64:
			value = attribute.Int64SliceValue(v)
		case uint:
			value = attribute.Int64Value(int64(v))
		// no uint slice method
		case uint8:
			value = attribute.Int64Value(int64(v))
		// no uint8 slice method
		case uint16:
			value = attribute.Int64Value(int64(v))
		// no uint16 slice method
		case uint32:
			value = attribute.Int64Value(int64(v))
		// no uint32 slice method
		case uint64:
			value = attribute.Int64Value(int64(v))
		// no uint64 slice method
		case string:
			value = attribute.StringValue(v)
		case []string:
			value = attribute.StringSliceValue(v)
		case time.Duration:
			value = attribute.StringValue(v.String())
		case time.Time:
			value = attribute.StringValue(v.Format(time.RFC3339Nano))
		case fmt.Stringer:
			value = attribute.StringValue(v.String())
		}

		if value.Type() != attribute.INVALID {
			attrs = append(attrs, attribute.KeyValue{
				Key:   attribute.Key(f.Name),
				Value: value,
			})
		}
	}

	return attrs
}
