package middleware

import (
	"time"

	logpkg "Noooste/garage-ui/pkg/logger"

	"github.com/gofiber/fiber/v3"
	"github.com/rs/zerolog"
)

// LoggerLocalsKey is the fiber.Ctx.Locals key carrying the per-request logger.
const LoggerLocalsKey = "logger"

// Logging returns middleware that (1) builds a per-request zerolog.Logger
// bound with request_id/method/path/remote_ip/user_agent, (2) injects it into
// c.Context() so service layers can retrieve it via logger.FromCtx, and
// (3) emits a single access-log line after the handler runs.
//
// The base logger is the one to derive from — typically the global zerolog
// logger configured at startup. Tests pass a buffer-backed logger here.
//
// Access-log line fields: request_id, method, path, remote_ip, user_agent,
// status, duration_ms, bytes_out. Skipped for /health and OPTIONS.
// Level is chosen from status: >=500 error, >=400 warn, else info.
func Logging(base zerolog.Logger) fiber.Handler {
	return func(c fiber.Ctx) error {
		requestID, _ := c.Locals(RequestIDLocalsKey).(string)

		reqLogger := base.With().
			Str("request_id", requestID).
			Str("method", c.Method()).
			Str("path", c.Path()).
			Str("remote_ip", c.IP()).
			Str("user_agent", c.Get("User-Agent")).
			Logger()

		c.Locals(LoggerLocalsKey, reqLogger)
		c.SetContext(logpkg.IntoCtx(c.Context(), reqLogger))

		start := time.Now()
		err := c.Next()
		duration := time.Since(start)

		if skipAccessLog(c) {
			return err
		}

		status := c.Response().StatusCode()
		bytesOut := len(c.Response().Body())

		evt := eventForStatus(&reqLogger, status)
		evt.
			Int("status", status).
			Float64("duration_ms", float64(duration.Microseconds())/1000.0).
			Int("bytes_out", bytesOut).
			Msg("http_request")

		return err
	}
}

func skipAccessLog(c fiber.Ctx) bool {
	if c.Method() == fiber.MethodOptions {
		return true
	}
	if c.Path() == "/health" {
		return true
	}
	return false
}

func eventForStatus(l *zerolog.Logger, status int) *zerolog.Event {
	switch {
	case status >= 500:
		return l.Error()
	case status >= 400:
		return l.Warn()
	default:
		return l.Info()
	}
}
