// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements.  See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License.  You may obtain a copy of the License at
//
//	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package logger

import (
	"os"
	"sync"

	grpcZap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var (
	mutex   = &sync.Mutex{}
	hasInit = false
	encoder = zapcore.NewConsoleEncoder(
		zapcore.EncoderConfig{
			MessageKey:     "msg",
			LevelKey:       "level",
			TimeKey:        "time",
			CallerKey:      "line",
			NameKey:        "logger",
			FunctionKey:    "",
			StacktraceKey:  "stacktrace",
			EncodeLevel:    zapcore.CapitalColorLevelEncoder,
			EncodeTime:     zapcore.RFC3339TimeEncoder,
			EncodeCaller:   zapcore.ShortCallerEncoder,
			EncodeDuration: zapcore.SecondsDurationEncoder,
		})
	logger    *zap.Logger
	sugar     *zap.SugaredLogger
	cmdLogger *zap.Logger
	cmdSugar  *CmdSugarLogger
)

// CmdSugarLogger wraps zap.SugaredLogger and zapcore.WriteSyncer in order to use Sugar
// while being able to use low-level writers.
type CmdSugarLogger struct {
	*zap.SugaredLogger
	// wrap ws to print directly
	ws zapcore.WriteSyncer
}

func (log *CmdSugarLogger) Print(s string) {
	_, _ = log.ws.Write([]byte(s))
}

// nolint
func Init() {
	mutex.Lock()
	defer mutex.Unlock()
	if hasInit {
		return
	}
	hasInit = true

	core := zapcore.NewCore(encoder, os.Stdout, zap.DebugLevel)
	logger = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(2))
	defer logger.Sync() // flushes buffer, if any
	sugar = logger.Sugar()

	// Create a separate logger for gRPC with higher log level to suppress INFO logs
	grpcCore := zapcore.NewCore(encoder, os.Stdout, zap.WarnLevel)
	grpcLogger := zap.New(grpcCore, zap.AddCaller(), zap.AddCallerSkip(2))
	// Make sure that log statements internal to gRPC library are logged using the zapLogger as well.
	grpcZap.ReplaceGrpcLoggerV2(grpcLogger)
}

// nolint
func InitCmdSugar(ws zapcore.WriteSyncer) {
	mutex.Lock()
	defer mutex.Unlock()

	core := zapcore.NewCore(encoder, ws, zap.DebugLevel)
	cmdLogger = zap.New(core)
	defer cmdLogger.Sync() // flushes buffer, if any
	cmdSugar = &CmdSugarLogger{
		SugaredLogger: cmdLogger.Sugar(),
		ws:            ws,
	}
}

func Sugar() *zap.SugaredLogger {
	if sugar == nil {
		Init()
	}
	return sugar
}

func Logger() *zap.Logger {
	if logger == nil {
		Init()
	}
	return logger
}

func CmdSugar() *CmdSugarLogger {
	if cmdSugar == nil {
		InitCmdSugar(os.Stdout)
	}
	return cmdSugar
}
