package util

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

const (
	colorReset  = "\033[0m"
	colorRed    = "\033[31m"
	colorGreen  = "\033[32m"
	colorYellow = "\033[33m"
	colorBlue   = "\033[34m"
	colorGray   = "\033[90m"
)

type ColorHandler struct {
	w        io.Writer
	level    slog.Level
	preAttrs []slog.Attr
}

func NewColorHandler(w io.Writer, opts *slog.HandlerOptions) *ColorHandler {
	level := slog.LevelInfo
	if opts != nil && opts.Level != nil {
		level = opts.Level.Level()
	}
	return &ColorHandler{
		w:     w,
		level: level,
	}
}

func (h *ColorHandler) Enabled(ctx context.Context, level slog.Level) bool {
	return level >= h.level
}

func (h *ColorHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
	newH := *h
	newH.preAttrs = append(append([]slog.Attr{}, h.preAttrs...), attrs...)
	return &newH
}

func (h *ColorHandler) WithGroup(name string) slog.Handler {
	return h
}

func (h *ColorHandler) Handle(ctx context.Context, r slog.Record) error {
	level := r.Level.String()
	var color string

	switch r.Level {
	case slog.LevelDebug:
		color = colorGray
	case slog.LevelInfo:
		color = colorBlue
	case slog.LevelWarn:
		color = colorYellow
	case slog.LevelError:
		color = colorRed
	default:
		color = colorReset
	}

	timestamp := r.Time.Format("3:04:05 PM")
	_, _ = fmt.Fprintf(h.w, "%s%s%s [%s%s%s] %s", //nolint:errcheck
		colorGray, timestamp, colorReset,
		color, level, colorReset,
		r.Message)

	for _, a := range h.preAttrs {
		_, _ = fmt.Fprintf(h.w, " %s=%v", a.Key, a.Value) //nolint:errcheck
	}

	r.Attrs(func(a slog.Attr) bool {
		_, _ = fmt.Fprintf(h.w, " %s=%v", a.Key, a.Value) //nolint:errcheck
		return true
	})

	_, _ = fmt.Fprintln(h.w) //nolint:errcheck
	return nil
}

func NewLogger(verbose bool) *slog.Logger {
	level := slog.LevelInfo
	if verbose {
		level = slog.LevelDebug
	}

	handler := NewColorHandler(os.Stdout, &slog.HandlerOptions{
		Level: level,
	})

	return slog.New(handler)
}

func LogError(logger *slog.Logger, msg string, err error, attrs ...any) error {
	allAttrs := append([]any{"error", err}, attrs...)
	logger.Error(msg, allAttrs...)
	return fmt.Errorf("%s: %w", msg, err)
}
