// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package plog

import (
	"context"
	"fmt"
	"net/url"
	"sync"

	"github.com/go-logr/logr"
	"go.uber.org/zap"
	"k8s.io/component-base/logs"
	"k8s.io/klog/v2"
)

//nolint:gochecknoglobals
var (
	// note that these globals have no locks on purpose - they are expected to be set at init and then again after config parsing.
	globalLevel  zap.AtomicLevel
	globalLogger logr.Logger
	globalFlush  func()

	// used as a temporary storage for a buffer per call of newLogr. see the init function below for more details.
	sinkMap sync.Map
)

//nolint:gochecknoinits
func init() {
	// make sure we always have a functional global logger
	globalLevel = zap.NewAtomicLevelAt(0) // log at the 0 verbosity level to start with, i.e. the "always" logs
	// use json encoding to start with
	// the context here is just used for test injection and thus can be ignored
	log, flush, err := newLogr(context.Background(), "json", 0)
	if err != nil {
		panic(err) // default logging config must always work
	}
	setGlobalLoggers(log, flush)

	// this is a little crazy but zap's builder code does not allow us to directly specify what
	// writer we want to use as our log sink.  to get around this limitation in tests, we use a
	// global map to temporarily hold the writer (the key is a random string that is generated
	// per invocation of newLogr).  we register a fake "pinniped" scheme so that we can lookup
	// the writer via pinniped:///<per newLogr invocation random string>.
	if err := zap.RegisterSink("pinniped", func(u *url.URL) (zap.Sink, error) {
		value, ok := sinkMap.Load(u.Path)
		if !ok {
			return nil, fmt.Errorf("key %q not in global sink", u.Path)
		}
		return value.(zap.Sink), nil
	}); err != nil {
		panic(err) // custom sink must always work
	}
}

// Deprecated: Use New instead.  This is meant for old code only.
// New provides a more ergonomic API and correctly responds to global log config change.
func Logr() logr.Logger {
	return globalLogger
}

func Setup() func() {
	logs.InitLogs()
	return func() {
		logs.FlushLogs()
		globalFlush()
	}
}

// setGlobalLoggers sets the plog and klog global loggers.  it is *not* go routine safe.
func setGlobalLoggers(log logr.Logger, flush func()) {
	// a contextual logger does its own level based enablement checks, which is true for all of our loggers
	klog.SetLoggerWithOptions(log, klog.ContextualLogger(true), klog.FlushLogger(flush))
	globalLogger = log
	globalFlush = flush
}