package commerce

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/go-chi/chi/v5"

	qmiddleware "qycw/go-server/internal/middleware"
	"qycw/go-server/internal/plugins"
	"qycw/go-server/internal/response"
)

type AdminHandler struct {
	service      *Service
	auth         *qmiddleware.Auth
	logger       *slog.Logger
	impersonator AdminImpersonator
}

type AdminImpersonator interface {
	ImpersonateUser(ctx context.Context, operatorID int64, userID int64, reason string, ipAddress string, userAgent string) (map[string]any, error)
}

func NewAdminHandler(service *Service, auth *qmiddleware.Auth, logger *slog.Logger, impersonator ...AdminImpersonator) *AdminHandler {
	var imp AdminImpersonator
	if len(impersonator) > 0 {
		imp = impersonator[0]
	}
	return &AdminHandler{service: service, auth: auth, logger: logger, impersonator: imp}
}

func (h *AdminHandler) Routes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Get("/dashboard", h.dashboard)
	r.Get("/users", h.listUsers)
	r.Get("/users/{user_id}", h.getUser)
	r.Patch("/users/{user_id}", h.updateUser)
	r.Get("/orders", h.listOrders)
	r.Get("/orders/{order_id}", h.getOrder)
	r.Patch("/orders/{order_id}", h.updateOrder)
	r.Get("/product-categories", h.listCategories)
	r.Post("/product-categories", h.saveCategory)
	r.Put("/product-categories/{category_id}", h.saveCategory)
	r.Get("/product-groups", h.listGroups)
	r.Post("/product-groups", h.saveGroup)
	r.Put("/product-groups/{group_id}", h.saveGroup)
	r.Get("/products", h.listProducts)
	r.Post("/products", h.saveProduct)
	r.Get("/products/{product_id}", h.getProduct)
	r.Put("/products/{product_id}", h.saveProduct)
	r.Get("/settings", h.listSettings)
	r.Get("/settings/{setting_key}", h.getSetting)
	r.Put("/settings/{setting_key}", h.setSetting)
	r.Patch("/settings/{setting_key}", h.setSetting)
	r.Get("/finance/recharge-orders", h.listRechargeOrders)
	r.Get("/finance/balance-logs", h.listBalanceLogs)
	return r
}

func (h *AdminHandler) DashboardRoutes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Get("/", h.dashboard)
	return r
}

func (h *AdminHandler) UserRoutes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Get("/", h.listUsers)
	r.Get("/{user_id}", h.getUser)
	r.Patch("/{user_id}", h.updateUser)
	r.Post("/{user_id}/reset-password", h.resetUserPassword)
	r.Post("/{user_id}/adjust-balance", h.adjustBalance)
	r.Post("/{user_id}/ban", h.userStatus("banned"))
	r.Post("/{user_id}/unban", h.userStatus("active"))
	r.With(h.auth.RequireRole("super_admin")).Post("/{user_id}/impersonate", h.impersonateUser)
	r.Post("/{user_id}/refund-quota/add", h.userQuota("refund_add"))
	r.Post("/{user_id}/refund-quota/reduce", h.userQuota("refund_reduce"))
	r.Post("/{user_id}/refund-quota/reset", h.userQuota("refund_reset"))
	r.Post("/{user_id}/invite-code-quota/add", h.userQuota("invite_add"))
	r.Post("/{user_id}/invite-code-quota/reset", h.userQuota("invite_reset"))
	r.Post("/{user_id}/verification-quota/add", h.verificationQuota("verification_add"))
	r.Post("/{user_id}/verification-quota/reduce", h.verificationQuota("verification_reduce"))
	r.Post("/{user_id}/verification-quota/reset-free", h.verificationQuota("verification_reset_free"))
	r.Get("/{user_id}/instances", h.listUserInstances)
	r.Get("/{user_id}/instances/{instance_id}", h.getUserInstance)
	r.Get("/{user_id}/instances/{instance_id}/nat-acl", h.listUserInstanceNatACL)
	r.Post("/{user_id}/instances/{instance_id}/nat-acl", h.createUserInstanceNatACL)
	r.Delete("/{user_id}/instances/{instance_id}/nat-acl/{nat_acl_id}", h.deleteUserInstanceNatACL)
	r.Post("/{user_id}/instances/{instance_id}/power/{action}", h.userInstancePower)
	r.Post("/{user_id}/instances/{instance_id}/suspend", h.suspendUserInstance)
	r.Post("/{user_id}/instances/{instance_id}/unsuspend", h.unsuspendUserInstance)
	r.Delete("/{user_id}/instances/{instance_id}", h.deleteUserInstance)
	r.Patch("/{user_id}/instances/{instance_id}/auto-renew", h.updateUserInstanceAutoRenew)
	r.Patch("/{user_id}/instances/{instance_id}/billing", h.updateUserInstanceBilling)
	r.Post("/{user_id}/instances/{instance_id}/provision", h.provisionUserInstance)
	return r
}

func (h *AdminHandler) OrderRoutes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Get("/", h.listOrders)
	r.Get("/{order_id}", h.getOrder)
	r.Patch("/{order_id}", h.updateOrder)
	return r
}

func (h *AdminHandler) CategoryRoutes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Get("/", h.listCategories)
	r.Post("/", h.saveCategory)
	r.Put("/{category_id}", h.saveCategory)
	r.Delete("/{category_id}", h.deleteCategory)
	return r
}

func (h *AdminHandler) GroupRoutes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Get("/", h.listGroups)
	r.Post("/", h.saveGroup)
	r.Put("/{group_id}", h.saveGroup)
	r.Delete("/{group_id}", h.deleteGroup)
	r.Get("/{group_id}/os-options", h.groupOSOptions)
	r.Put("/{group_id}/os-options/page", h.saveGroupOSOptions)
	r.Put("/{group_id}/verification-rule", h.saveGroupVerificationRule)
	return r
}

func (h *AdminHandler) ProductRoutes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Get("/provision-plugins", h.provisionPlugins)
	r.Get("/provision-interfaces", h.provisionInterfaces)
	r.Post("/provision-interfaces", h.rawSave("provision_interfaces", "interface_id"))
	r.Put("/provision-interfaces/{interface_id}", h.rawSave("provision_interfaces", "interface_id"))
	r.Delete("/provision-interfaces/{interface_id}", h.rawDelete("provision_interfaces", "interface_id"))
	r.Post("/provision-interfaces/{interface_id}/test", h.testProvisionInterface)
	r.Post("/provision-plugins/{plugin_id}/test", h.testProvisionPlugin)
	r.Get("/zjmf-finance/products", h.zjmfFinanceProducts)
	r.Get("/zjmf-finance/product-config", h.zjmfFinanceProductConfig)
	r.Get("/groups/{group_id}/readiness", h.groupReadiness)
	r.Get("/cloud-resources/{kind}", h.cloudResources)
	r.Get("/", h.listProducts)
	r.Post("/", h.saveProduct)
	r.Get("/{product_id}", h.getProduct)
	r.Put("/{product_id}", h.saveProduct)
	r.Delete("/{product_id}", h.deleteProduct)
	return r
}

func (h *AdminHandler) ProductCatalogRoutes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Get("/categories", h.listCategories)
	r.Post("/categories", h.saveCategory)
	r.Put("/categories/{category_id}", h.saveCategory)
	r.Delete("/categories/{category_id}", h.deleteCategory)
	r.Get("/groups", h.listGroups)
	r.Post("/groups", h.saveGroup)
	r.Get("/groups/{group_id}/os-options", h.groupOSOptions)
	r.Put("/groups/{group_id}/os-options/page", h.saveGroupOSOptions)
	r.Put("/groups/{group_id}/verification-rule", h.saveGroupVerificationRule)
	r.Put("/groups/{group_id}", h.saveGroup)
	r.Delete("/groups/{group_id}", h.deleteGroup)
	return r
}

func (h *AdminHandler) SettingRoutes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Get("/business-rules", h.businessRules)
	r.Put("/business-rules", h.updateBusinessRules)
	r.Get("/notification-plugins", h.notificationPlugins)
	r.Get("/plugin-diagnostics", h.pluginDiagnostics)
	r.Post("/branding/logo", h.uploadBrandLogo)
	r.Post("/branding/assets", h.uploadBrandAsset)
	r.Get("/frontend-config", h.frontendConfig)
	r.Put("/frontend-config", h.updateFrontendConfig)
	r.Post("/notification-plugins/preview", h.previewNotificationPlugin)
	r.Post("/notification-plugins/install", h.installNotificationPlugin)
	r.Post("/notification-plugins/reload", h.reloadNotificationPlugins)
	r.Get("/notification-plugins/{plugin_type}/{plugin_id}/config", h.notificationPluginConfig)
	r.Put("/notification-plugins/{plugin_type}/{plugin_id}/config", h.setNotificationPluginConfig)
	r.Post("/notification-plugins/{plugin_type}/{plugin_id}/enable", h.enableNotificationPlugin)
	r.Post("/notification-plugins/{plugin_type}/{plugin_id}/disable", h.disableNotificationPlugin)
	r.Post("/notification-plugins/{plugin_type}/{plugin_id}/test", h.testNotificationPlugin)
	r.Delete("/notification-plugins/{plugin_type}/{plugin_id}", h.deleteNotificationPlugin)
	r.Get("/", h.listSettings)
	r.Get("/{setting_key}", h.getSetting)
	r.Put("/{setting_key}", h.setSetting)
	r.Patch("/{setting_key}", h.setSetting)
	return r
}

func (h *AdminHandler) FinanceRoutes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Get("/recharge-orders", h.listRechargeOrders)
	r.Get("/recharge-orders/stats", h.rechargeStats)
	r.Get("/balance-logs", h.listBalanceLogs)
	return r
}

func (h *AdminHandler) CloudServerRoutes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Get("/", h.listCloudServers)
	r.Post("/", h.saveCloudServer)
	r.Post("/test", h.testCloudServer)
	r.Get("/{server_id}", h.getCloudServer)
	r.Put("/{server_id}", h.saveCloudServer)
	r.Delete("/{server_id}", h.deleteCloudServer)
	r.Post("/{server_id}/test", h.testCloudServer)
	return r
}

func (h *AdminHandler) InstanceRoutes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Get("/", h.listInstances)
	r.Post("/batch/{action}", h.batchInstances)
	r.Get("/{instance_id}", h.getInstance)
	r.Patch("/{instance_id}", h.updateInstance)
	r.Post("/{instance_id}/power/{action}", h.instancePower)
	r.Post("/{instance_id}/plugin-actions/{action}", h.instancePluginAction)
	r.Post("/{instance_id}/suspend", h.instanceStatus("suspended"))
	r.Post("/{instance_id}/unsuspend", h.instanceStatus("active"))
	r.Delete("/{instance_id}", h.instanceStatus("terminated"))
	r.Patch("/{instance_id}/auto-renew", h.updateInstance)
	r.Patch("/{instance_id}/billing", h.updateInstance)
	r.Post("/{instance_id}/provision", h.instanceStatus("provisioning"))
	return r
}

func (h *AdminHandler) AnnouncementRoutes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Get("/", h.listAnnouncements)
	r.Post("/", h.saveAnnouncement)
	r.Post("/notifications", h.sendNotification)
	r.Put("/{announcement_id}", h.saveAnnouncement)
	r.Delete("/{announcement_id}", h.deleteAnnouncement)
	return r
}

func (h *AdminHandler) PromoCodeRoutes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Get("/", h.listPromoCodes)
	r.Post("/", h.savePromoCode)
	r.Get("/{coupon_id}", h.getPromoCode)
	r.Put("/{coupon_id}", h.savePromoCode)
	r.Delete("/{coupon_id}", h.deletePromoCode)
	return r
}

func (h *AdminHandler) MallRoutes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Get("/items", h.listMallItems)
	r.Post("/items", h.createMallCoupon)
	r.Put("/items/{item_id}", h.updateMallItem)
	r.Delete("/items/{item_id}", h.deleteMallItem)
	r.Get("/claims", h.listMallClaims)
	return r
}

func (h *AdminHandler) MembershipRoutes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Get("/levels", h.listMemberLevels)
	r.Post("/levels", h.saveMemberLevel)
	r.Put("/levels/{level_id}", h.saveMemberLevel)
	r.Delete("/levels/{level_id}", h.rawDelete("member_levels", "level_id"))
	r.Get("/spend-event-types", h.rawList("spend_event_type_configs"))
	r.Patch("/spend-event-types/{code}", h.updateSpendEventType)
	r.Post("/spend/recalculate", h.recalculateMembershipSpend)
	return r
}

func (h *AdminHandler) AgentRoutes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Get("/agent-tiers", h.rawList("agent_tiers"))
	r.Post("/agent-tiers", h.rawSave("agent_tiers", "tier_id"))
	r.Put("/agent-tiers/{tier_id}", h.rawSave("agent_tiers", "tier_id"))
	r.Delete("/agent-tiers/{tier_id}", h.rawDelete("agent_tiers", "tier_id"))
	r.Get("/agents", h.listAgents)
	r.Get("/agents/{agent_id}", h.getAgent)
	r.Post("/agents/{agent_id}/review", h.reviewAgent)
	r.Put("/agents/{agent_id}/tier", h.assignAgentTier)
	r.Post("/agents/{agent_id}/evaluate", h.evaluateAgent)
	r.Post("/agents/{agent_id}/action", h.agentAction)
	r.Get("/agent-settlements", h.listAgentSettlements)
	r.Get("/affiliate/report", h.affiliateReport)
	return r
}

func (h *AdminHandler) TaskRoutes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Get("/tasks", h.taskList)
	r.Get("/tasks/runs", h.listTaskRuns)
	r.Post("/tasks/{task_key}/run", h.runTask)
	r.Get("/agent-tiers", h.rawList("agent_tiers"))
	r.Post("/agent-tiers", h.rawSave("agent_tiers", "tier_id"))
	r.Put("/agent-tiers/{tier_id}", h.rawSave("agent_tiers", "tier_id"))
	r.Delete("/agent-tiers/{tier_id}", h.rawDelete("agent_tiers", "tier_id"))
	r.Get("/agents", h.listAgents)
	r.Get("/agents/{agent_id}", h.getAgent)
	r.Post("/agents/{agent_id}/review", h.reviewAgent)
	r.Put("/agents/{agent_id}/tier", h.assignAgentTier)
	r.Post("/agents/{agent_id}/evaluate", h.evaluateAgent)
	r.Post("/agents/{agent_id}/action", h.agentAction)
	r.Get("/agent-settlements", h.listAgentSettlements)
	r.Get("/affiliate/report", h.affiliateReport)
	return r
}

func (h *AdminHandler) UpstreamActionRoutes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Get("/", h.listUpstreamActions)
	r.Post("/sync", h.syncUpstreamActions)
	r.Post("/{action_id}/sync", h.syncUpstreamAction)
	r.Post("/{action_id}/retry", h.upstreamAction("submitted"))
	r.Post("/{action_id}/mark-handled", h.upstreamAction("manual_handled"))
	r.Post("/{action_id}/ignore", h.upstreamAction("ignored"))
	return r
}

func (h *AdminHandler) ServiceOrderRoutes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Get("/", h.listServiceOrders)
	r.Get("/{order_id}", h.getServiceOrder)
	r.Post("/{order_id}/unsubscribe", h.unsubscribeServiceOrder)
	return r
}

func (h *AdminHandler) dashboard(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminDashboard(r.Context())
	h.writeResult(w, data, err)
}

func (h *AdminHandler) listUsers(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminListUsers(r.Context(), h.query(r))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) listUserInstances(w http.ResponseWriter, r *http.Request) {
	userID, ok := parseID(w, chi.URLParam(r, "user_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminListInstances(r.Context(), h.query(r), &userID)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) getUserInstance(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userInstanceIDs(w, r)
	if !ok {
		return
	}
	data, err := h.service.AdminGetUserInstance(r.Context(), userID, instanceID)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) listUserInstanceNatACL(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userInstanceIDs(w, r)
	if !ok {
		return
	}
	data, err := h.service.AdminListUserInstanceNatACL(r.Context(), userID, instanceID, r.URL.Query())
	h.writeResult(w, data, err)
}

func (h *AdminHandler) createUserInstanceNatACL(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userInstanceIDs(w, r)
	if !ok {
		return
	}
	payload := map[string]any{}
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	data, err := h.service.AdminCreateUserInstanceNatACL(r.Context(), userID, instanceID, payload)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) deleteUserInstanceNatACL(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userInstanceIDs(w, r)
	if !ok {
		return
	}
	natACLID, ok := parseID(w, chi.URLParam(r, "nat_acl_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminDeleteUserInstanceNatACL(r.Context(), userID, instanceID, natACLID)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) userInstancePower(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userInstanceIDs(w, r)
	if !ok {
		return
	}
	data, err := h.service.AdminUserInstancePower(r.Context(), userID, instanceID, chi.URLParam(r, "action"))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) suspendUserInstance(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userInstanceIDs(w, r)
	if !ok {
		return
	}
	payload := map[string]any{}
	if r.Body != nil && r.ContentLength != 0 {
		if !decodeAdminJSON(w, r, &payload) {
			return
		}
	}
	adminID, _ := notificationUserID(w, r)
	data, err := h.service.AdminSuspendUserInstance(r.Context(), adminID, userID, instanceID, payload)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) unsuspendUserInstance(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userInstanceIDs(w, r)
	if !ok {
		return
	}
	payload := map[string]any{}
	if r.Body != nil && r.ContentLength != 0 {
		if !decodeAdminJSON(w, r, &payload) {
			return
		}
	}
	adminID, _ := notificationUserID(w, r)
	data, err := h.service.AdminUnsuspendUserInstance(r.Context(), adminID, userID, instanceID, payload)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) deleteUserInstance(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userInstanceIDs(w, r)
	if !ok {
		return
	}
	adminID, _ := notificationUserID(w, r)
	data, err := h.service.AdminDeleteUserInstance(r.Context(), adminID, userID, instanceID)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) updateUserInstanceAutoRenew(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userInstanceIDs(w, r)
	if !ok {
		return
	}
	payload := map[string]any{}
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	data, err := h.service.AdminUpdateUserInstanceAutoRenew(r.Context(), userID, instanceID, truthyAny(payload["auto_renew"]))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) updateUserInstanceBilling(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userInstanceIDs(w, r)
	if !ok {
		return
	}
	payload := map[string]any{}
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	data, err := h.service.AdminUpdateUserInstanceBilling(r.Context(), userID, instanceID, payload)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) provisionUserInstance(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userInstanceIDs(w, r)
	if !ok {
		return
	}
	adminID, _ := notificationUserID(w, r)
	data, err := h.service.AdminProvisionUserInstance(r.Context(), adminID, userID, instanceID)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) getUser(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "user_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminGetUser(r.Context(), id)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) updateUser(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "user_id"))
	if !ok {
		return
	}
	var req AdminUserUpdateRequest
	if !decodeAdminJSON(w, r, &req) {
		return
	}
	principal, ok := qmiddleware.PrincipalFromContext(r.Context())
	if !ok {
		response.Detail(w, http.StatusUnauthorized, "Could not validate credentials")
		return
	}
	if req.Role != nil && principal.Role != "super_admin" {
		response.Detail(w, http.StatusForbidden, "仅超级管理员可修改用户角色")
		return
	}
	if principal.ID == id && (req.Role != nil || req.Status != nil) {
		response.Detail(w, http.StatusBadRequest, "不能修改自己的角色或状态")
		return
	}
	adminID := principal.ID
	data, err := h.service.AdminUpdateUser(r.Context(), adminID, id, req)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) resetUserPassword(w http.ResponseWriter, r *http.Request) {
	userID, ok := parseID(w, chi.URLParam(r, "user_id"))
	if !ok {
		return
	}
	payload := map[string]any{}
	if r.Body != nil && r.ContentLength != 0 {
		if !decodeAdminJSON(w, r, &payload) {
			return
		}
	}
	password := strings.TrimSpace(fmt.Sprint(payload["new_password"]))
	if password == "" || password == "<nil>" {
		password = strings.TrimSpace(fmt.Sprint(payload["password"]))
	}
	data, err := h.service.AdminResetUserPassword(r.Context(), userID, password)
	if err == nil && h.service.identity != nil {
		err = h.service.identity.RevokeAllRefresh(r.Context(), userID)
	}
	h.writeResult(w, data, err)
}

func (h *AdminHandler) adjustBalance(w http.ResponseWriter, r *http.Request) {
	adminID, _ := notificationUserID(w, r)
	userID, ok := parseID(w, chi.URLParam(r, "user_id"))
	if !ok {
		return
	}
	var payload map[string]any
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	delta := intValueAny(payload["delta"], 0)
	if delta == 0 {
		delta = intValueAny(payload["amount"], 0)
	}
	remark := strings.TrimSpace(fmt.Sprint(payload["remark"]))
	if remark == "" || remark == "<nil>" {
		remark = strings.TrimSpace(fmt.Sprint(payload["reason"]))
	}
	data, err := h.service.AdminAdjustBalance(r.Context(), adminID, userID, delta, remark)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) userStatus(status string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		userID, ok := parseID(w, chi.URLParam(r, "user_id"))
		if !ok {
			return
		}
		principal, ok := qmiddleware.PrincipalFromContext(r.Context())
		if !ok {
			response.Detail(w, http.StatusUnauthorized, "Could not validate credentials")
			return
		}
		if principal.ID == userID {
			response.Detail(w, http.StatusBadRequest, "不能修改自己的状态")
			return
		}
		data, err := h.service.AdminUpdateUser(r.Context(), principal.ID, userID, AdminUserUpdateRequest{Status: &status})
		h.writeResult(w, data, err)
	}
}

func (h *AdminHandler) verificationQuota(action string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		userID, ok := parseID(w, chi.URLParam(r, "user_id"))
		if !ok {
			return
		}
		payload := map[string]any{}
		_ = decodeAdminJSON(w, r, &payload)
		if action == "verification_reset_free" {
			err := h.service.db.WithContext(r.Context()).Table("user_verification_quotas").Where("user_id = ?", userID).Updates(map[string]any{"free_used": 0, "updated_at": time.Now().UTC()}).Error
			if err != nil {
				h.writeResult(w, nil, err)
				return
			}
			data, err := h.service.AdminGetUser(r.Context(), userID)
			h.writeResult(w, data, err)
			return
		}
		data, err := h.service.AdminUserQuotaAction(r.Context(), userID, action, payload)
		h.writeResult(w, data, err)
	}
}

func (h *AdminHandler) userQuota(action string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		userID, ok := parseID(w, chi.URLParam(r, "user_id"))
		if !ok {
			return
		}
		payload := map[string]any{}
		if r.Body != nil && r.ContentLength != 0 {
			if !decodeAdminJSON(w, r, &payload) {
				return
			}
		}
		adminID, _ := notificationUserID(w, r)
		data, err := h.service.AdminUserQuotaAction(r.Context(), userID, action, withOperator(payload, adminID))
		h.writeResult(w, data, err)
	}
}

func (h *AdminHandler) impersonateUser(w http.ResponseWriter, r *http.Request) {
	if h.impersonator == nil {
		response.Detail(w, http.StatusServiceUnavailable, "impersonation is not configured")
		return
	}
	userID, ok := parseID(w, chi.URLParam(r, "user_id"))
	if !ok {
		return
	}
	principal, ok := qmiddleware.PrincipalFromContext(r.Context())
	if !ok || principal.Role != "super_admin" {
		response.Detail(w, http.StatusForbidden, "only super administrators can impersonate users")
		return
	}
	var payload struct {
		Reason string `json:"reason"`
	}
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	if strings.TrimSpace(payload.Reason) == "" {
		response.Detail(w, http.StatusBadRequest, "impersonation reason is required")
		return
	}
	data, err := h.impersonator.ImpersonateUser(r.Context(), principal.ID, userID, payload.Reason, clientIP(r), r.UserAgent())
	h.writeResult(w, data, err)
}

func (h *AdminHandler) userInstanceIDs(w http.ResponseWriter, r *http.Request) (int64, int64, bool) {
	userID, ok := parseID(w, chi.URLParam(r, "user_id"))
	if !ok {
		return 0, 0, false
	}
	instanceID, ok := parseID(w, chi.URLParam(r, "instance_id"))
	if !ok {
		return 0, 0, false
	}
	return userID, instanceID, true
}

func (h *AdminHandler) listOrders(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminListOrders(r.Context(), h.query(r))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) getOrder(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "order_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminGetOrder(r.Context(), id)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) updateOrder(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "order_id"))
	if !ok {
		return
	}
	var req AdminOrderUpdateRequest
	if !decodeAdminJSON(w, r, &req) {
		return
	}
	data, err := h.service.AdminUpdateOrder(r.Context(), id, req)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) listServiceOrders(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminListServiceOrders(r.Context(), AdminServiceOrderQuery{
		Status:         strings.TrimSpace(r.URL.Query().Get("status")),
		Search:         firstString(strings.TrimSpace(r.URL.Query().Get("keyword")), strings.TrimSpace(r.URL.Query().Get("search"))),
		RefundStatus:   strings.TrimSpace(r.URL.Query().Get("refund_status")),
		RefundType:     strings.TrimSpace(r.URL.Query().Get("refund_type")),
		ResourceStatus: strings.TrimSpace(r.URL.Query().Get("resource_status")),
		UserID:         queryInt64Ptr(r.URL.Query().Get("user_id")),
		InstanceID:     queryInt64Ptr(r.URL.Query().Get("instance_id")),
		Limit:          queryInt(r.URL.Query().Get("limit"), 100, 1, 300),
		Offset:         queryInt(r.URL.Query().Get("offset"), 0, 0, 1000000),
	})
	h.writeResult(w, data, err)
}

func (h *AdminHandler) getServiceOrder(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "order_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminGetServiceOrder(r.Context(), id)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) unsubscribeServiceOrder(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "order_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminUnsubscribeServiceOrder(r.Context(), id)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) listCategories(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminListCategories(r.Context(), h.query(r))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) saveCategory(w http.ResponseWriter, r *http.Request) {
	var item ProductCategory
	if !decodeAdminJSON(w, r, &item) {
		return
	}
	if id := chi.URLParam(r, "category_id"); id != "" {
		parsed, ok := parseID(w, id)
		if !ok {
			return
		}
		item.ID = parsed
	}
	data, err := h.service.AdminSaveCategory(r.Context(), item)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) deleteCategory(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "category_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminRawDelete(r.Context(), "product_categories", id)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) listGroups(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminListGroups(r.Context(), h.query(r))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) saveGroup(w http.ResponseWriter, r *http.Request) {
	var item ProductGroup
	if !decodeAdminJSON(w, r, &item) {
		return
	}
	if id := chi.URLParam(r, "group_id"); id != "" {
		parsed, ok := parseID(w, id)
		if !ok {
			return
		}
		item.ID = parsed
	}
	data, err := h.service.AdminSaveGroup(r.Context(), item)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) deleteGroup(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "group_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminRawDelete(r.Context(), "product_groups", id)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) groupOSOptions(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "group_id"))
	if !ok {
		return
	}
	var group ProductGroup
	err := h.service.db.WithContext(r.Context()).Where("id = ?", id).First(&group).Error
	if err != nil {
		h.writeError(w, err)
		return
	}
	h.writeResult(w, map[string]any{"os_options": jsonAnyValue(group.OSOptions)}, nil)
}

func (h *AdminHandler) saveGroupOSOptions(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "group_id"))
	if !ok {
		return
	}
	payload := map[string]any{}
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	options := payload["os_options"]
	if options == nil {
		options = payload["items"]
	}
	data, err := h.service.AdminRawSave(r.Context(), "product_groups", id, map[string]any{"os_options": jsonRawFromAny(options)})
	h.writeResult(w, data, err)
}

func (h *AdminHandler) saveGroupVerificationRule(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "group_id"))
	if !ok {
		return
	}
	var payload struct {
		RequiresRealName bool  `json:"requires_real_name"`
		ApplyToProducts  *bool `json:"apply_to_products"`
	}
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	applyToProducts := true
	if payload.ApplyToProducts != nil {
		applyToProducts = *payload.ApplyToProducts
	}
	data, err := h.service.AdminSaveGroupVerificationRule(r.Context(), id, payload.RequiresRealName, applyToProducts)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) groupReadiness(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "group_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminGroupReadiness(r.Context(), id)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) listProducts(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminListProducts(r.Context(), h.query(r))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) getProduct(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "product_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminGetProduct(r.Context(), id)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) deleteProduct(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "product_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminRawDelete(r.Context(), "products", id)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) provisionPlugins(w http.ResponseWriter, r *http.Request) {
	if h.service.plugins == nil {
		h.writeResult(w, []any{}, nil)
		return
	}
	items, err := h.service.plugins.ListAll(r.Context(), "provision")
	if err != nil {
		h.writeError(w, err)
		return
	}
	out := make([]map[string]any, 0, len(items))
	for _, item := range items {
		out = append(out, publicGoPluginManifest(item))
	}
	h.writeResult(w, out, nil)
}

func (h *AdminHandler) provisionInterfaces(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminListProvisionInterfaces(r.Context(), h.query(r), r.URL.Query().Get("plugin_id"))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) testProvisionInterface(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "interface_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminTestProvisionInterface(r.Context(), id)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) testProvisionPlugin(w http.ResponseWriter, r *http.Request) {
	payload := map[string]any{}
	if r.Body != nil && r.ContentLength != 0 {
		if !decodeAdminJSON(w, r, &payload) {
			return
		}
	}
	data, err := h.service.AdminTestProvisionPlugin(r.Context(), chi.URLParam(r, "plugin_id"), payload)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) cloudResources(w http.ResponseWriter, r *http.Request) {
	kind := strings.TrimSpace(chi.URLParam(r, "kind"))
	if kind == "" {
		kind = "unknown"
	}
	cloudServerID := queryInt64Ptr(r.URL.Query().Get("cloud_server_id"))
	if cloudServerID == nil {
		response.Detail(w, http.StatusBadRequest, "cloud_server_id is required")
		return
	}
	data, err := h.service.AdminCloudResources(r.Context(), *cloudServerID, kind)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) zjmfFinanceProducts(w http.ResponseWriter, r *http.Request) {
	cloudServerID := queryInt64Ptr(r.URL.Query().Get("cloud_server_id"))
	if cloudServerID == nil {
		response.Detail(w, http.StatusBadRequest, "cloud_server_id is required")
		return
	}
	data, err := h.service.AdminZJMFFinanceProducts(r.Context(), *cloudServerID)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) zjmfFinanceProductConfig(w http.ResponseWriter, r *http.Request) {
	cloudServerID := queryInt64Ptr(r.URL.Query().Get("cloud_server_id"))
	pid := strings.TrimSpace(r.URL.Query().Get("pid"))
	if cloudServerID == nil || pid == "" {
		response.Detail(w, http.StatusBadRequest, "cloud_server_id and pid are required")
		return
	}
	data, err := h.service.AdminZJMFFinanceProductConfig(r.Context(), *cloudServerID, pid)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) saveProduct(w http.ResponseWriter, r *http.Request) {
	payload, ok := decodeProductPayload(w, r)
	if !ok {
		return
	}
	if payload.Product == nil {
		response.Detail(w, http.StatusBadRequest, msgInvalidParams)
		return
	}
	if id := chi.URLParam(r, "product_id"); id != "" {
		parsed, ok := parseID(w, id)
		if !ok {
			return
		}
		payload.Product.ID = parsed
	}
	data, err := h.service.AdminSaveProduct(r.Context(), *payload.Product, payload.Pricings, payload.ProductFields)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) listSettings(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminListSettings(r.Context(), strings.TrimSpace(r.URL.Query().Get("prefix")))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) getSetting(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminGetSetting(r.Context(), chi.URLParam(r, "setting_key"))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) setSetting(w http.ResponseWriter, r *http.Request) {
	var req AdminSettingRequest
	if !decodeAdminJSON(w, r, &req) {
		return
	}
	data, err := h.service.AdminSetSetting(r.Context(), chi.URLParam(r, "setting_key"), req)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) listRechargeOrders(w http.ResponseWriter, r *http.Request) {
	query := h.query(r)
	if query.Search == "" {
		query.Search = strings.TrimSpace(r.URL.Query().Get("keyword"))
	}
	data, err := h.service.AdminListRechargeOrders(r.Context(), query, r.URL.Query().Get("channel"))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) rechargeStats(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminFinanceRechargeStats(r.Context())
	h.writeResult(w, data, err)
}

func (h *AdminHandler) listBalanceLogs(w http.ResponseWriter, r *http.Request) {
	var userID *int64
	if raw := strings.TrimSpace(r.URL.Query().Get("user_id")); raw != "" {
		if id, ok := int64FromAny(raw); ok {
			userID = &id
		}
	}
	data, err := h.service.AdminListBalanceLogs(r.Context(), h.query(r), userID, r.URL.Query().Get("change_type"))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) listCloudServers(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminListCloudServers(r.Context(), h.query(r))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) getCloudServer(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "server_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminGetCloudServer(r.Context(), id)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) saveCloudServer(w http.ResponseWriter, r *http.Request) {
	id := int64(0)
	if raw := chi.URLParam(r, "server_id"); raw != "" {
		parsed, ok := parseID(w, raw)
		if !ok {
			return
		}
		id = parsed
	}
	payload := map[string]any{}
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	data, err := h.service.AdminSaveCloudServer(r.Context(), id, payload)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) deleteCloudServer(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "server_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminDeleteCloudServer(r.Context(), id)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) testCloudServer(w http.ResponseWriter, r *http.Request) {
	id := int64(0)
	if raw := chi.URLParam(r, "server_id"); raw != "" {
		parsed, ok := parseID(w, raw)
		if !ok {
			return
		}
		id = parsed
	}
	payload := map[string]any{}
	if r.Body != nil && r.ContentLength != 0 {
		if !decodeAdminJSON(w, r, &payload) {
			return
		}
	}
	data, err := h.service.AdminTestCloudServer(r.Context(), id, payload)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) listInstances(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminListInstances(r.Context(), h.query(r), queryInt64Ptr(r.URL.Query().Get("user_id")))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) getInstance(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "instance_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminGetInstance(r.Context(), id)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) updateInstance(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "instance_id"))
	if !ok {
		return
	}
	payload := map[string]any{}
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	data, err := h.service.AdminUpdateInstance(r.Context(), id, payload)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) instancePower(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "instance_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminInstancePower(r.Context(), id, chi.URLParam(r, "action"))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) instancePluginAction(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "instance_id"))
	if !ok {
		return
	}
	payload := map[string]any{}
	if r.Body != nil && r.ContentLength != 0 {
		if !decodeAdminJSON(w, r, &payload) {
			return
		}
	}
	data, err := h.service.AdminInstancePluginAction(r.Context(), id, chi.URLParam(r, "action"), payload)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) instanceStatus(status string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		id, ok := parseID(w, chi.URLParam(r, "instance_id"))
		if !ok {
			return
		}
		extra := map[string]any{}
		if status == "suspended" {
			extra["suspend_type"] = "admin"
		}
		if status == "active" {
			extra["suspend_type"] = nil
		}
		data, err := h.service.AdminSetInstanceStatus(r.Context(), id, status, extra)
		h.writeResult(w, data, err)
	}
}

func (h *AdminHandler) batchInstances(w http.ResponseWriter, r *http.Request) {
	payload := map[string]any{}
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	ids := []int64{}
	if raw, ok := payload["ids"].([]any); ok {
		for _, item := range raw {
			if id, ok := int64FromAny(item); ok {
				ids = append(ids, id)
			}
		}
	}
	data, err := h.service.AdminBatchInstances(r.Context(), chi.URLParam(r, "action"), ids)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) listAnnouncements(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminListAnnouncements(r.Context(), h.query(r))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) saveAnnouncement(w http.ResponseWriter, r *http.Request) {
	adminID, _ := notificationUserID(w, r)
	id := int64(0)
	if raw := chi.URLParam(r, "announcement_id"); raw != "" {
		parsed, ok := parseID(w, raw)
		if !ok {
			return
		}
		id = parsed
	}
	var item Announcement
	if !decodeAdminJSON(w, r, &item) {
		return
	}
	data, err := h.service.AdminSaveAnnouncement(r.Context(), id, item, adminID)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) deleteAnnouncement(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "announcement_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminDeleteAnnouncement(r.Context(), id)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) sendNotification(w http.ResponseWriter, r *http.Request) {
	adminID, _ := notificationUserID(w, r)
	payload := map[string]any{}
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	data, err := h.service.AdminSendNotification(r.Context(), adminID, payload)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) listPromoCodes(w http.ResponseWriter, r *http.Request) {
	query := h.query(r)
	if keyword := strings.TrimSpace(r.URL.Query().Get("keyword")); keyword != "" {
		query.Search = keyword
	}
	data, err := h.service.AdminListPromoCodes(r.Context(), query, strings.TrimSpace(r.URL.Query().Get("discount_level")), queryBoolPtr(r.URL.Query().Get("enabled")))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) getPromoCode(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "coupon_id"))
	if !ok {
		return
	}
	data, err := rawByID(r.Context(), h.service.db, "promo_codes", id)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) savePromoCode(w http.ResponseWriter, r *http.Request) {
	id := int64(0)
	if raw := chi.URLParam(r, "coupon_id"); raw != "" {
		parsed, ok := parseID(w, raw)
		if !ok {
			return
		}
		id = parsed
	}
	payload := map[string]any{}
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	data, err := h.service.AdminSavePromoCode(r.Context(), id, payload)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) deletePromoCode(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "coupon_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminDeletePromoCode(r.Context(), id)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) listMallItems(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminListMallItems(r.Context(), h.query(r), strings.TrimSpace(r.URL.Query().Get("category")))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) createMallCoupon(w http.ResponseWriter, r *http.Request) {
	adminID, _ := notificationUserID(w, r)
	payload := map[string]any{}
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	data, err := h.service.AdminCreateMallCoupon(r.Context(), adminID, payload)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) updateMallItem(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "item_id"))
	if !ok {
		return
	}
	payload := map[string]any{}
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	data, err := h.service.AdminUpdateMallItem(r.Context(), id, payload)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) deleteMallItem(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "item_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminDeleteMallItem(r.Context(), id)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) listMallClaims(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminListMallClaims(r.Context(), h.query(r), queryInt64Ptr(r.URL.Query().Get("mall_item_id")))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) rawList(table string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		data, err := h.service.AdminRawList(r.Context(), table, h.query(r))
		h.writeResult(w, data, err)
	}
}

func (h *AdminHandler) rawGet(table string, param string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		id, ok := parseID(w, chi.URLParam(r, param))
		if !ok {
			return
		}
		data, err := rawByID(r.Context(), h.service.db, table, id)
		h.writeResult(w, data, err)
	}
}

func (h *AdminHandler) rawSave(table string, param string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		id := int64(0)
		if raw := chi.URLParam(r, param); raw != "" {
			parsed, ok := parseID(w, raw)
			if !ok {
				return
			}
			id = parsed
		}
		payload := map[string]any{}
		if !decodeAdminJSON(w, r, &payload) {
			return
		}
		data, err := h.service.AdminRawSave(r.Context(), table, id, payload)
		h.writeResult(w, data, err)
	}
}

func (h *AdminHandler) rawDelete(table string, param string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		id, ok := parseID(w, chi.URLParam(r, param))
		if !ok {
			return
		}
		data, err := h.service.AdminRawDelete(r.Context(), table, id)
		h.writeResult(w, data, err)
	}
}

func (h *AdminHandler) listMemberLevels(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminListMemberLevels(r.Context(), h.query(r))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) saveMemberLevel(w http.ResponseWriter, r *http.Request) {
	id := int64(0)
	if raw := chi.URLParam(r, "level_id"); raw != "" {
		parsed, ok := parseID(w, raw)
		if !ok {
			return
		}
		id = parsed
	}
	var item MemberLevel
	if !decodeAdminJSON(w, r, &item) {
		return
	}
	data, err := h.service.AdminSaveMemberLevel(r.Context(), id, item)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) updateSpendEventType(w http.ResponseWriter, r *http.Request) {
	payload := map[string]any{}
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	data, err := h.service.AdminUpdateSpendEventType(r.Context(), chi.URLParam(r, "code"), payload)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) recalculateMembershipSpend(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminRecalculateMembershipSpend(r.Context())
	h.writeResult(w, data, err)
}

func (h *AdminHandler) listAgents(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminListAgents(r.Context(), AdminListQuery{
		Search: strings.TrimSpace(r.URL.Query().Get("keyword")),
		Status: strings.TrimSpace(r.URL.Query().Get("filter_status")),
		Limit:  queryInt(r.URL.Query().Get("limit"), 20, 1, 100),
		Offset: queryInt(r.URL.Query().Get("offset"), 0, 0, 100000),
	})
	h.writeResult(w, data, err)
}

func (h *AdminHandler) getAgent(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "agent_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminGetAgent(r.Context(), id)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) reviewAgent(w http.ResponseWriter, r *http.Request) {
	adminID, _ := notificationUserID(w, r)
	id, ok := parseID(w, chi.URLParam(r, "agent_id"))
	if !ok {
		return
	}
	payload := map[string]any{}
	if r.Body != nil && r.ContentLength != 0 {
		if !decodeAdminJSON(w, r, &payload) {
			return
		}
	}
	data, err := h.service.AdminReviewAgent(r.Context(), id, payload, adminID)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) assignAgentTier(w http.ResponseWriter, r *http.Request) {
	adminID, _ := notificationUserID(w, r)
	id, ok := parseID(w, chi.URLParam(r, "agent_id"))
	if !ok {
		return
	}
	payload := map[string]any{}
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	data, err := h.service.AdminAssignAgentTier(r.Context(), id, int64Value(payload["agent_tier_id"]), adminID)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) listAgentSettlements(w http.ResponseWriter, r *http.Request) {
	query := r.URL.Query()
	data, err := h.service.AdminListAgentSettlements(r.Context(), AdminAgentSettlementQuery{
		Status:         strings.TrimSpace(query.Get("status")),
		SettlementType: strings.TrimSpace(query.Get("settlement_type")),
		UserID:         queryInt64Ptr(query.Get("user_id")),
		OrderID:        queryInt64Ptr(query.Get("order_id")),
		Limit:          queryInt(query.Get("limit"), 50, 1, 200),
		Offset:         queryInt(query.Get("offset"), 0, 0, 1000000),
	})
	h.writeResult(w, data, err)
}

func (h *AdminHandler) evaluateAgent(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "agent_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminEvaluateAgent(r.Context(), id)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) agentAction(w http.ResponseWriter, r *http.Request) {
	adminID, _ := notificationUserID(w, r)
	id, ok := parseID(w, chi.URLParam(r, "agent_id"))
	if !ok {
		return
	}
	payload := map[string]any{}
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	data, err := h.service.AdminAgentManualAction(r.Context(), id, payload, adminID)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) affiliateReport(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminAffiliateReport(r.Context())
	h.writeResult(w, data, err)
}

func (h *AdminHandler) taskList(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminTaskList(r.Context())
	h.writeResult(w, data, err)
}

func (h *AdminHandler) listTaskRuns(w http.ResponseWriter, r *http.Request) {
	query := h.query(r)
	data, err := h.service.AdminListTaskRuns(r.Context(), query, strings.TrimSpace(r.URL.Query().Get("task_key")), strings.TrimSpace(r.URL.Query().Get("trigger_type")))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) runTask(w http.ResponseWriter, r *http.Request) {
	adminID, _ := notificationUserID(w, r)
	data, err := h.service.AdminRunTask(r.Context(), chi.URLParam(r, "task_key"), adminID)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) listUpstreamActions(w http.ResponseWriter, r *http.Request) {
	query := r.URL.Query()
	data, err := h.service.AdminListUpstreamActions(r.Context(), AdminUpstreamActionQuery{
		Status:     strings.TrimSpace(query.Get("status")),
		Search:     strings.TrimSpace(firstNonEmpty(query.Get("keyword"), query.Get("search"))),
		Source:     strings.TrimSpace(query.Get("source")),
		ActionType: strings.TrimSpace(query.Get("action_type")),
		UserID:     queryInt64Ptr(query.Get("user_id")),
		OrderID:    queryInt64Ptr(query.Get("order_id")),
		InstanceID: queryInt64Ptr(query.Get("instance_id")),
		Limit:      queryInt(query.Get("limit"), 100, 1, 500),
		Offset:     queryInt(query.Get("offset"), 0, 0, 1000000),
	})
	h.writeResult(w, data, err)
}

func (h *AdminHandler) upstreamAction(status string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		id, ok := parseID(w, chi.URLParam(r, "action_id"))
		if !ok {
			return
		}
		payload := map[string]any{}
		if r.Body != nil && r.ContentLength != 0 {
			if !decodeAdminJSON(w, r, &payload) {
				return
			}
		}
		data, err := h.service.AdminUpstreamActionUpdate(r.Context(), id, status, stringFromMap(payload, "remark"))
		h.writeResult(w, data, err)
	}
}

func (h *AdminHandler) syncUpstreamAction(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "action_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminSyncUpstreamAction(r.Context(), id)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) syncUpstreamActions(w http.ResponseWriter, r *http.Request) {
	payload := map[string]any{}
	if r.Body != nil && r.ContentLength != 0 {
		if !decodeAdminJSON(w, r, &payload) {
			return
		}
	}
	limit := intValueAny(payload["limit"], queryInt(r.URL.Query().Get("limit"), 100, 1, 500))
	data, err := h.service.SyncPendingUpstreamActions(r.Context(), limit)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) query(r *http.Request) AdminListQuery {
	search := strings.TrimSpace(r.URL.Query().Get("search"))
	if search == "" {
		search = strings.TrimSpace(r.URL.Query().Get("keyword"))
	}
	var groupID *int64
	if parsed, err := strconv.ParseInt(strings.TrimSpace(r.URL.Query().Get("group_id")), 10, 64); err == nil && parsed > 0 {
		groupID = &parsed
	}
	return AdminListQuery{
		Status:  strings.TrimSpace(r.URL.Query().Get("status")),
		Search:  search,
		Role:    strings.TrimSpace(r.URL.Query().Get("role")),
		GroupID: groupID,
		Limit:   queryInt(r.URL.Query().Get("limit"), 50, 1, 200),
		Offset:  queryInt(r.URL.Query().Get("offset"), 0, 0, 1000000),
	}
}

func queryBoolPtr(raw string) *bool {
	text := strings.ToLower(strings.TrimSpace(raw))
	if text == "" || text == "all" {
		return nil
	}
	value := text == "true" || text == "1" || text == "yes" || text == "on"
	if value || text == "false" || text == "0" || text == "no" || text == "off" {
		return &value
	}
	return nil
}

func (h *AdminHandler) writeResult(w http.ResponseWriter, data any, err error) {
	if err != nil {
		h.writeError(w, err)
		return
	}
	response.OK(w, data)
}

func (h *AdminHandler) writeError(w http.ResponseWriter, err error) {
	var httpErr HTTPError
	if errors.As(err, &httpErr) {
		response.Detail(w, httpErr.Status, httpErr.Detail)
		return
	}
	switch {
	case errors.Is(err, plugins.ErrNotFound):
		response.Detail(w, http.StatusNotFound, err.Error())
		return
	case errors.Is(err, plugins.ErrInvalidManifest), errors.Is(err, plugins.ErrCapabilityMissing), errors.Is(err, plugins.ErrDisabled):
		response.Detail(w, http.StatusBadRequest, err.Error())
		return
	}
	if isAdminUpstreamError(err) {
		response.Detail(w, http.StatusBadGateway, err.Error())
		return
	}
	if h.logger != nil {
		h.logger.Error("admin commerce handler error", "error", err)
	}
	response.Detail(w, http.StatusInternalServerError, msgServiceError)
}

func isAdminUpstreamError(err error) bool {
	text := err.Error()
	return strings.Contains(text, "魔方财务调用失败") ||
		strings.Contains(text, "上游登录失败") ||
		strings.Contains(text, "上游返回异常") ||
		strings.Contains(text, "DCIM upstream call failed") ||
		strings.Contains(text, "upstream HTTP") ||
		strings.Contains(text, "dial tcp") ||
		strings.Contains(text, "connectex") ||
		strings.Contains(text, "no such host") ||
		strings.Contains(text, "Client.Timeout exceeded")
}

func decodeAdminJSON(w http.ResponseWriter, r *http.Request, out any) bool {
	defer r.Body.Close()
	if err := json.NewDecoder(r.Body).Decode(out); err != nil {
		response.Detail(w, http.StatusBadRequest, msgInvalidParams)
		return false
	}
	return true
}

func decodeProductPayload(w http.ResponseWriter, r *http.Request) (AdminProductPayload, bool) {
	defer r.Body.Close()
	raw, err := io.ReadAll(r.Body)
	if err != nil {
		response.Detail(w, http.StatusBadRequest, msgInvalidParams)
		return AdminProductPayload{}, false
	}
	var payload AdminProductPayload
	if err := json.Unmarshal(raw, &payload); err == nil && payload.Product != nil {
		payload.ProductFields = productPayloadFields(raw)
		return payload, true
	}
	var product Product
	if err := json.Unmarshal(raw, &product); err != nil {
		response.Detail(w, http.StatusBadRequest, msgInvalidParams)
		return AdminProductPayload{}, false
	}
	var withPricing struct {
		Pricings []ProductPricing `json:"pricings"`
	}
	_ = json.Unmarshal(raw, &withPricing)
	return AdminProductPayload{Product: &product, Pricings: withPricing.Pricings, ProductFields: productPayloadFields(raw)}, true
}

func productPayloadFields(raw []byte) map[string]json.RawMessage {
	var top map[string]json.RawMessage
	if err := json.Unmarshal(raw, &top); err != nil {
		return nil
	}
	if rawProduct, ok := top["product"]; ok {
		var nested map[string]json.RawMessage
		if err := json.Unmarshal(rawProduct, &nested); err == nil {
			return nested
		}
	}
	delete(top, "pricings")
	return top
}
