package httpserver

import (
	"errors"
	"net/http"
	"strings"

	"update_server/internal/apikeys"
)

func (h *handler) requireAPIKey(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if h.apiKeys == nil {
			writeJSON(w, http.StatusServiceUnavailable, map[string]any{"error": "api key auth is unavailable"})
			return
		}

		token, ok := bearerToken(r.Header.Get("Authorization"))
		if !ok {
			writeAPIKeyAuthError(w, http.StatusUnauthorized, "missing bearer api key")
			return
		}

		state, err := h.apiKeys.Authenticate(r.Context(), token)
		if err != nil {
			if errors.Is(err, apikeys.ErrUnauthenticated) {
				writeAPIKeyAuthError(w, http.StatusUnauthorized, "invalid api key")
				return
			}

			writeJSON(w, http.StatusInternalServerError, map[string]any{"error": "api key lookup failed"})
			return
		}

		next.ServeHTTP(w, r.WithContext(apikeys.NewContext(r.Context(), state)))
	})
}

func (h *handler) requireAPIKeyPermission(permission apikeys.Permission) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			state, ok := apikeys.FromContext(r.Context())
			if !ok {
				writeAPIKeyAuthError(w, http.StatusUnauthorized, "api key authentication required")
				return
			}

			if !apikeys.HasPermission(state.APIKey, permission) {
				writeJSON(w, http.StatusForbidden, map[string]any{"error": "api key permission denied"})
				return
			}

			next.ServeHTTP(w, r)
		})
	}
}

func (h *handler) requireAPIKeyProjectAccess(routeParam string) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			state, ok := apikeys.FromContext(r.Context())
			if !ok {
				writeAPIKeyAuthError(w, http.StatusUnauthorized, "api key authentication required")
				return
			}

			projectID, err := routeID(r, routeParam)
			if err != nil {
				writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid project id"})
				return
			}

			allowed, err := h.apiKeys.CanAccessProject(r.Context(), state.APIKey, projectID)
			if err != nil {
				writeJSON(w, http.StatusInternalServerError, map[string]any{"error": "project access lookup failed"})
				return
			}

			if !allowed {
				writeJSON(w, http.StatusForbidden, map[string]any{"error": "api key cannot access this project"})
				return
			}

			next.ServeHTTP(w, r)
		})
	}
}

func bearerToken(header string) (string, bool) {
	header = strings.TrimSpace(header)
	if header == "" {
		return "", false
	}

	parts := strings.Fields(header)
	if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") {
		return "", false
	}

	token := strings.TrimSpace(parts[1])
	if token == "" {
		return "", false
	}

	return token, true
}

func writeAPIKeyAuthError(w http.ResponseWriter, status int, message string) {
	w.Header().Set("WWW-Authenticate", `Bearer realm="update-server"`)
	writeJSON(w, status, map[string]any{"error": message})
}
