package logger

import (
	"fmt"
	"os"
	"runtime"
	"strings"
	"time"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)

var (
	zlog *zap.SugaredLogger
)

type LoggerConfig struct {
	Filename      string `mapstructure:"filename" json:"filename" yaml:"filename"`
	Level         string `mapstructure:"level" json:"level" yaml:"level"`
	Format        string `mapstructure:"format" json:"format" yaml:"format"`
	Prefix        string `mapstructure:"prefix" json:"prefix" yaml:"prefix"`
	Director      string `mapstructure:"director" json:"director" yaml:"director"`
	ShowLine      bool   `mapstructure:"show-line" json:"show-line" yaml:"show-line"`
	EncodeLevel   string `mapstructure:"encode-level" json:"encode-level" yaml:"encode-level"`
	StacktraceKey string `mapstructure:"stacktrace-key" json:"stacktrace-key" yaml:"stacktrace-key"`
	LogInConsole  bool   `mapstructure:"log-in-console" json:"log-in-console" yaml:"log-in-console"`
}

func Init(conf *LoggerConfig) {
	if conf.Filename == "/dev/stdout" {
		ecf := zap.NewProductionEncoderConfig()
		ecf.FunctionKey = "func"
		ecf.EncodeTime = zapcore.ISO8601TimeEncoder
		ecf.ConsoleSeparator = " "
		ecf.EncodeCaller = zapcore.ShortCallerEncoder

		core := zapcore.NewCore(
			zapcore.NewConsoleEncoder(ecf),
			zapcore.AddSync(os.Stdout),
			zap.DebugLevel,
		)
		zl := zap.New(core, zap.AddCallerSkip(1), zap.AddCaller())
		zlog = zl.Sugar()
		return
	}

	_, err := os.Stat(conf.Filename)
	if err != nil {
		if os.IsNotExist(err) {
			err = os.MkdirAll(conf.Filename, os.ModePerm)
			if err != nil {
				panic(err)
			}
		} else {
			fmt.Println("logger init error:", err)
		}
	}
	if strings.LastIndex(conf.Filename, "/") != 0 {
		conf.Filename = conf.Filename + "/"
	}

	encoder := zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
		MessageKey:  "msg",
		LevelKey:    "level",
		EncodeLevel: zapcore.CapitalLevelEncoder,
		TimeKey:     "ts",
		EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
			enc.AppendString(t.Format("2006-01-02 15:04:05"))
		},
		CallerKey:    "file",
		EncodeCaller: zapcore.ShortCallerEncoder,
		EncodeDuration: func(d time.Duration, enc zapcore.PrimitiveArrayEncoder) {
			enc.AppendInt64(int64(d) / 1000000)
		},
	})

	// 实现两个判断日志等级的interface
	infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl >= zapcore.InfoLevel
	})

	errorLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl >= zapcore.ErrorLevel
	})

	// 获取 info、error日志文件的io.Writer 抽象 getWriter() 在下方实现

	now := time.Now()
	fileTime := now.Format("20060102") + "-" + now.Format("150405")
	fileFormat := "%s%s-%s.log"
	infoWriter := getWriter(fmt.Sprintf(fileFormat, conf.Filename, "info", fileTime))
	errorWriter := getWriter(fmt.Sprintf(fileFormat, conf.Filename, "error", fileTime))

	// 最后创建具体的Logger
	core := zapcore.NewTee(
		zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), infoLevel), //打印到控制台
		zapcore.NewCore(encoder, infoWriter, infoLevel),
		zapcore.NewCore(encoder, errorWriter, errorLevel),
	)

	zl := zap.New(core, zap.AddCallerSkip(1), zap.AddCaller())
	zlog = zl.Sugar()
}

func GetDefault() *zap.SugaredLogger {
	return zlog
}

func InitDefault() {
	Init(&LoggerConfig{
		Filename: "/dev/stdout",
	})
}

func Sync() {
	_ = zlog.Sync()
}

func getWriter(filename string) zapcore.WriteSyncer {
	lumberJackLogger := &lumberjack.Logger{
		Filename:   filename, // 文件位置
		MaxSize:    100,      // 进行切割之前,日志文件的最大大小(MB为单位)
		MaxAge:     10,       // 保留旧文件的最大天数
		MaxBackups: 3,        // 保留旧文件的最大个数
		Compress:   false,    // 是否压缩/归档旧文件
	}
	// AddSync 将 io.Writer 转换为 WriteSyncer。
	// 它试图变得智能：如果 io.Writer 的具体类型实现了 WriteSyncer，我们将使用现有的 Sync 方法。
	// 如果没有，我们将添加一个无操作同步。

	return zapcore.AddSync(lumberJackLogger)
}

func Debug(args ...interface{}) {
	zlog.Debug(args...)
}

func Debugf(template string, args ...interface{}) {
	zlog.Debugf(template, args...)
}

func Info(args ...interface{}) {
	zlog.Info(args...)
}

func Infof(template string, args ...interface{}) {
	zlog.Infof(template, args...)
}

func Warn(args ...interface{}) {
	zlog.Warn(args...)
}

func Warnf(template string, args ...interface{}) {
	zlog.Warnf(template, args...)
}

func Error(args ...interface{}) {
	zlog.Error(args...)
}

func Errorf(template string, args ...interface{}) {
	zlog.Errorf(template, args...)
}

func DPanic(args ...interface{}) {
	zlog.DPanic(args...)
}

func DPanicf(template string, args ...interface{}) {
	zlog.DPanicf(template, args...)
}

func Panic(args ...interface{}) {
	zlog.Panic(args...)
}

func Panicf(template string, args ...interface{}) {
	zlog.Panicf(template, args...)
}

func Fatal(args ...interface{}) {
	zlog.Fatal(args...)
}

func Fatalf(template string, args ...interface{}) {
	zlog.Fatalf(template, args...)
}

func SafeGoroutine(fn func()) {
	go func() {
		defer func() {
			if r := recover(); r != nil {
				buf := make([]byte, 1<<16) // 64KB
				stackSize := runtime.Stack(buf, false)
				msg := fmt.Sprintf("panic: %v\n%s\n", r, buf[:stackSize])
				Errorf(msg)
			}
		}()
		fn()
	}()
}
