package chislog

import (
	"context"
	"log/slog"
	"net/http"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
)

type Slogger interface {
	Log(ctx context.Context, level slog.Level, msg string, args ...any)
}

// Logger returns a go-chi middleware that logs requests using the given Slogger.
func Logger(logger Slogger, opts ...Option) func(next http.Handler) http.Handler {
	cfg := newConfig(opts...)
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			for _, f := range cfg.filters {
				if !f(r) {
					next.ServeHTTP(w, r)
					return
				}
			}

			ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
			start := time.Now()

			next.ServeHTTP(ww, r)

			end := time.Now()
			status := ww.Status()

			logger.Log(
				r.Context(),
				statusLevel(status),
				statusLabel(status),
				slog.String("routePattern", chi.RouteContext(r.Context()).RoutePattern()),
				slog.String("httpRequest.method", r.Method),
				slog.String("httpRequest.uri", r.RequestURI),
				slog.String("httpRequest.referer", r.Referer()),
				slog.String("httpRequest.userAgent", r.UserAgent()),
				slog.String("httpRequest.proto", r.Proto),
				slog.String("httpRequest.ip", cfg.ipExtractor(r)),
				slog.Int("httpResponse.status", status),
				slog.Int("httpResponse.bytes", ww.BytesWritten()),
				//nolint:gomnd
				slog.Float64("httpResponse.duration", float64(end.Sub(start).Nanoseconds())/1000000.0), // in milliseconds
			)
		})
	}
}

func statusLevel(status int) slog.Level {
	switch {
	case status < http.StatusContinue:
		return slog.LevelWarn
	case status >= http.StatusBadRequest && status < http.StatusInternalServerError:
		return slog.LevelWarn
	case status >= http.StatusInternalServerError:
		return slog.LevelError
	default: // for statuses 100 <= 400
		return slog.LevelInfo
	}
}

func statusLabel(status int) string {
	switch {
	case status >= http.StatusContinue && status < http.StatusMultipleChoices:
		return "OK"
	case status >= http.StatusMultipleChoices && status < http.StatusBadRequest:
		return "Redirect"
	case status >= http.StatusBadRequest && status < http.StatusInternalServerError:
		return "Client Error"
	case status >= http.StatusInternalServerError:
		return "Server Error"
	default:
		return "Unknown"
	}
}
