package handlers

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"path"
	"strconv"
	"time"

	"warpbox.dev/backend/libs/jobs"
	"warpbox.dev/backend/libs/services"
	"warpbox.dev/backend/libs/web"
)

const adminCookieName = "warpbox_admin"

type adminPageData struct {
	Stats         services.AdminStats
	Boxes         []adminBoxView
	Users         []adminUserView
	Settings      services.UploadPolicySettings
	Storage       []services.StorageBackendView
	UserEdit      adminUserEditView
	StorageForm   adminStorageFormView
	StorageTest   adminStorageTestView
	StorageTypes  []adminStorageProviderView
	Section       string
	PageTitle     string
	LastInviteURL string
	Notice        string
	Error         string
}

type adminStorageFormView struct {
	Mode          string
	Provider      string
	ProviderLabel string
	Action        string
	BackHref      string
	Config        services.StorageBackendConfig
}

type adminStorageTestView struct {
	Config     services.StorageBackendConfig
	UsageLabel string
	Tests      []services.StorageSpeedTest
	CanRun     bool
}

type adminStorageSpeedTestJSON struct {
	ID            string `json:"id"`
	Mode          string `json:"mode"`
	ModeLabel     string `json:"modeLabel"`
	Status        string `json:"status"`
	Stage         string `json:"stage"`
	Progress      int    `json:"progress"`
	CustomLabel   string `json:"customLabel,omitempty"`
	StartedLabel  string `json:"startedLabel"`
	FinishedLabel string `json:"finishedLabel"`
	Files         int    `json:"files"`
	SizeLabel     string `json:"sizeLabel"`
	WriteSpeed    string `json:"writeSpeed"`
	ReadSpeed     string `json:"readSpeed"`
	Error         string `json:"error,omitempty"`
}

type adminStorageProviderView struct {
	Provider    string
	Label       string
	Description string
	Icon        string
}

type adminBoxView struct {
	ID             string
	Owner          string
	CreatedAt      string
	ExpiresAt      string
	FileCount      int
	TotalSizeLabel string
	DownloadCount  int
	MaxDownloads   int
	Protected      bool
	Expired        bool
}

type adminUserView struct {
	ID             string
	Username       string
	Email          string
	Role           string
	Status         string
	StorageUsed    string
	StorageQuota   string
	DailyUsed      string
	StorageBackend string
	CreatedAt      string
}

type adminUserEditView struct {
	ID                   string
	Username             string
	Email                string
	Role                 string
	Status               string
	StorageUsed          string
	DailyUsed            string
	EffectiveStorage     string
	EffectiveDaily       string
	EffectiveMaxDays     int
	EffectiveDailyBoxes  int
	EffectiveActiveBoxes int
	EffectiveBackend     string
	MaxUploadMB          string
	DailyUploadMB        string
	StorageQuotaMB       string
	MaxDays              string
	DailyBoxes           string
	ActiveBoxes          string
	ShortWindowRequests  string
	StorageBackendID     string
}

func (a *App) AdminLogin(w http.ResponseWriter, r *http.Request) {
	if a.isAdmin(r) {
		http.Redirect(w, r, "/admin", http.StatusSeeOther)
		return
	}
	a.renderAdminLogin(w, r, http.StatusOK, "")
}

func (a *App) AdminLoginPost(w http.ResponseWriter, r *http.Request) {
	if !a.rateLimiter.Allow("admin-login:"+uploadClientIP(r), 10, time.Minute, time.Now().UTC()) {
		a.renderAdminLogin(w, r, http.StatusTooManyRequests, "Too many admin login attempts.")
		return
	}
	if err := r.ParseForm(); err != nil {
		a.renderAdminLogin(w, r, http.StatusBadRequest, "Unable to read login form.")
		return
	}
	if a.cfg.AdminToken == "" || r.FormValue("token") != a.cfg.AdminToken {
		a.logger.Warn("admin login failed", "source", "admin", "severity", "warn", "code", 4301)
		a.renderAdminLogin(w, r, http.StatusUnauthorized, "Invalid admin token.")
		return
	}

	http.SetCookie(w, &http.Cookie{
		Name:     adminCookieName,
		Value:    adminCookieValue(a.cfg.AdminToken),
		Path:     "/admin",
		HttpOnly: true,
		SameSite: http.SameSiteLaxMode,
		Secure:   r.TLS != nil,
		Expires:  time.Now().Add(12 * time.Hour),
	})
	a.logger.Info("admin login", "source", "admin", "severity", "user_activity", "code", 2301)
	http.Redirect(w, r, "/admin", http.StatusSeeOther)
}

func (a *App) AdminLogout(w http.ResponseWriter, r *http.Request) {
	if !a.validateCSRF(w, r) {
		return
	}
	a.clearUserSessionCookie(w)
	http.SetCookie(w, &http.Cookie{
		Name:     adminCookieName,
		Value:    "",
		Path:     "/admin",
		HttpOnly: true,
		SameSite: http.SameSiteLaxMode,
		MaxAge:   -1,
	})
	http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
}

func (a *App) AdminDashboard(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) {
		return
	}

	stats, err := a.uploadService.AdminStats()
	if err != nil {
		http.Error(w, "unable to load admin stats", http.StatusInternalServerError)
		return
	}
	boxes, err := a.adminBoxes(8)
	if err != nil {
		http.Error(w, "unable to load recent boxes", http.StatusInternalServerError)
		return
	}

	a.renderPage(w, r, http.StatusOK, "admin.html", web.PageData{
		Title:       "Admin overview",
		Description: "Warpbox admin overview.",
		CurrentUser: a.currentPublicUser(r),
		Data: adminPageData{
			Stats:     stats,
			Boxes:     boxes,
			Section:   "overview",
			PageTitle: "Admin overview",
		},
	})
}

func (a *App) AdminFiles(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) {
		return
	}

	stats, err := a.uploadService.AdminStats()
	if err != nil {
		http.Error(w, "unable to load admin stats", http.StatusInternalServerError)
		return
	}
	boxes, err := a.adminBoxes(100)
	if err != nil {
		http.Error(w, "unable to load boxes", http.StatusInternalServerError)
		return
	}

	a.renderPage(w, r, http.StatusOK, "admin.html", web.PageData{
		Title:       "Admin files",
		Description: "Manage Warpbox uploads.",
		CurrentUser: a.currentPublicUser(r),
		Data: adminPageData{
			Stats:     stats,
			Boxes:     boxes,
			Section:   "files",
			PageTitle: "Admin files",
		},
	})
}

func (a *App) AdminUsers(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) {
		return
	}
	stats, err := a.uploadService.AdminStats()
	if err != nil {
		http.Error(w, "unable to load admin stats", http.StatusInternalServerError)
		return
	}
	users, err := a.authService.ListUsers()
	if err != nil {
		http.Error(w, "unable to load users", http.StatusInternalServerError)
		return
	}
	rows := make([]adminUserView, 0, len(users))
	settings, err := a.settingsService.UploadPolicy()
	if err != nil {
		http.Error(w, "unable to load settings", http.StatusInternalServerError)
		return
	}
	for _, user := range users {
		storageUsed, _ := a.uploadService.UserActiveStorageUsed(user.ID)
		usage, _ := a.settingsService.UsageForUser(user.ID, time.Now().UTC())
		policy := a.settingsService.EffectivePolicyForUser(settings, user)
		quota := "unlimited"
		if policy.StorageQuotaSet {
			quota = formatMB(policy.StorageQuotaMB)
		}
		rows = append(rows, adminUserView{
			ID:             user.ID,
			Username:       user.Username,
			Email:          user.Email,
			Role:           user.Role,
			Status:         user.Status,
			StorageUsed:    services.FormatMegabytesFromBytes(storageUsed),
			StorageQuota:   quota,
			DailyUsed:      services.FormatMegabytesFromBytes(usage.UploadedBytes),
			StorageBackend: policy.StorageBackendID,
			CreatedAt:      user.CreatedAt.Format("Jan 2 15:04"),
		})
	}
	a.renderPage(w, r, http.StatusOK, "admin_users.html", web.PageData{
		Title:       "Admin users",
		Description: "Manage Warpbox users and invites.",
		CurrentUser: a.currentPublicUser(r),
		Data: adminPageData{
			Stats:         stats,
			Users:         rows,
			Section:       "users",
			PageTitle:     "Users",
			LastInviteURL: r.URL.Query().Get("invite"),
		},
	})
}

func (a *App) AdminEditUser(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) {
		return
	}
	user, err := a.authService.UserByID(r.PathValue("userID"))
	if err != nil {
		http.NotFound(w, r)
		return
	}
	settings, err := a.settingsService.UploadPolicy()
	if err != nil {
		http.Error(w, "unable to load settings", http.StatusInternalServerError)
		return
	}
	storage, err := a.storageBackendViews()
	if err != nil {
		http.Error(w, "unable to load storage", http.StatusInternalServerError)
		return
	}
	edit, err := a.adminUserEdit(user, settings)
	if err != nil {
		http.Error(w, "unable to load user policy", http.StatusInternalServerError)
		return
	}
	a.renderPage(w, r, http.StatusOK, "admin_user_edit.html", web.PageData{
		Title:       "Edit user",
		Description: "Edit a Warpbox user.",
		CurrentUser: a.currentPublicUser(r),
		Data: adminPageData{
			UserEdit:      edit,
			Storage:       storage,
			Section:       "users",
			PageTitle:     "Edit user",
			LastInviteURL: r.URL.Query().Get("invite"),
			Error:         r.URL.Query().Get("error"),
		},
	})
}

func (a *App) AdminSettings(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) {
		return
	}
	settings, err := a.settingsService.UploadPolicy()
	if err != nil {
		http.Error(w, "unable to load settings", http.StatusInternalServerError)
		return
	}
	storage, err := a.storageBackendViews()
	if err != nil {
		http.Error(w, "unable to load storage", http.StatusInternalServerError)
		return
	}
	a.renderPage(w, r, http.StatusOK, "admin_settings.html", web.PageData{
		Title:       "Admin settings",
		Description: "Manage Warpbox upload policy.",
		CurrentUser: a.currentPublicUser(r),
		Data: adminPageData{
			Settings:  settings,
			Storage:   storage,
			Section:   "settings",
			PageTitle: "Settings",
		},
	})
}

func (a *App) AdminSettingsPost(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) {
		return
	}
	if err := r.ParseForm(); err != nil {
		http.Redirect(w, r, "/admin/settings", http.StatusSeeOther)
		return
	}
	settings, err := a.settingsService.UploadPolicy()
	if err != nil {
		http.Error(w, "unable to load settings", http.StatusInternalServerError)
		return
	}
	settings.AnonymousUploadsEnabled = r.FormValue("anonymous_uploads_enabled") == "on"
	if value := parsePositiveInt(r.FormValue("usage_retention_days")); value > 0 {
		settings.UsageRetentionDays = value
	}
	if value := parsePositiveFloat(r.FormValue("local_storage_max_gb")); value > 0 {
		settings.LocalStorageMaxGB = value
	}
	if value := parsePositiveInt(r.FormValue("anonymous_max_days")); value > 0 {
		settings.AnonymousMaxDays = value
	}
	if value := parsePositiveInt(r.FormValue("user_max_days")); value > 0 {
		settings.UserMaxDays = value
	}
	if value := parsePositiveInt(r.FormValue("anonymous_daily_boxes")); value > 0 {
		settings.AnonymousDailyBoxes = value
	}
	if value := parsePositiveInt(r.FormValue("user_daily_boxes")); value > 0 {
		settings.UserDailyBoxes = value
	}
	if value := parsePositiveInt(r.FormValue("anonymous_active_boxes")); value > 0 {
		settings.AnonymousActiveBoxes = value
	}
	if value := parsePositiveInt(r.FormValue("user_active_boxes")); value > 0 {
		settings.UserActiveBoxes = value
	}
	if value := parsePositiveInt(r.FormValue("short_window_requests")); value > 0 {
		settings.ShortWindowRequests = value
	}
	if value := parsePositiveInt(r.FormValue("short_window_seconds")); value > 0 {
		settings.ShortWindowSeconds = value
	}
	if value := r.FormValue("anonymous_storage_backend"); value != "" {
		settings.AnonymousStorageBackend = value
	}
	if value := r.FormValue("user_storage_backend"); value != "" {
		settings.UserStorageBackend = value
	}
	if settings.AnonymousMaxUploadMB, err = services.ParseMegabytesLimitValue(r.FormValue("anonymous_max_upload_mb")); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	if settings.AnonymousDailyUploadMB, err = services.ParseMegabytesLimitValue(r.FormValue("anonymous_daily_upload_mb")); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	if settings.UserDailyUploadMB, err = services.ParseMegabytesLimitValue(r.FormValue("user_daily_upload_mb")); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	if settings.DefaultUserStorageMB, err = services.ParseMegabytesValue(r.FormValue("default_user_storage_mb")); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	if settings.UsageRetentionDays <= 0 {
		http.Error(w, "usage retention days must be positive", http.StatusBadRequest)
		return
	}
	if _, err := a.uploadService.Storage().BackendConfig(settings.AnonymousStorageBackend); err != nil {
		http.Error(w, "anonymous storage backend not found", http.StatusBadRequest)
		return
	}
	if _, err := a.uploadService.Storage().BackendConfig(settings.UserStorageBackend); err != nil {
		http.Error(w, "user storage backend not found", http.StatusBadRequest)
		return
	}
	if err := a.settingsService.UpdateUploadPolicy(settings); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	http.Redirect(w, r, "/admin/settings", http.StatusSeeOther)
}

func (a *App) AdminStorage(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) {
		return
	}
	settings, err := a.settingsService.UploadPolicy()
	if err != nil {
		http.Error(w, "unable to load settings", http.StatusInternalServerError)
		return
	}
	views, err := a.storageBackendViews()
	if err != nil {
		http.Error(w, "unable to load storage", http.StatusInternalServerError)
		return
	}
	a.renderPage(w, r, http.StatusOK, "admin_storage.html", web.PageData{
		Title:       "Admin storage",
		Description: "Manage Warpbox storage backends.",
		CurrentUser: a.currentPublicUser(r),
		Data: adminPageData{
			Settings:  settings,
			Storage:   views,
			Section:   "storage",
			PageTitle: "Storage",
			Notice:    r.URL.Query().Get("notice"),
			Error:     r.URL.Query().Get("error"),
		},
	})
}

func (a *App) AdminNewStorage(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) {
		return
	}
	a.renderPage(w, r, http.StatusOK, "admin_storage_new.html", web.PageData{
		Title:       "Add storage",
		Description: "Choose a Warpbox storage provider.",
		CurrentUser: a.currentPublicUser(r),
		Data: adminPageData{
			Section:      "storage",
			PageTitle:    "Add storage",
			StorageTypes: adminStorageProviderOptions(),
			Error:        r.URL.Query().Get("error"),
		},
	})
}

func (a *App) AdminNewStorageProvider(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) {
		return
	}
	rawProvider := adminStorageProviderFromRequest(r)
	if !validAdminStorageProvider(rawProvider) {
		http.NotFound(w, r)
		return
	}
	provider := normalizeAdminStorageProvider(rawProvider)
	a.renderStorageForm(w, r, http.StatusOK, adminStorageFormView{
		Mode:          "create",
		Provider:      provider,
		ProviderLabel: adminStorageProviderLabel(provider),
		Action:        "/admin/storage/new/" + provider,
		BackHref:      "/admin/storage/new",
		Config: services.StorageBackendConfig{
			Provider: provider,
			Type:     adminStorageTypeForProvider(provider),
			UseSSL:   true,
		},
	}, r.URL.Query().Get("error"))
}

func (a *App) AdminEditStorageForm(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) {
		return
	}
	cfg, err := a.uploadService.Storage().BackendConfig(r.PathValue("backendID"))
	if err != nil || cfg.ID == services.StorageBackendLocal {
		http.NotFound(w, r)
		return
	}
	provider := normalizeAdminStorageProvider(cfg.Provider)
	a.renderStorageForm(w, r, http.StatusOK, adminStorageFormView{
		Mode:          "edit",
		Provider:      provider,
		ProviderLabel: adminStorageProviderLabel(provider),
		Action:        "/admin/storage/" + cfg.ID + "/edit",
		BackHref:      "/admin/storage",
		Config:        cfg,
	}, r.URL.Query().Get("error"))
}

func (a *App) AdminStorageTests(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) {
		return
	}
	cfg, err := a.uploadService.Storage().BackendConfig(r.PathValue("backendID"))
	if err != nil {
		http.NotFound(w, r)
		return
	}
	tests, err := a.uploadService.Storage().ListSpeedTests(cfg.ID, 100)
	if err != nil {
		http.Error(w, "unable to load storage tests", http.StatusInternalServerError)
		return
	}
	var usage int64
	if cfg.Enabled {
		if backend, err := a.uploadService.Storage().Backend(cfg.ID); err == nil {
			usage, _ = backend.Usage(context.Background())
		}
	}
	a.renderPage(w, r, http.StatusOK, "admin_storage_tests.html", web.PageData{
		Title:       cfg.Name + " tests",
		Description: "Storage speed-test history.",
		CurrentUser: a.currentPublicUser(r),
		Data: adminPageData{
			Section:   "storage",
			PageTitle: cfg.Name + " tests",
			StorageTest: adminStorageTestView{
				Config:     cfg,
				UsageLabel: services.FormatMegabytesFromBytes(usage),
				Tests:      tests,
				CanRun:     cfg.Enabled && cfg.LastTestSuccess,
			},
			Notice: r.URL.Query().Get("notice"),
			Error:  r.URL.Query().Get("error"),
		},
	})
}

func (a *App) AdminStorageTestsJSON(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) {
		return
	}
	cfg, err := a.uploadService.Storage().BackendConfig(r.PathValue("backendID"))
	if err != nil {
		http.NotFound(w, r)
		return
	}
	tests, err := a.uploadService.Storage().ListSpeedTests(cfg.ID, 100)
	if err != nil {
		http.Error(w, "unable to load storage tests", http.StatusInternalServerError)
		return
	}
	payload := struct {
		Tests []adminStorageSpeedTestJSON `json:"tests"`
	}{Tests: adminStorageSpeedTestsJSON(tests)}
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(payload)
}

func (a *App) renderStorageForm(w http.ResponseWriter, r *http.Request, status int, form adminStorageFormView, message string) {
	a.renderPage(w, r, status, "admin_storage_form.html", web.PageData{
		Title:       form.ProviderLabel + " storage",
		Description: "Configure Warpbox storage.",
		CurrentUser: a.currentPublicUser(r),
		Data: adminPageData{
			Section:     "storage",
			PageTitle:   form.ProviderLabel + " storage",
			StorageForm: form,
			Error:       message,
		},
	})
}

func (a *App) AdminCreateStorage(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) {
		return
	}
	rawProvider := adminStorageProviderFromRequest(r)
	if !validAdminStorageProvider(rawProvider) {
		http.NotFound(w, r)
		return
	}
	provider := normalizeAdminStorageProvider(rawProvider)
	if err := r.ParseForm(); err != nil {
		http.Redirect(w, r, "/admin/storage/new/"+provider, http.StatusSeeOther)
		return
	}
	_, err := a.uploadService.Storage().CreateBackend(a.storageConfigFromForm(r, provider))
	if err != nil {
		http.Redirect(w, r, "/admin/storage/new/"+provider+"?error="+url.QueryEscape(err.Error()), http.StatusSeeOther)
		return
	}
	http.Redirect(w, r, "/admin/storage?notice="+url.QueryEscape("Storage backend added."), http.StatusSeeOther)
}

func (a *App) AdminEditStorage(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) {
		return
	}
	if err := r.ParseForm(); err != nil {
		http.Redirect(w, r, "/admin/storage", http.StatusSeeOther)
		return
	}
	provider := normalizeAdminStorageProvider(r.FormValue("provider"))
	_, err := a.uploadService.Storage().UpdateBackend(r.PathValue("backendID"), a.storageConfigFromForm(r, provider))
	if err != nil {
		http.Redirect(w, r, "/admin/storage/"+r.PathValue("backendID")+"/edit?error="+url.QueryEscape(err.Error()), http.StatusSeeOther)
		return
	}
	http.Redirect(w, r, "/admin/storage?notice="+url.QueryEscape("Storage backend updated."), http.StatusSeeOther)
}

func (a *App) AdminTestStorage(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) {
		return
	}
	next := "/admin/storage"
	if r.FormValue("next") == "tests" {
		next = "/admin/storage/" + r.PathValue("backendID") + "/tests"
	}
	if _, err := a.uploadService.Storage().TestBackend(r.PathValue("backendID")); err != nil {
		http.Redirect(w, r, next+"?error="+url.QueryEscape(err.Error()), http.StatusSeeOther)
		return
	}
	http.Redirect(w, r, next+"?notice="+url.QueryEscape("Storage connection test passed."), http.StatusSeeOther)
}

func (a *App) AdminStartStorageSpeedTest(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) {
		return
	}
	if err := r.ParseForm(); err != nil {
		http.Redirect(w, r, "/admin/storage?error="+url.QueryEscape("Unable to read speed test form."), http.StatusSeeOther)
		return
	}
	options := services.StorageSpeedTestOptions{
		Mode:             r.FormValue("mode"),
		CustomFileCount:  parsePositiveInt(r.FormValue("custom_file_count")),
		CustomFileSizeMB: parsePositiveFloat(r.FormValue("custom_file_size_mb")),
	}
	test, err := a.uploadService.Storage().StartSpeedTestWithOptions(r.PathValue("backendID"), options)
	if err != nil {
		http.Redirect(w, r, "/admin/storage/"+r.PathValue("backendID")+"/tests?error="+url.QueryEscape(err.Error()), http.StatusSeeOther)
		return
	}
	go a.uploadService.Storage().RunSpeedTest(context.Background(), test.ID)
	http.Redirect(w, r, "/admin/storage/"+r.PathValue("backendID")+"/tests?notice="+url.QueryEscape("Storage speed test started in the background."), http.StatusSeeOther)
}

func (a *App) AdminDisableStorage(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) {
		return
	}
	id := r.PathValue("backendID")
	inUse, _ := a.storageBackendInUse(id)
	if err := a.uploadService.Storage().DisableBackend(id, inUse); err != nil {
		http.Redirect(w, r, "/admin/storage?error="+url.QueryEscape(err.Error()), http.StatusSeeOther)
		return
	}
	http.Redirect(w, r, "/admin/storage", http.StatusSeeOther)
}

func (a *App) AdminDeleteStorage(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) {
		return
	}
	id := r.PathValue("backendID")
	inUse, _ := a.storageBackendInUse(id)
	if err := a.uploadService.Storage().DeleteBackend(id, inUse); err != nil {
		http.Redirect(w, r, "/admin/storage?error="+url.QueryEscape(err.Error()), http.StatusSeeOther)
		return
	}
	http.Redirect(w, r, "/admin/storage", http.StatusSeeOther)
}

func (a *App) AdminRunStorageCleanup(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) {
		return
	}
	cleaned, err := jobs.RunCleanupNow(a.uploadService, a.logger)
	if err != nil {
		http.Redirect(w, r, "/admin/storage?error="+url.QueryEscape(err.Error()), http.StatusSeeOther)
		return
	}
	http.Redirect(w, r, "/admin/storage?notice="+url.QueryEscape(fmt.Sprintf("Cleanup finished. Removed %d unavailable boxes.", cleaned)), http.StatusSeeOther)
}

func (a *App) AdminRunStorageThumbnails(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) {
		return
	}
	result, err := jobs.RunThumbnailsNow(a.uploadService, a.logger)
	if err != nil {
		http.Redirect(w, r, "/admin/storage?error="+url.QueryEscape(err.Error()), http.StatusSeeOther)
		return
	}
	message := fmt.Sprintf("Thumbnail pass finished. Scanned %d files, generated %d, failed %d.", result.Scanned, result.Generated, result.Failed)
	http.Redirect(w, r, "/admin/storage?notice="+url.QueryEscape(message), http.StatusSeeOther)
}

func (a *App) AdminVerifyStorageBackends(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) {
		return
	}
	configs, err := a.uploadService.Storage().ListBackendConfigs()
	if err != nil {
		http.Redirect(w, r, "/admin/storage?error="+url.QueryEscape(err.Error()), http.StatusSeeOther)
		return
	}
	passed := 0
	failed := 0
	for _, cfg := range configs {
		if !cfg.Enabled {
			continue
		}
		if _, err := a.uploadService.Storage().TestBackend(cfg.ID); err != nil {
			failed++
			continue
		}
		passed++
	}
	message := fmt.Sprintf("Storage verification finished. %d passed, %d failed.", passed, failed)
	http.Redirect(w, r, "/admin/storage?notice="+url.QueryEscape(message), http.StatusSeeOther)
}

func (a *App) AdminUpdateUserQuota(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) {
		return
	}
	if err := r.ParseForm(); err != nil {
		http.Redirect(w, r, "/admin/users", http.StatusSeeOther)
		return
	}
	var quota *float64
	if r.FormValue("storage_quota_mb") != "" {
		parsed, err := services.ParseMegabytesValue(r.FormValue("storage_quota_mb"))
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		quota = &parsed
	}
	if err := a.authService.SetUserStorageQuota(r.PathValue("userID"), quota); err != nil {
		http.Error(w, "unable to update quota", http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, "/admin/users", http.StatusSeeOther)
}

func (a *App) AdminUpdateUserPolicy(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) {
		return
	}
	if err := r.ParseForm(); err != nil {
		http.Redirect(w, r, "/admin/users", http.StatusSeeOther)
		return
	}
	policy := services.UserPolicy{
		MaxUploadMB:         optionalMB(r.FormValue("max_upload_mb")),
		DailyUploadMB:       optionalMB(r.FormValue("daily_upload_mb")),
		StorageQuotaMB:      optionalMBAllowZero(r.FormValue("storage_quota_mb")),
		MaxDays:             optionalInt(r.FormValue("max_days")),
		DailyBoxes:          optionalInt(r.FormValue("daily_boxes")),
		ActiveBoxes:         optionalInt(r.FormValue("active_boxes")),
		ShortWindowRequests: optionalInt(r.FormValue("short_window_requests")),
	}
	if backendID := r.FormValue("storage_backend_id"); backendID != "" {
		if _, err := a.uploadService.Storage().BackendConfig(backendID); err != nil {
			http.Error(w, "storage backend not found", http.StatusBadRequest)
			return
		}
		policy.StorageBackendID = &backendID
	}
	if err := a.authService.SetUserPolicy(r.PathValue("userID"), policy); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	http.Redirect(w, r, "/admin/users", http.StatusSeeOther)
}

func (a *App) AdminUpdateUser(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) {
		return
	}
	if err := r.ParseForm(); err != nil {
		http.Redirect(w, r, "/admin/users/"+r.PathValue("userID")+"/edit", http.StatusSeeOther)
		return
	}
	policy := services.UserPolicy{
		MaxUploadMB:         optionalMB(r.FormValue("max_upload_mb")),
		DailyUploadMB:       optionalMB(r.FormValue("daily_upload_mb")),
		StorageQuotaMB:      optionalMBAllowZero(r.FormValue("storage_quota_mb")),
		MaxDays:             optionalInt(r.FormValue("max_days")),
		DailyBoxes:          optionalInt(r.FormValue("daily_boxes")),
		ActiveBoxes:         optionalInt(r.FormValue("active_boxes")),
		ShortWindowRequests: optionalInt(r.FormValue("short_window_requests")),
	}
	if backendID := r.FormValue("storage_backend_id"); backendID != "" {
		if _, err := a.uploadService.Storage().BackendConfig(backendID); err != nil {
			http.Redirect(w, r, "/admin/users/"+r.PathValue("userID")+"/edit?error="+url.QueryEscape("storage backend not found"), http.StatusSeeOther)
			return
		}
		policy.StorageBackendID = &backendID
	}
	if _, err := a.authService.UpdateUserAdminFields(
		r.PathValue("userID"),
		r.FormValue("username"),
		r.FormValue("email"),
		r.FormValue("role"),
		r.FormValue("status"),
		policy,
	); err != nil {
		http.Redirect(w, r, "/admin/users/"+r.PathValue("userID")+"/edit?error="+url.QueryEscape(err.Error()), http.StatusSeeOther)
		return
	}
	http.Redirect(w, r, "/admin/users/"+r.PathValue("userID")+"/edit", http.StatusSeeOther)
}

func (a *App) AdminUpdateUserStorage(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) {
		return
	}
	if err := r.ParseForm(); err != nil {
		http.Redirect(w, r, "/admin/users", http.StatusSeeOther)
		return
	}
	if backendID := r.FormValue("storage_backend_id"); backendID != "" {
		if _, err := a.uploadService.Storage().BackendConfig(backendID); err != nil {
			http.Error(w, "storage backend not found", http.StatusBadRequest)
			return
		}
	}
	if err := a.authService.SetUserStorageBackend(r.PathValue("userID"), r.FormValue("storage_backend_id")); err != nil {
		http.Error(w, "unable to update user storage", http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, "/admin/users", http.StatusSeeOther)
}

func (a *App) AdminCreateInvite(w http.ResponseWriter, r *http.Request) {
	admin, ok := a.requireAdminUser(w, r)
	if !ok || !a.validateCSRF(w, r) {
		return
	}
	if err := r.ParseForm(); err != nil {
		http.Redirect(w, r, "/admin/users", http.StatusSeeOther)
		return
	}
	result, err := a.authService.CreateInvite(r.FormValue("email"), r.FormValue("role"), admin.ID, 7*24*time.Hour)
	if err != nil {
		http.Redirect(w, r, "/admin/users", http.StatusSeeOther)
		return
	}
	a.logger.Info("invite created", "source", "admin", "severity", "user_activity", "code", 2404, "admin_id", admin.ID)
	http.Redirect(w, r, "/admin/users?invite="+url.QueryEscape(result.URL), http.StatusSeeOther)
}

func (a *App) AdminDisableUser(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) {
		return
	}
	disabled := r.URL.Query().Get("disabled") != "false"
	if err := a.authService.DisableUser(r.PathValue("userID"), disabled); err != nil {
		http.Error(w, "unable to update user", http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, "/admin/users", http.StatusSeeOther)
}

func (a *App) AdminResetUser(w http.ResponseWriter, r *http.Request) {
	admin, ok := a.requireAdminUser(w, r)
	if !ok || !a.validateCSRF(w, r) {
		return
	}
	result, err := a.authService.CreatePasswordResetInvite(r.PathValue("userID"), admin.ID)
	if err != nil {
		http.Error(w, "unable to create reset link", http.StatusInternalServerError)
		return
	}
	if r.URL.Query().Get("next") == "edit" {
		http.Redirect(w, r, "/admin/users/"+r.PathValue("userID")+"/edit?invite="+url.QueryEscape(result.URL), http.StatusSeeOther)
		return
	}
	http.Redirect(w, r, "/admin/users?invite="+url.QueryEscape(result.URL), http.StatusSeeOther)
}

func (a *App) AdminDeleteBox(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) || !a.validateCSRF(w, r) {
		return
	}

	boxID := r.PathValue("boxID")
	if err := a.uploadService.DeleteBox(boxID); err != nil {
		a.logger.Warn("admin delete failed", "source", "admin", "severity", "warn", "code", 4302, "box_id", boxID, "error", err.Error())
		http.Error(w, "unable to delete box", http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, "/admin/files", http.StatusSeeOther)
}

func (a *App) AdminViewBox(w http.ResponseWriter, r *http.Request) {
	if !a.requireAdmin(w, r) {
		return
	}

	box, err := a.uploadService.GetBox(r.PathValue("boxID"))
	if err != nil {
		http.NotFound(w, r)
		return
	}

	if a.uploadService.IsProtected(box) {
		http.SetCookie(w, &http.Cookie{
			Name:     unlockCookieName(box.ID),
			Value:    a.uploadService.UnlockToken(box),
			Path:     "/d/" + box.ID,
			HttpOnly: true,
			SameSite: http.SameSiteLaxMode,
			Secure:   r.TLS != nil,
			Expires:  box.ExpiresAt,
		})
		a.logger.Info("admin bypassed box password", "source", "admin", "severity", "user_activity", "code", 2302, "box_id", box.ID)
	}

	http.Redirect(w, r, "/d/"+box.ID, http.StatusSeeOther)
}

func (a *App) renderAdminLogin(w http.ResponseWriter, r *http.Request, status int, message string) {
	a.renderPage(w, r, status, "admin_login.html", web.PageData{
		Title:       "Admin login",
		Description: "Sign in to the Warpbox admin console.",
		Data: adminPageData{
			Error: message,
		},
	})
}

func (a *App) adminBoxes(limit int) ([]adminBoxView, error) {
	boxes, err := a.uploadService.AdminBoxes(limit)
	if err != nil {
		return nil, err
	}

	rows := make([]adminBoxView, 0, len(boxes))
	for _, box := range boxes {
		owner := "Anonymous"
		if box.OwnerID != "" {
			if user, err := a.authService.UserByID(box.OwnerID); err == nil {
				owner = user.Email
			} else {
				owner = "User"
			}
		}
		rows = append(rows, adminBoxView{
			ID:             box.ID,
			Owner:          owner,
			CreatedAt:      box.CreatedAt.Format("Jan 2 15:04"),
			ExpiresAt:      box.ExpiresAt.Format("Jan 2 15:04"),
			FileCount:      box.FileCount,
			TotalSizeLabel: box.TotalSizeLabel,
			DownloadCount:  box.DownloadCount,
			MaxDownloads:   box.MaxDownloads,
			Protected:      box.Protected,
			Expired:        box.Expired,
		})
	}
	return rows, nil
}

func (a *App) requireAdmin(w http.ResponseWriter, r *http.Request) bool {
	if a.isAdmin(r) {
		return true
	}
	http.Redirect(w, r, "/admin/login", http.StatusSeeOther)
	return false
}

func (a *App) isAdmin(r *http.Request) bool {
	if user, ok := a.currentUser(r); ok && user.Role == services.UserRoleAdmin {
		return true
	}
	if a.cfg.AdminToken == "" {
		return false
	}
	cookie, err := r.Cookie(adminCookieName)
	if err != nil {
		return false
	}
	return cookie.Value == adminCookieValue(a.cfg.AdminToken)
}

func (a *App) requireAdminUser(w http.ResponseWriter, r *http.Request) (services.User, bool) {
	user, ok := a.currentUser(r)
	if ok && user.Role == services.UserRoleAdmin {
		return user, true
	}
	if a.cfg.AdminToken != "" && a.isAdmin(r) {
		return services.User{ID: "env-admin", Role: services.UserRoleAdmin, Status: services.UserStatusActive}, true
	}
	http.Redirect(w, r, "/login?next="+r.URL.Path, http.StatusSeeOther)
	return services.User{}, false
}

func (a *App) currentPublicUser(r *http.Request) any {
	if user, ok := a.currentUser(r); ok {
		return a.authService.PublicUser(user)
	}
	return nil
}

func adminCookieValue(token string) string {
	sum := sha256.Sum256([]byte("warpbox-admin:" + token))
	return hex.EncodeToString(sum[:])
}

func parsePositiveInt(value string) int {
	parsed, err := strconv.Atoi(value)
	if err != nil {
		return 0
	}
	return parsed
}

func parsePositiveFloat(value string) float64 {
	parsed, err := strconv.ParseFloat(value, 64)
	if err != nil {
		return 0
	}
	return parsed
}

func optionalMB(value string) *float64 {
	if value == "" {
		return nil
	}
	parsed, err := services.ParseMegabytesLimitValue(value)
	if err != nil {
		return nil
	}
	return &parsed
}

func optionalMBAllowZero(value string) *float64 {
	if value == "" {
		return nil
	}
	parsed, err := strconv.ParseFloat(value, 64)
	if err != nil || parsed < 0 {
		return nil
	}
	return &parsed
}

func optionalInt(value string) *int {
	if value == "" {
		return nil
	}
	parsed, err := strconv.Atoi(value)
	if err != nil || parsed <= 0 {
		return nil
	}
	return &parsed
}

func formatMB(value float64) string {
	return strconv.FormatFloat(value, 'f', -1, 64) + " MB"
}

func adminStorageSpeedTestsJSON(tests []services.StorageSpeedTest) []adminStorageSpeedTestJSON {
	rows := make([]adminStorageSpeedTestJSON, 0, len(tests))
	for _, test := range tests {
		rows = append(rows, adminStorageSpeedTestJSON{
			ID:            test.ID,
			Mode:          test.Mode,
			ModeLabel:     test.ModeLabel(),
			Status:        test.Status,
			Stage:         test.Stage,
			Progress:      test.ProgressPercent,
			CustomLabel:   storageSpeedCustomLabel(test),
			StartedLabel:  test.StartedLabel(),
			FinishedLabel: test.FinishedLabel(),
			Files:         test.FilesWritten,
			SizeLabel:     test.TotalSizeLabel(),
			WriteSpeed:    test.WriteSpeedLabel(),
			ReadSpeed:     test.ReadSpeedLabel(),
			Error:         test.Error,
		})
	}
	return rows
}

func storageSpeedCustomLabel(test services.StorageSpeedTest) string {
	if test.Mode != services.StorageSpeedModeCustom {
		return ""
	}
	return fmt.Sprintf("%d files × %s each", test.CustomFileCount, services.FormatMegabytesLabel(test.CustomFileSizeMB))
}

func (a *App) storageConfigFromForm(r *http.Request, provider string) services.StorageBackendConfig {
	cfg := services.StorageBackendConfig{
		Provider: provider,
		Name:     r.FormValue("name"),
	}
	switch provider {
	case services.StorageProviderSFTP:
		cfg.Host = r.FormValue("host")
		cfg.Port = parsePositiveInt(r.FormValue("port"))
		cfg.Username = r.FormValue("username")
		cfg.Password = r.FormValue("password")
		cfg.PrivateKey = r.FormValue("private_key")
		cfg.HostKey = r.FormValue("host_key")
		cfg.RemotePath = r.FormValue("remote_path")
	case services.StorageProviderSMB:
		cfg.Host = r.FormValue("host")
		cfg.Port = parsePositiveInt(r.FormValue("port"))
		cfg.Share = r.FormValue("share")
		cfg.Domain = r.FormValue("domain")
		cfg.Username = r.FormValue("username")
		cfg.Password = r.FormValue("password")
		cfg.RemotePath = r.FormValue("remote_path")
	case services.StorageProviderWebDAV:
		cfg.Endpoint = r.FormValue("endpoint")
		cfg.Username = r.FormValue("username")
		cfg.Password = r.FormValue("password")
		cfg.RemotePath = r.FormValue("remote_path")
	case services.StorageProviderContabo:
		cfg.Endpoint = r.FormValue("endpoint")
		cfg.Region = r.FormValue("region")
		cfg.Bucket = r.FormValue("bucket")
		cfg.AccessKey = r.FormValue("access_key")
		cfg.SecretKey = r.FormValue("secret_key")
		cfg.UseSSL = true
		cfg.PathStyle = true
	default:
		cfg.Endpoint = r.FormValue("endpoint")
		cfg.Region = r.FormValue("region")
		cfg.Bucket = r.FormValue("bucket")
		cfg.AccessKey = r.FormValue("access_key")
		cfg.SecretKey = r.FormValue("secret_key")
		cfg.UseSSL = r.FormValue("use_ssl") == "on"
		cfg.PathStyle = r.FormValue("path_style") == "on"
	}
	return cfg
}

func adminStorageProviderOptions() []adminStorageProviderView {
	return []adminStorageProviderView{
		{Provider: services.StorageProviderS3, Label: "S3 Bucket", Description: "Generic S3-compatible object storage.", Icon: "cloud"},
		{Provider: services.StorageProviderContabo, Label: "Contabo Object Storage", Description: "Contabo COS with TLS and path-style lookup locked on.", Icon: "cloud"},
		{Provider: services.StorageProviderSFTP, Label: "SFTP", Description: "SSH file transfer to a server or NAS.", Icon: "database"},
		{Provider: services.StorageProviderSMB, Label: "Samba / SMB", Description: "Windows share or network attached storage.", Icon: "folder"},
		{Provider: services.StorageProviderWebDAV, Label: "WebDAV", Description: "Nextcloud, ownCloud, or any WebDAV endpoint.", Icon: "sync"},
	}
}

func normalizeAdminStorageProvider(provider string) string {
	switch provider {
	case services.StorageProviderContabo:
		return services.StorageProviderContabo
	case services.StorageProviderSFTP:
		return services.StorageProviderSFTP
	case services.StorageProviderSMB:
		return services.StorageProviderSMB
	case services.StorageProviderWebDAV:
		return services.StorageProviderWebDAV
	default:
		return services.StorageProviderS3
	}
}

func adminStorageProviderFromRequest(r *http.Request) string {
	if provider := r.PathValue("provider"); provider != "" {
		return provider
	}
	return path.Base(r.URL.Path)
}

func validAdminStorageProvider(provider string) bool {
	switch provider {
	case services.StorageProviderS3, services.StorageProviderContabo, services.StorageProviderSFTP, services.StorageProviderSMB, services.StorageProviderWebDAV:
		return true
	default:
		return false
	}
}

func adminStorageProviderLabel(provider string) string {
	switch normalizeAdminStorageProvider(provider) {
	case services.StorageProviderContabo:
		return "Contabo Object Storage"
	case services.StorageProviderSFTP:
		return "SFTP"
	case services.StorageProviderSMB:
		return "Samba / SMB"
	case services.StorageProviderWebDAV:
		return "WebDAV"
	default:
		return "S3 Bucket"
	}
}

func adminStorageTypeForProvider(provider string) string {
	switch normalizeAdminStorageProvider(provider) {
	case services.StorageProviderSFTP:
		return services.StorageBackendSFTP
	case services.StorageProviderSMB:
		return services.StorageBackendSMB
	case services.StorageProviderWebDAV:
		return services.StorageBackendWebDAV
	default:
		return services.StorageBackendS3
	}
}

func (a *App) storageBackendViews() ([]services.StorageBackendView, error) {
	configs, err := a.uploadService.Storage().ListBackendConfigs()
	if err != nil {
		return nil, err
	}
	views := make([]services.StorageBackendView, 0, len(configs))
	for _, cfg := range configs {
		var usage int64
		if backend, err := a.uploadService.Storage().BackendConfig(cfg.ID); err == nil && backend.Enabled {
			if concrete, err := a.uploadService.Storage().Backend(cfg.ID); err == nil {
				usage, _ = concrete.Usage(context.Background())
			}
		}
		inUse, _ := a.storageBackendInUse(cfg.ID)
		speedTests, _ := a.uploadService.Storage().ListSpeedTests(cfg.ID, 25)
		views = append(views, services.StorageBackendView{
			Config:       cfg,
			UsageBytes:   usage,
			UsageLabel:   services.FormatMegabytesFromBytes(usage),
			InUse:        inUse,
			SpeedTests:   speedTests,
			CanSpeedTest: cfg.LastTestSuccess,
		})
	}
	return views, nil
}

func (a *App) adminUserEdit(user services.User, settings services.UploadPolicySettings) (adminUserEditView, error) {
	storageUsed, err := a.uploadService.UserActiveStorageUsed(user.ID)
	if err != nil {
		return adminUserEditView{}, err
	}
	usage, err := a.settingsService.UsageForUser(user.ID, time.Now().UTC())
	if err != nil {
		return adminUserEditView{}, err
	}
	effective := a.settingsService.EffectivePolicyForUser(settings, user)
	view := adminUserEditView{
		ID:                   user.ID,
		Username:             user.Username,
		Email:                user.Email,
		Role:                 user.Role,
		Status:               user.Status,
		StorageUsed:          services.FormatMegabytesFromBytes(storageUsed),
		DailyUsed:            services.FormatMegabytesFromBytes(usage.UploadedBytes),
		EffectiveDaily:       services.FormatMegabytesLabel(effective.DailyUploadMB),
		EffectiveMaxDays:     effective.MaxDays,
		EffectiveDailyBoxes:  effective.DailyBoxes,
		EffectiveActiveBoxes: effective.ActiveBoxes,
		EffectiveBackend:     effective.StorageBackendID,
		MaxUploadMB:          floatPtrString(user.Policy.MaxUploadMB),
		DailyUploadMB:        floatPtrString(user.Policy.DailyUploadMB),
		StorageQuotaMB:       floatPtrString(user.Policy.StorageQuotaMB),
		MaxDays:              intPtrString(user.Policy.MaxDays),
		DailyBoxes:           intPtrString(user.Policy.DailyBoxes),
		ActiveBoxes:          intPtrString(user.Policy.ActiveBoxes),
		ShortWindowRequests:  intPtrString(user.Policy.ShortWindowRequests),
		StorageBackendID:     stringPtrString(user.Policy.StorageBackendID),
	}
	if effective.StorageQuotaSet {
		view.EffectiveStorage = services.FormatMegabytesLabel(effective.StorageQuotaMB)
	} else {
		view.EffectiveStorage = "unlimited"
	}
	return view, nil
}

func (a *App) storageBackendInUse(id string) (bool, error) {
	settings, err := a.settingsService.UploadPolicy()
	if err != nil {
		return false, err
	}
	if settings.AnonymousStorageBackend == id || settings.UserStorageBackend == id {
		return true, nil
	}
	boxes, err := a.uploadService.ListBoxes(0)
	if err != nil {
		return false, err
	}
	for _, box := range boxes {
		if a.uploadService.BoxStorageBackendID(box) == id {
			return true, nil
		}
	}
	users, err := a.authService.ListUsers()
	if err != nil {
		return false, err
	}
	for _, user := range users {
		if user.Policy.StorageBackendID != nil && *user.Policy.StorageBackendID == id {
			return true, nil
		}
	}
	return false, nil
}

func floatPtrString(value *float64) string {
	if value == nil {
		return ""
	}
	return strconv.FormatFloat(*value, 'f', -1, 64)
}

func intPtrString(value *int) string {
	if value == nil {
		return ""
	}
	return strconv.Itoa(*value)
}

func stringPtrString(value *string) string {
	if value == nil {
		return ""
	}
	return *value
}
