package logger

import (
	"fmt"
	"io"
	stdlog "log"
	"os"
	"testing"

	"github.com/fiskerinc/cloud-services/pkg/testhelper"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/diode"
)

func setupTestLogOutput() diode.Writer {
	wr := diode.NewWriter(os.Stdout, RING_BUFFER_SIZE, 0, func(missed int) {
		Logger.Error().Msgf("logger dropped %d messages", missed)
	})
	logger := zerolog.New(wr)
	Logger = &logger

	return wr
}

func TestGetLogger(t *testing.T) {
	GetLogger()
	logLevel := zerolog.GlobalLevel().String()
	expectedLevel := "info"
	if logLevel != expectedLevel {
		t.Errorf(testhelper.TestErrorTemplate, "TestLoggerInit", expectedLevel, logLevel)
	}
}

func TestLoggerSetGlobalLevel(t *testing.T) {
	logger := zerolog.New(io.Discard)
	Logger = &logger

	err := SetGlobalLevel("debug")
	if err != nil {
		t.Errorf(testhelper.TestErrorTemplate, "TestLoggerSetGlobalLevel", nil, err)
	}

	logLevel := zerolog.GlobalLevel().String()
	expectedLevel := "debug"

	if logLevel != expectedLevel {
		t.Errorf(testhelper.TestErrorTemplate, "TestLoggerSetGlobalLevel", expectedLevel, logLevel)
	}
}

func TestLoggerSetGlobalLevelUnknown(t *testing.T) {
	err := SetGlobalLevel("fisker")
	if err == nil {
		t.Errorf(testhelper.TestErrorTemplate, "TestLoggerSetGlobalLevelUnknown", "err", err)
	}
}

func ExampleDebug() {
	wr := setupTestLogOutput()
	defer wr.Close()

	Debug().Msg("Use this to record individual events")
	// Output: {"level":"debug","message":"Use this to record individual events"}
}

func ExampleInfo() {
	wr := setupTestLogOutput()
	defer wr.Close()

	Info().Msg("This is the default level")
	// Output: {"level":"info","message":"This is the default level"}
}

func ExampleWarn() {
	wr := setupTestLogOutput()
	defer wr.Close()

	Warn().Msg("Use this to notify of abnormal events/triggers")
	// Output: {"level":"warn","message":"Use this to notify of abnormal events/triggers"}
}

func ExampleError() {
	wr := setupTestLogOutput()
	defer wr.Close()

	Error().Err(fmt.Errorf("Use this as a substitute for panic/fatal")).Send()
	// Output: {"level":"error","message":"Use this as a substitute for panic/fatal"}
}

func ExampleConfidential() {
	wr := setupTestLogOutput()
	defer wr.Close()

	type User struct {
		Name     string
		Password string
		Phone    string
	}
	type VehicleData struct {
		Email string
		VIN   string
		Miles int
		User1 User
		User2 *User
	}

	confObject := &VehicleData{
		Email: "dtaylor@fiskerinc.com",
		VIN:   "5VST414",
		Miles: 29500,
		User1: User{
			Name:     "Henrik Fisker",
			Password: "ocean123!",
			Phone:    "+45(123)456-7890",
		},
		User2: &User{
			Name:     "Drew Taylor",
			Password: "Emotion123!",
			Phone:    "+1(123)456-7890",
		},
	}
	Info().Confidential(confObject).Send()
	// Output: {"level":"info","Email":"d******@fiskerinc.com","VIN":"5VST414","Miles":29500,"User1":{"Name":"Henrik Fisker","Password":"*********","Phone":"+4*(1**)4**-7***"},"User2":{"Name":"Drew Taylor","Password":"***********","Phone":"+1(1**)4**-7***"}}
}

func ExampleContext() {
	wr := setupTestLogOutput()
	defer wr.Close()

	Info().
		Str("vehicle", "Ocean").
		Str("vin", "ABC123").
		Int("miles", 8077).
		Send()
	// Output: {"level":"info","vehicle":"Ocean","vin":"ABC123","miles":8077}
}

func ExampleSublog() {
	wr := setupTestLogOutput()
	defer wr.Close()

	sublogger := Logger.With().
		Str("component", "test").
		Logger()
	sublogger.Info().Msg("Branch sublogs off root logger")
	// Output: {"level":"info","component":"test","message":"Branch sublogs off root logger"}
}

func BenchmarkZeroLogDefault(b *testing.B) {
	logger := zerolog.New(io.Discard)
	for i := 0; i < b.N; i++ {
		logger.Info().Msg("hello fisker")
	}
}

func BenchmarkZeroLogWithTime(b *testing.B) {
	logger := zerolog.New(io.Discard).With().Timestamp().Logger()
	for i := 0; i < b.N; i++ {
		logger.Info().Msg("hello fisker")
	}
}

func BenchmarkZeroLogWithCaller(b *testing.B) {
	logger := zerolog.New(io.Discard).With().Caller().Logger()
	for i := 0; i < b.N; i++ {
		logger.Info().Msg("hello fisker")
	}
}

func BenchmarkZeroLogWithCallerAndTime(b *testing.B) {
	logger := zerolog.New(io.Discard).With().Timestamp().Caller().Logger()
	for i := 0; i < b.N; i++ {
		logger.Info().Msg("hello fisker")
	}
}

func BenchmarkNativeLogDefault(b *testing.B) {
	stdlog.SetOutput(io.Discard)
	for i := 0; i < b.N; i++ {
		stdlog.Print("hello fisker")
	}
}

func BenchmarkNativeLogWithFields(b *testing.B) {
	stdlog.SetOutput(io.Discard)
	stdlog.SetFlags(stdlog.Ldate | stdlog.Ltime | stdlog.Lmicroseconds | stdlog.Llongfile)
	for i := 0; i < b.N; i++ {
		stdlog.Print("hello fisker")
	}
}
