package auth

import (
	"context"
	"net/http"

	"github.com/google/uuid"
)

// AdminLookup is the minimal interface RequireAdmin needs to consult the
// caller's paliad.users row. Implemented by services.UserService — kept as an
// interface here so the auth package doesn't import services (which would be
// a layering inversion: services depends on auth, not the other way around).
type AdminLookup interface {
	IsAdmin(ctx context.Context, userID uuid.UUID) (bool, error)
}

// RequireAdmin wraps a handler so only callers whose paliad.users row has
// role='admin' may proceed. Anyone else gets 403 (JSON for /api/*, redirect
// to /dashboard for browser paths).
//
// Must run downstream of Client.Middleware + Client.WithUserID — the user's
// UUID is read from the request context that those populate.
//
// If the lookup itself errors, the request is rejected with 500 rather than
// fail-open: an admin-gated endpoint that silently lets non-admins through
// when the DB blips is the worst possible failure mode.
func RequireAdmin(lookup AdminLookup) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			uid, ok := UserIDFromContext(r.Context())
			if !ok {
				rejectAdmin(w, r, http.StatusUnauthorized, "authentication required")
				return
			}
			ok, err := lookup.IsAdmin(r.Context(), uid)
			if err != nil {
				rejectAdmin(w, r, http.StatusInternalServerError, "internal error")
				return
			}
			if !ok {
				rejectAdmin(w, r, http.StatusForbidden, "admin access required")
				return
			}
			next.ServeHTTP(w, r)
		})
	}
}

// RequireAdminFunc is the http.HandlerFunc-flavoured wrapper, since most of
// the protected mux is registered with HandleFunc.
func RequireAdminFunc(lookup AdminLookup, h http.HandlerFunc) http.HandlerFunc {
	wrapped := RequireAdmin(lookup)(h)
	return wrapped.ServeHTTP
}

func rejectAdmin(w http.ResponseWriter, r *http.Request, status int, msg string) {
	if isAPIRequest(r) {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(status)
		// Hand-rolled to avoid pulling encoding/json for one constant payload.
		_, _ = w.Write([]byte(`{"error":"` + msg + `"}`))
		return
	}
	// Browser path: send the user back to /dashboard with a flash-style query
	// param the page can pick up if it wants to surface the message. Avoids
	// rendering a bare 403 the user has no obvious way to recover from.
	http.Redirect(w, r, "/dashboard?forbidden=admin", http.StatusFound)
}
