package log

import (
	"os"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/rs/zerolog"
)

const defaultPerms = 0o0600

//nolint:gochecknoglobals
var loggerSetTimeFormat sync.Once

// Logger extends zerolog's Logger.
type Logger struct {
	zerolog.Logger
}

func (l Logger) Println(v ...interface{}) {
	l.Logger.Error().Msg("panic recovered") //nolint: check-logs
}

func NewLogger(level, output string) Logger {
	loggerSetTimeFormat.Do(func() {
		zerolog.TimeFieldFormat = time.RFC3339Nano
	})

	lvl, err := zerolog.ParseLevel(level)
	if err != nil {
		panic(err)
	}

	zerolog.SetGlobalLevel(lvl)

	var log zerolog.Logger

	if output == "" {
		log = zerolog.New(os.Stdout)
	} else {
		file, err := os.OpenFile(output, os.O_APPEND|os.O_WRONLY|os.O_CREATE, defaultPerms)
		if err != nil {
			panic(err)
		}

		log = zerolog.New(file)
	}

	return Logger{Logger: log.Hook(goroutineHook{}).With().Caller().Timestamp().Logger()}
}

func NewAuditLogger(level, output string) *Logger {
	loggerSetTimeFormat.Do(func() {
		zerolog.TimeFieldFormat = time.RFC3339Nano
	})

	lvl, err := zerolog.ParseLevel(level)
	if err != nil {
		panic(err)
	}

	zerolog.SetGlobalLevel(lvl)

	var auditLog zerolog.Logger

	if output == "" {
		auditLog = zerolog.New(os.Stdout)
	} else {
		auditFile, err := os.OpenFile(output, os.O_APPEND|os.O_WRONLY|os.O_CREATE, defaultPerms)
		if err != nil {
			panic(err)
		}

		auditLog = zerolog.New(auditFile)
	}

	return &Logger{Logger: auditLog.With().Timestamp().Logger()}
}

// GoroutineID adds goroutine-id to logs to help debug concurrency issues.
func GoroutineID() int {
	var buf [64]byte
	n := runtime.Stack(buf[:], false)
	idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]

	id, err := strconv.Atoi(idField)
	if err != nil {
		return -1
	}

	return id
}

type goroutineHook struct{}

func (h goroutineHook) Run(e *zerolog.Event, level zerolog.Level, _ string) {
	if level != zerolog.NoLevel {
		e.Int("goroutine", GoroutineID())
	}
}
