package main

import (
	"strings"

	"google.golang.org/grpc/grpclog"

	"github.com/spf13/viper"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

type (
	zapLogger struct {
		zapcore.Core
		log *zap.SugaredLogger
	}

	logger interface {
		grpclog.LoggerV2
		Println(v ...interface{})
	}
)

const (
	formatJSON    = "json"
	formatConsole = "console"

	defaultSamplingInitial    = 100
	defaultSamplingThereafter = 100
)

func gRPCLogger(l *zap.Logger) logger {
	log := l.WithOptions(
		// skip gRPCLog + zapLogger in caller
		zap.AddCallerSkip(2))
	return &zapLogger{
		Core: log.Core(),
		log:  log.Sugar(),
	}
}

func safeLevel(lvl string) zap.AtomicLevel {
	switch strings.ToLower(lvl) {
	case "debug":
		return zap.NewAtomicLevelAt(zap.DebugLevel)
	case "warn":
		return zap.NewAtomicLevelAt(zap.WarnLevel)
	case "error":
		return zap.NewAtomicLevelAt(zap.ErrorLevel)
	case "fatal":
		return zap.NewAtomicLevelAt(zap.FatalLevel)
	case "panic":
		return zap.NewAtomicLevelAt(zap.PanicLevel)
	default:
		return zap.NewAtomicLevelAt(zap.InfoLevel)
	}
}

func newLogger(v *viper.Viper) *zap.Logger {
	c := zap.NewProductionConfig()

	c.OutputPaths = []string{"stdout"}
	c.ErrorOutputPaths = []string{"stdout"}

	if v.IsSet("logger.sampling") {
		c.Sampling = &zap.SamplingConfig{
			Initial:    defaultSamplingInitial,
			Thereafter: defaultSamplingThereafter,
		}

		if val := v.GetInt(cfgLoggerSamplingInitial); val > 0 {
			c.Sampling.Initial = val
		}

		if val := v.GetInt(cfgLoggerSamplingThereafter); val > 0 {
			c.Sampling.Thereafter = val
		}
	}

	// logger level
	c.Level = safeLevel(v.GetString(cfgLoggerLevel))
	traceLvl := safeLevel(v.GetString(cfgLoggerTraceLevel))

	// logger format
	switch f := v.GetString(cfgLoggerFormat); strings.ToLower(f) {
	case formatConsole:
		c.Encoding = formatConsole
	default:
		c.Encoding = formatJSON
	}

	// logger time
	c.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder

	l, err := c.Build(
		// enable trace only for current log-level
		zap.AddStacktrace(traceLvl))
	if err != nil {
		panic(err)
	}

	if v.GetBool(cfgLoggerNoDisclaimer) {
		return l
	}

	name := v.GetString(cfgApplicationName)
	version := v.GetString(cfgApplicationVersion)

	return l.With(
		zap.String("app_name", name),
		zap.String("app_version", version))
}

func (z *zapLogger) Info(args ...interface{}) { z.log.Info(args...) }

func (z *zapLogger) Infoln(args ...interface{}) { z.log.Info(args...) }

func (z *zapLogger) Infof(format string, args ...interface{}) { z.log.Infof(format, args...) }

func (z *zapLogger) Println(args ...interface{}) { z.log.Info(args...) }

func (z *zapLogger) Printf(format string, args ...interface{}) { z.log.Infof(format, args...) }

func (z *zapLogger) Warning(args ...interface{}) { z.log.Warn(args...) }

func (z *zapLogger) Warningln(args ...interface{}) { z.log.Warn(args...) }

func (z *zapLogger) Warningf(format string, args ...interface{}) { z.log.Warnf(format, args...) }

func (z *zapLogger) Error(args ...interface{}) { z.log.Error(args...) }

func (z *zapLogger) Errorln(args ...interface{}) { z.log.Error(args...) }

func (z *zapLogger) Errorf(format string, args ...interface{}) { z.log.Errorf(format, args...) }

func (z *zapLogger) Fatal(args ...interface{}) { z.log.Fatal(args...) }

func (z *zapLogger) Fatalln(args ...interface{}) { z.log.Fatal(args...) }

func (z *zapLogger) Fatalf(format string, args ...interface{}) { z.Fatalf(format, args...) }

func (z *zapLogger) V(int) bool { return z.Enabled(zapcore.DebugLevel) }