package logger

import (
	"io"
	"os"

	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

type Logger struct {
	opts options
	*zap.Logger
	level zap.AtomicLevel
}

func New(opts ...Option) (*Logger, error) {
	logger := new(Logger)

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

	err := logger.logInit()

	return logger, err
}

func (l *Logger) SetLogLevel(levelStr string) error {
	level := new(zapcore.Level)
	if err := level.UnmarshalText([]byte(levelStr)); err != nil {
		return err
	}

	l.level.SetLevel(*level)

	return nil
}

func (l *Logger) logWriter() zapcore.WriteSyncer {
	var res io.Writer
	res = os.Stdout

	if l.opts.mode == "release" {
		lj := &lumberjack.Logger{
			Filename:   l.opts.fileName,
			MaxSize:    l.opts.maxSize,
			MaxBackups: l.opts.maxBackUp,
			MaxAge:     l.opts.maxAge,
		}

		res = io.MultiWriter(lj, res)
	}

	return zapcore.AddSync(res)
}

func (l *Logger) logEncoder() zapcore.Encoder {
	ec := zap.NewProductionEncoderConfig()
	ec.EncodeTime = zapcore.ISO8601TimeEncoder
	ec.TimeKey = "time"
	ec.EncodeLevel = zapcore.CapitalColorLevelEncoder
	ec.EncodeDuration = zapcore.SecondsDurationEncoder
	ec.EncodeCaller = zapcore.ShortCallerEncoder

	return zapcore.NewConsoleEncoder(ec)
}

func (l *Logger) logLevel() error {
	level, err := zap.ParseAtomicLevel(l.opts.level)
	if err != nil {
		return err
	}

	l.level = level

	return nil
}

func (l *Logger) logInit() error {
	if err := l.logLevel(); err != nil {
		return err
	}

	writeSyncer := l.logWriter()
	encoder := l.logEncoder()

	core := zapcore.NewCore(encoder, writeSyncer, l.level)

	l.Logger = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))

	zap.ReplaceGlobals(l.Logger)

	return nil
}
