package main

import (
	"context"
	"fmt"
	"io"
	"log/slog"
	"os"

	"github.com/urfave/cli/v2"
)

type logLevel string

const (
	logLevelDebug logLevel = "debug"
	logLevelInfo  logLevel = "info"
	logLevelWarn  logLevel = "warn"
	logLevelError logLevel = "error"
)

func (l logLevel) slogLevel() slog.Level {
	switch l {
	case logLevelDebug:
		return slog.LevelDebug
	case logLevelInfo:
		return slog.LevelInfo
	case logLevelWarn:
		return slog.LevelWarn
	case logLevelError:
		return slog.LevelError
	default:
		panic("unknown log level: " + l)
	}
}

func (l logLevel) String() string {
	return string(l)
}

type logFormat string

const (
	logFormatText logFormat = "text"
	logFormatJSON logFormat = "json"
)

func (f logFormat) newHandler(w io.Writer, opts *slog.HandlerOptions) slog.Handler {
	switch f {
	case logFormatText:
		return slog.NewTextHandler(w, opts)
	case logFormatJSON:
		return slog.NewJSONHandler(w, opts)
	default:
		panic("unknown log format: " + f)
	}
}

func (f logFormat) String() string {
	return string(f)
}

var (
	logFlagLevel = &cli.GenericFlag{
		Name: "log.level",
		Value: &EnumValue{
			Enum: []string{
				logLevelDebug.String(),
				logLevelInfo.String(),
				logLevelWarn.String(),
				logLevelError.String(),
			},
			Default: logLevelInfo.String(),
		},
		Usage:   fmt.Sprintf("%s, %s, %s or %s", logLevelDebug, logLevelInfo, logLevelWarn, logLevelError),
		EnvVars: []string{"LOG_LEVEL"},
	}
	logFlagFormat = &cli.GenericFlag{
		Name: "log.format",
		Value: &EnumValue{
			Enum: []string{
				logFormatJSON.String(),
				logFormatText.String(),
			},
			Default: logFormatText.String(),
		},
		Usage:   fmt.Sprintf("%s or %s", logFormatText, logFormatJSON),
		EnvVars: []string{"LOG_FORMAT"},
	}
	logFlags = []cli.Flag{
		logFlagLevel,
		logFlagFormat,
	}
)

func newLoggerFromFlags(c *cli.Context) *slog.Logger {
	return newLogger(loggerConfig{
		level:  logLevel(c.String(logFlagLevel.Name)),
		format: logFormat(c.String(logFlagFormat.Name)),
	})
}

type loggerConfig struct {
	level  logLevel
	format logFormat
}

func newLogger(cfg loggerConfig) *slog.Logger {
	return slog.New(cfg.format.newHandler(os.Stderr, &slog.HandlerOptions{
		Level: cfg.level.slogLevel(),
	}))
}

type loggerCtxKey struct{}

func loggerToCtx(ctx context.Context, l *slog.Logger) context.Context {
	return context.WithValue(ctx, loggerCtxKey{}, l)
}

func loggerFromCtx(ctx context.Context) *slog.Logger {
	logger, _ := ctx.Value(loggerCtxKey{}).(*slog.Logger)
	return logger
}
