package logger

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"time"

	"bindbox-game/internal/repository/mysql/dao"
	"bindbox-game/internal/repository/mysql/model"

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

const (
	// DefaultLevel the default log level
	DefaultLevel = zapcore.InfoLevel

	// DefaultTimeLayout the default time layout;
	DefaultTimeLayout = time.RFC3339
)

// Option custom setup config
type Option func(*option)

type option struct {
	level           zapcore.Level
	fields          map[string]string
	file            io.Writer
	timeLayout      string
	outputInConsole bool
}

// WithDebugLevel only greater than 'level' will output
func WithDebugLevel() Option {
	return func(opt *option) {
		opt.level = zapcore.DebugLevel
	}
}

// WithInfoLevel only greater than 'level' will output
func WithInfoLevel() Option {
	return func(opt *option) {
		opt.level = zapcore.InfoLevel
	}
}

// WithWarnLevel only greater than 'level' will output
func WithWarnLevel() Option {
	return func(opt *option) {
		opt.level = zapcore.WarnLevel
	}
}

// WithErrorLevel only greater than 'level' will output
func WithErrorLevel() Option {
	return func(opt *option) {
		opt.level = zapcore.ErrorLevel
	}
}

// WithField add some field(s) to log
func WithField(key, value string) Option {
	return func(opt *option) {
		opt.fields[key] = value
	}
}

// WithFileP write log to some file
func WithFileP(file string) Option {
	dir := filepath.Dir(file)
	if err := os.MkdirAll(dir, 0766); err != nil {
		panic(err)
	}

	f, err := os.OpenFile(file, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0766)
	if err != nil {
		panic(err)
	}

	return func(opt *option) {
		opt.file = zapcore.Lock(f)
	}
}

// WithFileRotationP write log to some file with rotation
func WithFileRotationP(file string) Option {
	dir := filepath.Dir(file)
	if err := os.MkdirAll(dir, 0766); err != nil {
		panic(err)
	}

	return func(opt *option) {
		opt.file = &lumberjack.Logger{ // concurrent-safed
			Filename:   file, // 文件路径
			MaxSize:    128,  // 单个文件最大尺寸，默认单位 M
			MaxBackups: 300,  // 最多保留 300 个备份
			MaxAge:     30,   // 最大时间，默认单位 day
			LocalTime:  true, // 使用本地时间
			Compress:   true, // 是否压缩 disabled by default
		}
	}
}

// WithTimeLayout custom time format
func WithTimeLayout(timeLayout string) Option {
	return func(opt *option) {
		opt.timeLayout = timeLayout
	}
}

// WithOutputInConsole write log to os.Stdout or os.Stderr
func WithOutputInConsole() Option {
	return func(opt *option) {
		opt.outputInConsole = true
	}
}

// NewJSONLogger return a json-encoder zap logger,
func NewJSONLogger(opts ...Option) (*zap.Logger, error) {
	opt := &option{level: DefaultLevel, fields: make(map[string]string)}
	for _, f := range opts {
		f(opt)
	}

	timeLayout := DefaultTimeLayout
	if opt.timeLayout != "" {
		timeLayout = opt.timeLayout
	}

	// similar to zap.NewProductionEncoderConfig()
	encoderConfig := zapcore.EncoderConfig{
		TimeKey:       "time",
		LevelKey:      "level",
		NameKey:       "logger", // used by logger.Named(key); optional; useless
		CallerKey:     "caller",
		MessageKey:    "msg",
		StacktraceKey: "stacktrace", // use by zap.AddStacktrace; optional; useless
		LineEnding:    zapcore.DefaultLineEnding,
		EncodeLevel:   zapcore.LowercaseLevelEncoder, // 小写编码器
		EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
			enc.AppendString(t.Format(timeLayout))
		},
		EncodeDuration: zapcore.MillisDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder, // 全路径编码器
	}

	jsonEncoder := zapcore.NewJSONEncoder(encoderConfig)

	// lowPriority usd by info\debug\warn
	lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl >= opt.level && lvl < zapcore.ErrorLevel
	})

	// highPriority usd by error\panic\fatal
	highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl >= opt.level && lvl >= zapcore.ErrorLevel
	})

	stdout := zapcore.Lock(os.Stdout) // lock for concurrent safe
	stderr := zapcore.Lock(os.Stderr) // lock for concurrent safe

	core := zapcore.NewTee()

	if opt.outputInConsole {
		core = zapcore.NewTee(
			zapcore.NewCore(jsonEncoder,
				zapcore.NewMultiWriteSyncer(stdout),
				lowPriority,
			),
			zapcore.NewCore(jsonEncoder,
				zapcore.NewMultiWriteSyncer(stderr),
				highPriority,
			),
		)
	}

	if opt.file != nil {
		core = zapcore.NewTee(core,
			zapcore.NewCore(jsonEncoder,
				zapcore.AddSync(opt.file),
				zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
					return lvl >= opt.level
				}),
			),
		)
	}

	logger := zap.New(core,
		zap.AddCaller(),
		zap.ErrorOutput(stderr),
	)

	for key, value := range opt.fields {
		logger = logger.WithOptions(zap.Fields(zapcore.Field{Key: key, Type: zapcore.StringType, String: value}))
	}

	return logger, nil
}

// CustomLogger 自定义日志记录器
type customLogger struct {
	db     *dao.Query
	logger *zap.Logger
}

var _ CustomLogger = (*customLogger)(nil)

type CustomLogger interface {
	i()
	Info(msg string, fields ...zap.Field)
	Debug(msg string, fields ...zap.Field)
	Warn(msg string, fields ...zap.Field)
	Error(msg string, fields ...zap.Field)
	Fatal(msg string, fields ...zap.Field)
	Sync() error
}

// NewCustomLogger 创建自定义日志记录器
func NewCustomLogger(db *dao.Query, opts ...Option) (CustomLogger, error) {
	logger, err := NewJSONLogger(opts...)
	if err != nil {
		return nil, err
	}
	return &customLogger{
		db:     db,
		logger: logger,
	}, nil
}

func (c *customLogger) i() {
}

// fieldsToJSON 将 zap.Field 转换为 JSON 字符串
func (c *customLogger) fieldsToJSON(msg string, fields []zap.Field) (string, error) {
	// 创建一个 buffer 来存储编码后的 JSON
	buffer := &bytes.Buffer{}

	// 创建一个 zapcore.Encoder 对象
	encoderConfig := zap.NewProductionEncoderConfig()
	encoder := zapcore.NewJSONEncoder(encoderConfig)

	// 创建一个 zapcore.Core 对象
	core := zapcore.NewCore(encoder, zapcore.AddSync(buffer), zap.InfoLevel)

	// 创建一个 zap.Logger 对象
	tempLogger := zap.New(core)

	// 使用 tempLogger 记录一条日志，只包含 fields
	tempLogger.Info(msg, fields...)

	// 获取编码后的 JSON 字符串
	jsonStr := buffer.String()

	// 去掉最后的换行符
	if len(jsonStr) > 0 && jsonStr[len(jsonStr)-1] == '\n' {
		jsonStr = jsonStr[:len(jsonStr)-1]
	}

	// 解析 JSON 字符串以确保格式正确
	var parsedJSON map[string]interface{}
	err := json.Unmarshal([]byte(jsonStr), &parsedJSON)
	if err != nil {
		return "", err
	}

	// 重新编码为 JSON 字符串
	jsonBytes, err := json.Marshal(parsedJSON)
	if err != nil {
		return "", err
	}

	return string(jsonBytes), nil
}

// fieldsJsonToDB 将 zap.Field 转换为数据库记录
func (c *customLogger) fieldsJsonToDB(level, msg string, fields []zap.Field) {
	content := ""

	jsonFields, err := c.fieldsToJSON(msg, fields)
	if err != nil {
		c.logger.Error("Failed to convert zap fields to JSON", zap.Error(err))
	} else {
		content = jsonFields
	}

	if c.db != nil {
		if err := c.db.LogOperation.Create(&model.LogOperation{
			Level:     level,
			Msg:       msg,
			Content:   content,
			CreatedAt: time.Now(),
		}); err != nil {
			log.Println(fmt.Sprintf("Failed to create log operation record: %s", err.Error()))
		}
	} else {
		log.Println(level, msg, content)
	}
}

// Info 重写 Info 方法
func (c *customLogger) Info(msg string, fields ...zap.Field) {
	c.logger.Info(msg, fields...)
	c.fieldsJsonToDB("info", msg, fields)
}

// Debug 调用底层 Debug 方法
func (c *customLogger) Debug(msg string, fields ...zap.Field) {
	c.logger.Debug(msg, fields...)
	c.fieldsJsonToDB("debug", msg, fields)
}

// Warn 调用底层 Warn 方法
func (c *customLogger) Warn(msg string, fields ...zap.Field) {
	c.logger.Warn(msg, fields...)
	c.fieldsJsonToDB("warn", msg, fields)
}

// Error 调用底层 Error 方法
func (c *customLogger) Error(msg string, fields ...zap.Field) {
	c.logger.Error(msg, fields...)
	c.fieldsJsonToDB("error", msg, fields)
}

// Fatal 调用底层 Fatal 方法
func (c *customLogger) Fatal(msg string, fields ...zap.Field) {
	c.logger.Fatal(msg, fields...)
	c.fieldsJsonToDB("fatal", msg, fields)
}

// Sync 调用底层 Sync 方法
func (c *customLogger) Sync() error {
	return c.logger.Sync()
}

// With 调用底层 With 方法
//func (c *customLogger) With(db mysql.Repo, fields ...zap.Field) *customLogger {
//	return &customLogger{db: db, logger: c.logger.With(fields...)}
//}

var _ Meta = (*meta)(nil)

// Meta key-value
type Meta interface {
	Key() string
	Value() interface{}
	meta()
}

type meta struct {
	key   string
	value interface{}
}

func (m *meta) Key() string {
	return m.key
}

func (m *meta) Value() interface{} {
	return m.value
}

func (m *meta) meta() {}

// NewMeta create meat
func NewMeta(key string, value interface{}) Meta {
	return &meta{key: key, value: value}
}

// WrapMeta wrap meta to zap fields
func WrapMeta(err error, metas ...Meta) (fields []zap.Field) {
	capacity := len(metas) + 1 // namespace meta
	if err != nil {
		capacity++
	}

	fields = make([]zap.Field, 0, capacity)
	if err != nil {
		fields = append(fields, zap.Error(err))
	}

	fields = append(fields, zap.Namespace("meta"))
	for _, meta := range metas {
		fields = append(fields, zap.Any(meta.Key(), meta.Value()))
	}

	return
}
