package logger

import (
	golog "log"
	"os"
	"time"

	"code.justin.tv/eventbus/controlplane/internal/ecs"
	"code.justin.tv/eventbus/controlplane/internal/environment"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

// Logger wraps the implementation of the logger to provide a more flexible
// interface.
type Logger struct {
	*zap.Logger
}

var filename = os.Getenv("ECS_CONTAINER_METADATA_FILE")
var retryInterval = 200 * time.Millisecond // check for readiness every ...
var timeout = 2 * time.Second
var cachedContainer container

func init() {
	metadata, err := ecs.ReadMetadata(filename, retryInterval, timeout)
	env := environment.Environment()
	// Only warn the user about container context load failures when running
	// in an AWS environment
	if (environment.IsProductionEnv(env) || environment.IsStagingEnv(env)) && err != nil {
		golog.Println("Unable to read container context", err)
	}
	cachedContainer = container(metadata)
}

// New constructs a new instance of a Logger.
func new() (*Logger, error) {
	var log *zap.Logger
	var err error

	env := environment.Environment()
	if env == "" {
		env = "local"
	}

	fields := []zap.Field{zap.String("env", env)}
	options := []zap.Option{
		zap.Fields(fields...),
	}

	if environment.IsProductionEnv(env) || environment.IsStagingEnv(env) {
		config := zap.Config{
			Level:             zap.NewAtomicLevelAt(zap.InfoLevel),
			DisableCaller:     false,
			DisableStacktrace: false,
			Encoding:          "json",
			EncoderConfig:     zap.NewProductionEncoderConfig(),
			OutputPaths:       []string{"stderr"},
			ErrorOutputPaths:  []string{"stderr"},
			// Zap samples by logging the first N entries with a given level and
			// message each tick. If more Entries with the same level and message
			// are seen during the same interval, every Mth message is logged and the
			// rest are dropped.
			Sampling: &zap.SamplingConfig{
				Initial:    100, // logs per second
				Thereafter: 100, // logs per second
			},
		}
		log, err = config.Build(options...)
	} else if environment.IsLocalDev() {
		config := zap.NewDevelopmentConfig()
		config.Level = zap.NewAtomicLevelAt(zap.ErrorLevel)
		log, err = config.Build(options...)
	} else if environment.IsEndToEndTest() {
		config := zap.NewDevelopmentConfig()
		config.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
		config.DisableCaller = true
		config.DisableStacktrace = true
		log, err = config.Build(options...)
	} else {
		// Assume development on laptop.
		log, err = zap.NewDevelopment(options...)
	}

	if err != nil {
		return nil, err
	}

	log = AddContainerContext(log)

	return &Logger{Logger: log}, nil
}

func (l *Logger) With(fields ...zapcore.Field) *Logger {
	return &Logger{l.Logger.With(fields...)}
}

func AddContainerContext(l *zap.Logger) *zap.Logger {
	// ECS context may be empty - avoid cluttering logs in this case
	if cachedContainer.ContainerID != "" {
		l = l.With(zap.Object("container", cachedContainer))
	}

	return l
}
