package logger

import (
	"context"
	"fmt"
	"log/slog"
	"os"
	"runtime"
	"sync"
	"time"
)

func init() {
	lvl, err := GetLevel(os.Getenv("MICRO_LOG_LEVEL"))
	if err != nil {
		lvl = InfoLevel
	}

	DefaultLogger = NewLogger(WithLevel(lvl))
}

type defaultLogger struct {
	opts Options
	slog *slog.Logger
	sync.RWMutex
}

// Init (opts...) should only overwrite provided options.
func (l *defaultLogger) Init(opts ...Option) error {
	l.Lock()
	defer l.Unlock()

	for _, o := range opts {
		o(&l.opts)
	}

	// Recreate slog logger with new options
	handlerOpts := &slog.HandlerOptions{
		Level:     l.opts.Level.ToSlog(),
		AddSource: true,
	}

	// Create text handler for stdout
	textHandler := slog.NewTextHandler(l.opts.Out, handlerOpts)

	// Create debug log handler for debug/log buffer
	debugHandler := newDebugLogHandler(handlerOpts.Level)

	// Combine both handlers
	handler := newMultiHandler(textHandler, debugHandler)

	l.slog = slog.New(handler)

	// Add fields if any
	if len(l.opts.Fields) > 0 {
		const fieldsPerKV = 2
		args := make([]any, 0, len(l.opts.Fields)*fieldsPerKV)
		for k, v := range l.opts.Fields {
			args = append(args, k, v)
		}

		l.slog = l.slog.With(args...)
	}

	return nil
}

func (l *defaultLogger) String() string {
	return "default"
}

func (l *defaultLogger) Fields(fields map[string]interface{}) Logger {
	l.RLock()
	nfields := copyFields(l.opts.Fields)
	opts := l.opts
	l.RUnlock()

	for k, v := range fields {
		nfields[k] = v
	}

	// Create new logger without locks
	newLogger := NewLogger(
		WithLevel(opts.Level),
		WithFields(nfields),
		WithOutput(opts.Out),
		WithCallerSkipCount(opts.CallerSkipCount),
	)

	return newLogger
}

func copyFields(src map[string]interface{}) map[string]interface{} {
	dst := make(map[string]interface{}, len(src))
	for k, v := range src {
		dst[k] = v
	}

	return dst
}

func (l *defaultLogger) Log(level Level, v ...interface{}) {
	// TODO decide does we need to write message if log level not used?
	if !l.opts.Level.Enabled(level) {
		return
	}

	l.RLock()
	slogger := l.slog

	if slogger == nil {
		// Fallback if not initialized
		slogger = slog.Default()
	}
	l.RUnlock()

	// Get caller information
	var pcs [1]uintptr

	runtime.Callers(l.opts.CallerSkipCount, pcs[:])
	r := slog.NewRecord(time.Now(), level.ToSlog(), fmt.Sprint(v...), pcs[0])

	_ = slogger.Handler().Handle(context.Background(), r)
}

func (l *defaultLogger) Logf(level Level, format string, v ...interface{}) {
	//	 TODO decide does we need to write message if log level not used?
	if !l.opts.Level.Enabled(level) {
		return
	}

	l.RLock()
	slogger := l.slog

	if slogger == nil {
		// Fallback if not initialized
		slogger = slog.Default()
	}
	l.RUnlock()

	// Get caller information
	var pcs [1]uintptr

	runtime.Callers(l.opts.CallerSkipCount, pcs[:])
	r := slog.NewRecord(time.Now(), level.ToSlog(), fmt.Sprintf(format, v...), pcs[0])

	_ = slogger.Handler().Handle(context.Background(), r)
}

func (l *defaultLogger) Options() Options {
	// not guard against options Context values
	l.RLock()
	defer l.RUnlock()

	opts := l.opts
	opts.Fields = copyFields(l.opts.Fields)

	return opts
}

// NewLogger builds a new logger based on options.
func NewLogger(opts ...Option) Logger {
	// Default options
	const defaultCallerSkipCount = 2

	options := Options{
		Level:           InfoLevel,
		Fields:          make(map[string]interface{}),
		Out:             os.Stderr,
		CallerSkipCount: defaultCallerSkipCount,
		Context:         context.Background(),
	}

	l := &defaultLogger{opts: options}
	if err := l.Init(opts...); err != nil {
		l.Log(FatalLevel, err)
	}

	return l
}
