package middleware

import (
	"crypto/rand"
	"encoding/hex"
	"errors"
	"fmt"
	"log/slog"
	"net"
	"net/http"
	"runtime/debug"
	"time"

	"github.com/labstack/echo/v4"
)

func RequestID() echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			id := c.Request().Header.Get(echo.HeaderXRequestID)
			if id == "" {
				id = newRequestID()
			}
			c.Response().Header().Set(echo.HeaderXRequestID, id)
			c.Set(echo.HeaderXRequestID, id)
			return next(c)
		}
	}
}

func newRequestID() string {
	var buf [16]byte
	if _, err := rand.Read(buf[:]); err != nil {
		return fmt.Sprintf("req-%d", time.Now().UnixNano())
	}
	return hex.EncodeToString(buf[:])
}

func RealIP() echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			if forwarded := c.Request().Header.Get("X-Forwarded-For"); forwarded != "" {
				c.Set("real_ip", forwarded)
			} else if host, _, err := net.SplitHostPort(c.Request().RemoteAddr); err == nil {
				c.Set("real_ip", host)
			}
			return next(c)
		}
	}
}

func AccessLog(logger *slog.Logger) echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			start := time.Now()
			err := next(c)
			session := GetSession(c)
			customerID := int64Value(session.CustomerID)
			cartID := int64Value(session.CartID)

			logger.Info("request complete",
				"method", c.Request().Method,
				"path", c.Request().URL.Path,
				"status", c.Response().Status,
				"latency_ms", time.Since(start).Milliseconds(),
				"request_id", c.Response().Header().Get(echo.HeaderXRequestID),
				"parse_status", session.ParseStatus,
				"customer_id", customerID,
				"cart_id", cartID,
			)

			return err
		}
	}
}

func Recover(logger *slog.Logger) echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			defer func() {
				if recovered := recover(); recovered != nil {
					logger.Error("panic recovered", "error", recovered, "stack", string(debug.Stack()))
					_ = c.JSON(http.StatusInternalServerError, map[string]string{"error": "internal server error"})
				}
			}()
			return next(c)
		}
	}
}

func HTTPErrorHandler(logger *slog.Logger) echo.HTTPErrorHandler {
	return func(err error, c echo.Context) {
		if c.Response().Committed {
			return
		}

		var httpErr *echo.HTTPError
		code := http.StatusInternalServerError
		message := map[string]string{"error": "internal server error"}

		if errors.As(err, &httpErr) {
			code = httpErr.Code
			if msg, ok := httpErr.Message.(string); ok {
				message = map[string]string{"error": msg}
			}
		}

		logger.Error("request failed", "status", code, "error", err)
		_ = c.JSON(code, message)
	}
}

func int64Value(value *int64) any {
	if value == nil {
		return nil
	}
	return *value
}
