package rlog

import (
	"context"
	"log/slog"
	"os"
	"runtime"
	"strings"
	"time"

	"github.com/rs/zerolog"
	slogmulti "github.com/samber/slog-multi"
	slogzerolog "github.com/samber/slog-zerolog"
)

var (
	zl       = zerolog.New(&zerolog.ConsoleWriter{Out: os.Stderr})
	handlers = []slog.Handler{
		slogzerolog.Option{Logger: &zl}.NewZerologHandler(),
	}
	handler       = slogmulti.Fanout(handlers...)
	defaultLogger = slog.New(handler)
)

func init() {
	slog.SetDefault(defaultLogger)
}

func AddHandler(nh slog.Handler) {
	handlers = append(handlers, nh)
	handler = slogmulti.Fanout(handlers...)
	defaultLogger = slog.New(handler)
	slog.SetDefault(defaultLogger)
}

type Logger struct {
	handler     slog.Handler
	callNesting int
	component   []string
}

const functionKey = "function"

const componentKey = "component"
const componentSep = "."

func (l *Logger) log(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr) {
	var pcs [1]uintptr
	runtime.Callers(3+l.callNesting, pcs[:])
	pc := pcs[0]
	f := runtime.FuncForPC(pc)
	if f != nil {
		attrs = append(attrs, slog.String(functionKey, f.Name()))
	}

	if len(l.component) > 0 {
		attrs = append(attrs, slog.String(componentKey, strings.Join(l.component, componentSep)))
	}

	r := slog.NewRecord(time.Now(), level, msg, pc)
	r.AddAttrs(attrs...)
	if ctx == nil {
		ctx = context.Background()
	}

	_ = l.handler.Handle(ctx, r)
}

func (l *Logger) Debug(ctx context.Context, msg string, attrs ...slog.Attr) {
	l.log(ctx, slog.LevelDebug, msg, attrs...)
}

func (l *Logger) Info(ctx context.Context, msg string, attrs ...slog.Attr) {
	l.log(ctx, slog.LevelInfo, msg, attrs...)
}

func (l *Logger) Warn(ctx context.Context, msg string, attrs ...slog.Attr) {
	l.log(ctx, slog.LevelWarn, msg, attrs...)
}

func (l *Logger) Error(ctx context.Context, msg string, attrs ...slog.Attr) {
	l.log(ctx, slog.LevelError, msg, attrs...)
}

func (log *Logger) WithComponent(name string) *Logger {
	return &Logger{
		handler:   log.handler,
		component: append(log.component, name),
	}
}

func (l *Logger) With(attrs ...slog.Attr) *Logger {
	return &Logger{
		handler:   l.handler.WithAttrs(attrs),
		component: l.component,
	}
}

func (l *Logger) Nested(callNesting int) *Logger {
	return &Logger{
		handler:     l.handler,
		component:   l.component,
		callNesting: callNesting,
	}
}

// returns a new slog logger with the same attribures as the original logger
// TODO currently not logging function name
func (l *Logger) Slog() *slog.Logger {
	return slog.New(l.handler)
}

const errKey = "error"

func Error(err error) slog.Attr {
	return slog.Attr{Key: errKey, Value: errValue(err)}
}

// errValue returns a slog.GroupValue with keys "msg" and "trace". If the error
// does not implement interface { StackTrace() errors.StackTrace }, the "trace"
// key is omitted.
func errValue(err error) slog.Value {
	if err == nil {
		return slog.AnyValue(nil)
	}

	var groupValues []slog.Attr

	groupValues = append(groupValues,
		slog.String("msg", err.Error()),
		slog.Any("value", err),
	)

	return slog.GroupValue(groupValues...)
}

func Component(name ...string) *Logger {
	return &Logger{
		handler:   handler,
		component: name,
	}
}