package log

import (
	"context"
	"log/slog"
	"os"
	"time"
)

const (
	LevelTrace = slog.Level(-8)
	LevelFatal = slog.Level(12)
)

type Log struct {
	Ctx       context.Context
	Log       *slog.Logger
	SLogLevel slog.LevelVar
}

var (
	// LevelNames set the names associated with custom logging levels.
	LevelNames = map[slog.Leveler]string{
		LevelTrace: "TRACE",
		LevelFatal: "FATAL",
	}
	// L is the global interface used for calling the logger subfunctions.
	L = Log{}
)

func Init(writer string) {
	slogOptions := &slog.HandlerOptions{
		Level: &L.SLogLevel,
		ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
			if a.Key == slog.TimeKey {
				a.Value = slog.StringValue(a.Value.Time().Format(time.DateTime))

				// To disable the timestamp all together, return an empty value.
				// return slog.Attr{}
			}
			if a.Key == slog.LevelKey {
				level := a.Value.Any().(slog.Level)
				levelLabel, exists := LevelNames[level]
				if !exists {
					levelLabel = level.String()
				}

				a.Value = slog.StringValue(levelLabel)
			}

			return a
		},
	}

	// Initialize SLog and translate new logging levels
	switch writer {
	case "json":
		L.Log = slog.New(slog.NewJSONHandler(os.Stdout, slogOptions))
	default:
		L.Log = slog.New(slog.NewTextHandler(os.Stdout, slogOptions))
	}
	// create context
	L.Ctx = context.Background()
}

// SetNumericLevel will set the log level based on a number from 1-100.
// The larger the number the more verbose the logs.
//
// 1-20 = Fatal, 21-40 = Error, 41-60 = Warn, 61-80 = Info, 81-99 = Debug,
// and 100 = Trace.
func SetNumericLevel(level int) {
	var llu string = "Log Level Updated"

	switch {
	// fatal
	case level <= 20:
		L.SLogLevel.Set(LevelFatal)
		Info(llu, "level", LevelFatal)
	// error
	case level > 20 && level <= 40:
		L.SLogLevel.Set(slog.LevelError)
		Info(llu, "level", slog.LevelError)
	// warning
	case level > 40 && level <= 60:
		L.SLogLevel.Set(slog.LevelWarn)
		Info(llu, "level", slog.LevelWarn)
	// info
	case level > 60 && level <= 80:
		L.SLogLevel.Set(slog.LevelInfo)
		Info(llu, "level", slog.LevelInfo)
	// debug
	case level > 80 && level <= 99:
		L.SLogLevel.Set(slog.LevelDebug)
		Info(llu, "level", slog.LevelDebug)
	// trace
	case level > 99:
		L.SLogLevel.Set(LevelTrace)
		Info(llu, "level", LevelTrace)
	}

	// set default logger
	slog.SetDefault(L.Log)
}

func Fatal(msg string, attrs ...interface{}) {
	L.Log.Log(
		L.Ctx,
		LevelFatal,
		msg,
		attrs...,
	)
}

func Error(msg string, attrs ...interface{}) {
	L.Log.Log(
		L.Ctx,
		slog.LevelError,
		msg,
		attrs...,
	)
}

func Warn(msg string, attrs ...interface{}) {
	L.Log.Log(
		L.Ctx,
		slog.LevelWarn,
		msg,
		attrs...,
	)
}

func Info(msg string, attrs ...interface{}) {
	L.Log.Log(
		L.Ctx,
		slog.LevelInfo,
		msg,
		attrs...,
	)
}

func Debug(msg string, attrs ...interface{}) {
	L.Log.Log(
		L.Ctx,
		slog.LevelDebug,
		msg,
		attrs...,
	)
}

func Trace(msg string, attrs ...interface{}) {
	L.Log.Log(
		L.Ctx,
		LevelTrace,
		msg,
		attrs...,
	)
}