package grpctool

import (
	"context"
	"fmt"
	"log/slog"
	"os"
	"runtime"
	"time"
)

// Logger implements gRPC logging interface on top of slog.Handler.
// We don't have an official adapter, unfortunately: https://github.com/grpc/grpc-go/issues/6590.
type Logger struct {
	Handler slog.Handler
}

func (l *Logger) Info(args ...any) {
	l.log(slog.LevelInfo, args...)
}

func (l *Logger) Infoln(args ...any) {
	l.logln(slog.LevelInfo, args...)
}

func (l *Logger) Infof(format string, args ...any) {
	l.logf(slog.LevelInfo, format, args...)
}

func (l *Logger) InfoDepth(depth int, args ...any) {
	l.logDepth(slog.LevelInfo, depth, args...)
}

func (l *Logger) Warning(args ...any) {
	l.log(slog.LevelWarn, args...)
}

func (l *Logger) Warningln(args ...any) {
	l.logln(slog.LevelWarn, args...)
}

func (l *Logger) Warningf(format string, args ...any) {
	l.logf(slog.LevelWarn, format, args...)
}

func (l *Logger) WarningDepth(depth int, args ...any) {
	l.logDepth(slog.LevelWarn, depth, args...)
}

func (l *Logger) Error(args ...any) {
	l.log(slog.LevelError, args...)
}

func (l *Logger) Errorln(args ...any) {
	l.logln(slog.LevelError, args...)
}

func (l *Logger) Errorf(format string, args ...any) {
	l.logf(slog.LevelError, format, args...)
}

func (l *Logger) ErrorDepth(depth int, args ...any) {
	l.logDepth(slog.LevelError, depth, args...)
}

func (l *Logger) Fatal(args ...any) {
	l.log(slog.LevelError, args...) // Don't delegate to ErrorDepth() - will break the source detection (depth).
	os.Exit(1)
}

func (l *Logger) Fatalln(args ...any) {
	l.logln(slog.LevelError, args...) // Don't delegate to ErrorDepth() - will break the source detection (depth).
	os.Exit(1)
}

func (l *Logger) Fatalf(format string, args ...any) {
	l.logf(slog.LevelError, format, args...) // Don't delegate to ErrorDepth() - will break the source detection (depth).
	os.Exit(1)
}

func (l *Logger) FatalDepth(depth int, args ...any) {
	l.logDepth(slog.LevelError, depth, args...) // Don't delegate to ErrorDepth() - will break the source detection (depth).
	os.Exit(1)
}

func (l *Logger) V(lvl int) bool {
	// More verbose means more messages are allowed.
	// 0 -> error
	// 1 -> warn
	// 2 -> info // gRPC uses 2 and then logs on Info.
	// 3 -> debug
	var slvl slog.Level
	switch lvl {
	case 0:
		slvl = slog.LevelError
	case 1:
		slvl = slog.LevelWarn
	case 2:
		slvl = slog.LevelInfo
	case 3:
		slvl = slog.LevelDebug
	default:
		// We don't know what this means. Let the caller proceed with calling a logging method and then we'll
		// decide if it should be logged or not.
		return true
	}
	return l.Handler.Enabled(context.Background(), slvl)
}

func (l *Logger) handle(level slog.Level, msg string) {
	l.handleDepth(level, 6, msg)
}

func (l *Logger) handleDepth(level slog.Level, depth int, msg string) {
	r := slog.NewRecord(time.Now(), level, msg, callerPC(depth))
	_ = l.Handler.Handle(context.Background(), r)
}

func (l *Logger) log(level slog.Level, args ...any) {
	if !l.Handler.Enabled(context.Background(), level) {
		return
	}
	l.handle(level, fmt.Sprint(args...))
}

func (l *Logger) logln(level slog.Level, args ...any) {
	if !l.Handler.Enabled(context.Background(), level) {
		return
	}
	s := fmt.Sprintln(args...)
	s = s[:len(s)-1] // remove \n
	l.handle(level, s)
}

func (l *Logger) logf(level slog.Level, format string, args ...any) {
	if !l.Handler.Enabled(context.Background(), level) {
		return
	}
	l.handle(level, fmt.Sprintf(format, args...))
}

func (l *Logger) logDepth(level slog.Level, depth int, args ...any) {
	if !l.Handler.Enabled(context.Background(), level) {
		return
	}
	l.handleDepth(level, 5+depth, fmt.Sprint(args...))
}

// callerPC returns the program counter at the given stack depth.
func callerPC(depth int) uintptr {
	var pcs [1]uintptr
	runtime.Callers(depth, pcs[:])
	return pcs[0]
}
