package logx

import (
	"context"
	"fmt"
	"log/slog"
	"mayfly-go/pkg/utils/runtimex"
	"path/filepath"
	"runtime"
)

var (
	config *Config
)

func GetConfig() *Config {
	if config == nil {
		return &Config{
			Level:     "info",
			Type:      "text",
			AddSource: false,
		}
	}
	return config
}

func Init(logConf Config) {
	config = &logConf
	var handler slog.Handler
	if logConf.IsJsonType() {
		handler = NewJsonHandler(config)
	} else {
		handler = NewTextHandler(config)
	}
	slog.SetDefault(slog.New(handler))
}

func Print(msg string, args ...any) {
	Log(context.Background(), slog.LevelInfo, msg, args...)
}

func Debug(msg string, args ...any) {
	Log(context.Background(), slog.LevelDebug, msg, args...)
}

func Debugf(format string, args ...any) {
	Log(context.Background(), slog.LevelDebug, fmt.Sprintf(format, args...))
}

func DebugWithFields(msg string, mapFields map[string]any) {
	Log(context.Background(), slog.LevelDebug, msg, map2Attrs(mapFields)...)
}

// debug记录，并将堆栈信息添加至msg里，默认记录10个堆栈信息
func DebugTrace(msg string, err error) {
	Log(context.Background(), slog.LevelDebug, fmt.Sprintf(msg+" %s\n%s", err.Error(), runtimex.StatckStr(2, 10)))
}

func Info(msg string, args ...any) {
	Log(context.Background(), slog.LevelInfo, msg, args...)
}

func InfoContext(ctx context.Context, msg string, args ...any) {
	Log(ctx, slog.LevelInfo, msg, args...)
}

func Infof(format string, args ...any) {
	Log(context.Background(), slog.LevelInfo, fmt.Sprintf(format, args...))
}

func InfoWithFields(msg string, mapFields map[string]any) {
	Log(context.Background(), slog.LevelInfo, msg, map2Attrs(mapFields)...)
}

func Warn(msg string, args ...any) {
	Log(context.Background(), slog.LevelWarn, msg, args...)
}

func Warnf(format string, args ...any) {
	Log(context.Background(), slog.LevelWarn, fmt.Sprintf(format, args...))
}

func WarnWithFields(msg string, mapFields map[string]any) {
	Log(context.Background(), slog.LevelWarn, msg, map2Attrs(mapFields)...)
}

func Error(msg string, args ...any) {
	Log(context.Background(), slog.LevelError, msg, args...)
}

func Errorf(format string, args ...any) {
	Log(context.Background(), slog.LevelError, fmt.Sprintf(format, args...))
}

// 错误记录，并将堆栈信息添加至msg里，默认记录10个堆栈信息
func ErrorTrace(msg string, err error) {
	Log(context.Background(), slog.LevelError, fmt.Sprintf(msg+" %s\n%s", err.Error(), runtimex.StatckStr(2, 10)))
}

func ErrorWithFields(msg string, mapFields map[string]any) {
	Log(context.Background(), slog.LevelError, msg, map2Attrs(mapFields)...)
}

func Panic(msg string, args ...any) {
	Log(context.Background(), slog.LevelError, msg, args...)
	panic(msg)
}

func Panicf(format string, args ...any) {
	msg := fmt.Sprintf(format, args...)
	Log(context.Background(), slog.LevelError, fmt.Sprintf(format, args...))
	panic(msg)
}

func Log(ctx context.Context, level slog.Level, msg string, args ...any) {
	slog.Log(ctx, level, msg, appendCommonAttr(ctx, level, args)...)
}

// 获取日志公共属性
func getCommonAttr(ctx context.Context, level slog.Level) []any {
	commonAttrs := make([]any, 0)

	// 如果系统配置添加方法信息或者为错误级别时则 记录方法信息及行号
	if GetConfig().AddSource || level == slog.LevelError {
		// skip [runtime.Callers, getCommonAttr, appendCommonAttr, logx.Log, logx.Info|Debug|Warn|Error..]
		var pcs [1]uintptr
		runtime.Callers(5, pcs[:])
		fs := runtime.CallersFrames(pcs[:])
		f, _ := fs.Next()

		source := &Source{
			Function: f.Function,
			Fileline: fmt.Sprintf("%s:%d", filepath.Base(f.File), f.Line),
		}
		commonAttrs = append(commonAttrs, slog.SourceKey, source)
	}

	return commonAttrs
}

func appendCommonAttr(ctx context.Context, level slog.Level, args []any) []any {
	commonAttrs := getCommonAttr(ctx, level)
	if len(commonAttrs) > 0 {
		args = append(commonAttrs, args...)
	}
	return args
}

// map类型转为attr
func map2Attrs(mapArg map[string]any) []any {
	atts := make([]any, 0)
	for k, v := range mapArg {
		atts = append(atts, slog.Any(k, v))
	}
	return atts
}

type Source struct {
	// Function is the package path-qualified function name containing the
	// source line. If non-empty, this string uniquely identifies a single
	// function in the program. This may be the empty string if not known.
	Function string `json:"function"`
	// File and Line are the file name and line number (1-based) of the source
	// line. These may be the empty string and zero, respectively, if not known.
	Fileline string `json:"fileline"`
}

func (s Source) String() string {
	return fmt.Sprintf("%s (%s)", s.Function, s.Fileline)
}
