package commerce

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"path/filepath"
	"sort"
	"strings"

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

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

func (h *AdminHandler) businessRules(w http.ResponseWriter, r *http.Request) {
	items, err := h.service.AdminBusinessRules(r.Context())
	h.writeResult(w, items, err)
}

func (h *AdminHandler) updateBusinessRules(w http.ResponseWriter, r *http.Request) {
	var payload struct {
		Values map[string]any `json:"values"`
	}
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	items, err := h.service.AdminUpdateBusinessRules(r.Context(), payload.Values)
	h.writeResult(w, items, err)
}

func (h *AdminHandler) notificationPlugins(w http.ResponseWriter, r *http.Request) {
	if h.service.plugins == nil {
		h.writeResult(w, []any{}, nil)
		return
	}
	if err := h.service.plugins.Refresh(r.Context()); err != nil {
		h.writeError(w, err)
		return
	}
	capability := pluginTypeCapability(chi.URLParam(r, "plugin_type"))
	if capability == "" {
		capability = pluginTypeCapability(r.URL.Query().Get("plugin_type"))
	}
	items, err := h.service.plugins.ListAll(r.Context(), capability)
	if err != nil {
		h.writeError(w, err)
		return
	}
	selection, err := h.service.adminPluginSelection(r.Context())
	if err != nil {
		h.writeError(w, err)
		return
	}
	out := make([]map[string]any, 0, len(items))
	for _, item := range items {
		if capability == "" && !isAdminListedPlugin(item) {
			continue
		}
		out = append(out, adminPluginManifestWithSelection(item, selection))
	}
	h.writeResult(w, out, nil)
}

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

func (h *AdminHandler) uploadBrandLogo(w http.ResponseWriter, r *http.Request) {
	if err := r.ParseMultipartForm(brandLogoMaxBytes + 1024); err != nil {
		response.Detail(w, http.StatusBadRequest, "无效的上传请求")
		return
	}
	file, header, err := r.FormFile("file")
	if err != nil {
		response.Detail(w, http.StatusBadRequest, "请选择 Logo 文件")
		return
	}
	defer file.Close()
	data, err := h.service.UploadBrandLogo(r.Context(), file, header.Filename, header.Header.Get("Content-Type"))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) uploadBrandAsset(w http.ResponseWriter, r *http.Request) {
	if err := r.ParseMultipartForm(brandAssetMaxBytes + 1024); err != nil {
		response.Detail(w, http.StatusBadRequest, "无效的上传请求")
		return
	}
	file, header, err := r.FormFile("file")
	if err != nil {
		response.Detail(w, http.StatusBadRequest, "请选择资源文件")
		return
	}
	defer file.Close()
	data, err := h.service.UploadBrandAsset(r.Context(), file, header.Filename, header.Header.Get("Content-Type"))
	h.writeResult(w, data, err)
}

func (h *AdminHandler) frontendConfig(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminFrontendConfig(r.Context())
	h.writeResult(w, data, err)
}
func (h *AdminHandler) updateFrontendConfig(w http.ResponseWriter, r *http.Request) {
	var payload map[string]any
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	data, err := h.service.AdminUpdateFrontendConfig(r.Context(), payload)
	h.writeResult(w, data, err)
}

func (h *AdminHandler) previewNotificationPlugin(w http.ResponseWriter, r *http.Request) {
	if h.service.plugins == nil {
		response.Detail(w, http.StatusBadRequest, "plugin runtime is not enabled")
		return
	}
	if err := r.ParseMultipartForm(128 << 20); err != nil {
		response.Detail(w, http.StatusBadRequest, "invalid multipart upload")
		return
	}
	file, header, err := r.FormFile("file")
	if err != nil {
		response.Detail(w, http.StatusBadRequest, "missing plugin package file")
		return
	}
	defer file.Close()
	manifest, err := h.service.plugins.Preview(r.Context(), file, header.Filename)
	if err != nil {
		h.writeError(w, err)
		return
	}
	installPath, err := h.service.plugins.InstallPath(manifest.ID, manifest.Version)
	if err != nil {
		h.writeError(w, err)
		return
	}
	_, existingErr := h.service.plugins.Manifest(r.Context(), manifest.ID)
	data := adminPluginPreviewMap(manifest, header.Filename, installPath, existingErr == nil)
	h.writeResult(w, data, nil)
}

func (h *AdminHandler) installNotificationPlugin(w http.ResponseWriter, r *http.Request) {
	if h.service.plugins == nil {
		response.Detail(w, http.StatusBadRequest, "plugin runtime is not enabled")
		return
	}
	if err := r.ParseMultipartForm(128 << 20); err != nil {
		response.Detail(w, http.StatusBadRequest, "invalid multipart upload")
		return
	}
	file, header, err := r.FormFile("file")
	if err != nil {
		response.Detail(w, http.StatusBadRequest, "missing plugin package file")
		return
	}
	defer file.Close()
	result, err := h.service.plugins.Install(r.Context(), file, plugins.InstallOptions{
		Filename: header.Filename,
		Replace:  truthyString(r.URL.Query().Get("overwrite")) || truthyString(r.FormValue("overwrite")) || truthyString(r.FormValue("replace")),
		Enable:   !falseyString(r.FormValue("enable")),
	})
	if err != nil {
		h.writeError(w, err)
		return
	}
	data := adminPluginManifestMap(result.Manifest)
	data["path"] = filepath.ToSlash(result.Path)
	data["install_path"] = filepath.ToSlash(result.Path)
	data["replaced"] = result.Replaced
	h.writeResult(w, data, nil)
}

func (h *AdminHandler) reloadNotificationPlugins(w http.ResponseWriter, r *http.Request) {
	if h.service.plugins == nil {
		response.Detail(w, http.StatusBadRequest, "plugin runtime is not enabled")
		return
	}
	var payload struct {
		InstallDependencies bool `json:"install_dependencies"`
		RestartBackend      bool `json:"restart_backend"`
	}
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	err := h.service.plugins.Refresh(r.Context())
	data := map[string]any{
		"dependencies": map[string]any{
			"installed":        true,
			"packages":         []string{},
			"pending_packages": []string{},
			"python":           "",
			"result":           nil,
		},
		"restart": map[string]any{
			"requested": payload.RestartBackend,
			"mode":      "go_registry_refresh",
			"result":    map[string]any{"reloaded": err == nil},
		},
	}
	h.writeResult(w, data, err)
}

func (h *AdminHandler) notificationPluginConfig(w http.ResponseWriter, r *http.Request) {
	manifest, config, ok := h.loadAdminPlugin(w, r)
	if !ok {
		return
	}
	h.writeResult(w, map[string]any{"plugin": adminPluginManifestMap(manifest), "values": redactAdminPluginConfig(config), "config": redactAdminPluginConfig(config), "schema": manifest.ConfigSchema}, nil)
}

func (h *AdminHandler) setNotificationPluginConfig(w http.ResponseWriter, r *http.Request) {
	pluginID := chi.URLParam(r, "plugin_id")
	var payload map[string]any
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	values := map[string]any{}
	if nested, ok := payload["values"].(map[string]any); ok {
		values = nested
	} else if nested, ok := payload["config"].(map[string]any); ok {
		values = nested
	} else {
		values = payload
	}
	manifest, err := h.service.plugins.Manifest(r.Context(), pluginID)
	if err != nil {
		h.writeError(w, err)
		return
	}
	current, err := h.service.effectiveAdminPluginConfig(r.Context(), manifest)
	if err != nil {
		h.writeError(w, err)
		return
	}
	for key, value := range values {
		if strings.TrimSpace(fmt.Sprint(value)) == "***" {
			if existing, ok := current[key]; ok {
				values[key] = existing
			}
		}
	}
	config, err := h.service.plugins.SetConfig(r.Context(), pluginID, values)
	if err != nil {
		h.writeError(w, err)
		return
	}
	h.writeResult(w, map[string]any{"plugin": adminPluginManifestMap(manifest), "values": redactAdminPluginConfig(config), "config": redactAdminPluginConfig(config)}, nil)
}

func (h *AdminHandler) enableNotificationPlugin(w http.ResponseWriter, r *http.Request) {
	h.setNotificationPluginEnabled(w, r, true)
}

func (h *AdminHandler) disableNotificationPlugin(w http.ResponseWriter, r *http.Request) {
	h.setNotificationPluginEnabled(w, r, false)
}

func (h *AdminHandler) setNotificationPluginEnabled(w http.ResponseWriter, r *http.Request, enabled bool) {
	pluginID := chi.URLParam(r, "plugin_id")
	manifest, err := h.service.plugins.Manifest(r.Context(), pluginID)
	if err != nil {
		h.writeError(w, err)
		return
	}
	capability := pluginTypeCapability(chi.URLParam(r, "plugin_type"))
	if capability == "" || !manifest.HasCapability(capability) {
		response.Detail(w, http.StatusBadRequest, "plugin type does not match its capability")
		return
	}
	if enabled && !manifest.Enabled {
		manifest, err = h.service.plugins.SetEnabled(r.Context(), pluginID, r.URL.Query().Get("version"), true)
		if err != nil {
			h.writeError(w, err)
			return
		}
	}
	if err := h.service.setAdminPluginSelection(r.Context(), manifest, capability, enabled); err != nil {
		h.writeError(w, err)
		return
	}
	selection, err := h.service.adminPluginSelection(r.Context())
	if err != nil {
		h.writeError(w, err)
		return
	}
	h.writeResult(w, adminPluginManifestWithSelection(manifest, selection), nil)
}

func (h *AdminHandler) testNotificationPlugin(w http.ResponseWriter, r *http.Request) {
	manifest, baseConfig, ok := h.loadAdminPlugin(w, r)
	if !ok {
		return
	}
	var payload struct {
		Target     string         `json:"target"`
		Mode       string         `json:"mode"`
		RealName   string         `json:"real_name"`
		IDCardNo   string         `json:"id_card_no"`
		Phone      string         `json:"phone"`
		ReturnURL  string         `json:"return_url"`
		Values     map[string]any `json:"values"`
		Config     map[string]any `json:"config"`
		Template   string         `json:"template"`
		TemplateID string         `json:"template_code"`
	}
	if !decodeAdminJSON(w, r, &payload) {
		return
	}
	config := mergeAdminPluginConfig(baseConfig, payload.Values)
	config = mergeAdminPluginConfig(config, payload.Config)
	capability := pluginTypeCapability(chi.URLParam(r, "plugin_type"))
	var data any
	var err error
	switch capability {
	case plugins.CapabilityEmail:
		if strings.TrimSpace(payload.Target) == "" {
			response.Detail(w, http.StatusBadRequest, "test email is required")
			return
		}
		data, err = h.service.plugins.SendEmail(r.Context(), manifest.ID, plugins.EmailSendRequest{
			To:       payload.Target,
			Subject:  "QYCW plugin test",
			TextBody: "This is a QYCW plugin test email.",
			Config:   config,
		})
	case plugins.CapabilitySMS:
		if strings.TrimSpace(payload.Target) == "" {
			response.Detail(w, http.StatusBadRequest, "test phone number is required")
			return
		}
		data, err = h.service.plugins.SendSMS(r.Context(), manifest.ID, plugins.SMSSendRequest{
			Phone:        payload.Target,
			Code:         "123456",
			Purpose:      "admin_test",
			TemplateCode: firstString(payload.TemplateID, payload.Template),
			Config:       config,
		})
	case plugins.CapabilityVerification:
		mode := strings.TrimSpace(payload.Mode)
		if mode == "" {
			mode = "two_factor"
		}
		req := plugins.VerificationRequest{Mode: mode, RealName: payload.RealName, IDCardNo: strings.ToUpper(strings.TrimSpace(payload.IDCardNo)), Phone: payload.Phone, ReturnURL: payload.ReturnURL, Config: config}
		if mode == "face_redirect" || mode == "redirect" {
			data, err = h.service.plugins.StartVerificationSession(r.Context(), manifest.ID, req)
		} else {
			data, err = h.service.plugins.VerifyIdentity(r.Context(), manifest.ID, req)
		}
	default:
		data = map[string]any{"ok": true, "message": "plugin manifest and config loaded", "plugin": adminPluginManifestMap(manifest), "config_keys": mapKeys(config)}
	}
	h.writeResult(w, data, err)
}

func (h *AdminHandler) deleteNotificationPlugin(w http.ResponseWriter, r *http.Request) {
	pluginID := chi.URLParam(r, "plugin_id")
	manifest, err := h.service.plugins.Manifest(r.Context(), pluginID)
	if err != nil {
		h.writeResult(w, nil, err)
		return
	}
	selection, err := h.service.adminPluginSelection(r.Context())
	if err != nil {
		h.writeResult(w, nil, err)
		return
	}
	selected := adminPluginManifestWithSelection(manifest, selection)
	if selected["current"] == true {
		response.Detail(w, http.StatusBadRequest, "请先停用该插件再删除")
		return
	}
	result, err := h.service.plugins.Uninstall(r.Context(), pluginID, r.URL.Query().Get("version"))
	h.writeResult(w, result, err)
}

func (h *AdminHandler) loadAdminPlugin(w http.ResponseWriter, r *http.Request) (plugins.Manifest, map[string]any, bool) {
	if h.service.plugins == nil {
		response.Detail(w, http.StatusBadRequest, "plugin runtime is not enabled")
		return plugins.Manifest{}, nil, false
	}
	manifest, err := h.service.plugins.Manifest(r.Context(), chi.URLParam(r, "plugin_id"))
	if err != nil {
		h.writeError(w, err)
		return plugins.Manifest{}, nil, false
	}
	capability := pluginTypeCapability(chi.URLParam(r, "plugin_type"))
	if capability != "" && !manifest.HasCapability(capability) {
		response.Detail(w, http.StatusBadRequest, fmt.Sprintf("plugin does not provide %s", capability))
		return plugins.Manifest{}, nil, false
	}
	config, err := h.service.effectiveAdminPluginConfig(r.Context(), manifest)
	if err != nil {
		h.writeError(w, err)
		return plugins.Manifest{}, nil, false
	}
	return manifest, config, true
}

func (s *Service) effectiveAdminPluginConfig(ctx context.Context, manifest plugins.Manifest) (map[string]any, error) {
	if s.plugins == nil {
		return nil, plugins.ErrNotFound
	}
	var base map[string]any
	switch primaryPluginCapability(manifest) {
	case plugins.CapabilityEmail:
		base = s.legacyPluginConfig(ctx, "notification.email.", map[string]string{
			"smtp_host":           "smtp_host",
			"smtp_port":           "smtp_port",
			"smtp_user":           "smtp_username",
			"smtp_password":       "smtp_password",
			"smtp_from_email":     "smtp_from",
			"smtp_from_name":      "smtp_from_name",
			"smtp_security":       "smtp_security",
			"smtp_auth_enabled":   "smtp_auth_enabled",
			"smtp_validate_certs": "smtp_validate_certs",
			"smtp_timeout":        "smtp_timeout",
			"smtp_reply_to":       "smtp_reply_to",
		})
	case plugins.CapabilitySMS:
		base = s.legacyPluginConfig(ctx, "notification.sms.", identityPluginConfigKeys(
			"access_key_id", "access_key_secret", "endpoint", "sign_name", "template_code",
		))
	case plugins.CapabilityVerification:
		base = s.legacyVerificationPluginConfig(ctx, manifest.ID)
	case plugins.CapabilityPayment:
		return s.paymentConfig(ctx, manifest.ID), nil
	default:
		base = map[string]any{}
	}
	return s.plugins.MergeConfig(ctx, manifest.ID, base), nil
}

func identityPluginConfigKeys(keys ...string) map[string]string {
	out := make(map[string]string, len(keys))
	for _, key := range keys {
		out[key] = key
	}
	return out
}

func (s *Service) legacyVerificationPluginConfig(ctx context.Context, pluginID string) map[string]any {
	switch pluginID {
	case "aliyun_idcert":
		return s.legacyPluginConfig(ctx, "verification.aliyun_idcert.", identityPluginConfigKeys("appcode", "endpoint"))
	case "baidu_face_v3":
		return s.legacyPluginConfig(ctx, "verification.baidu_face_v3.", identityPluginConfigKeys("access_key", "secret_key", "score_threshold"))
	case "leafsm_h5_face":
		return s.legacyPluginConfig(ctx, "verification.leafsm_h5_face.", identityPluginConfigKeys("app_id", "app_secret", "base_url", "h5_base_url", "timeout"))
	case "tencent_wechat_h5":
		return s.legacyPluginConfig(ctx, "verification.tencent.", map[string]string{
			"secret_id":         "secret_id",
			"secret_key":        "secret_key",
			"wechat_h5.rule_id": "wechat_h5_rule_id",
			"endpoint":          "endpoint",
			"info_type":         "info_type",
			"score_threshold":   "score_threshold",
		})
	case "tencent_wechat_float_h5":
		return s.legacyPluginConfig(ctx, "verification.tencent.", map[string]string{
			"secret_id":               "secret_id",
			"secret_key":              "secret_key",
			"wechat_float_h5.rule_id": "wechat_float_h5_rule_id",
			"endpoint":                "endpoint",
			"info_type":               "info_type",
			"score_threshold":         "score_threshold",
		})
	default:
		return map[string]any{}
	}
}

func (s *Service) legacyPluginConfig(ctx context.Context, prefix string, keys map[string]string) map[string]any {
	out := map[string]any{}
	if s.db == nil || len(keys) == 0 {
		return out
	}
	settingKeys := make([]string, 0, len(keys))
	for suffix := range keys {
		settingKeys = append(settingKeys, prefix+suffix)
	}
	var settings []SystemSetting
	if err := s.db.WithContext(ctx).Where("`key` IN ?", settingKeys).Find(&settings).Error; err != nil {
		return out
	}
	for _, item := range settings {
		suffix := strings.TrimPrefix(item.Key, prefix)
		key, ok := keys[suffix]
		if !ok {
			continue
		}
		var value any
		if err := json.Unmarshal(item.Value, &value); err != nil {
			value = strings.Trim(strings.TrimSpace(string(item.Value)), `"`)
		}
		out[key] = value
	}
	return out
}

func pluginTypeCapability(value string) string {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case "sms":
		return plugins.CapabilitySMS
	case "email":
		return plugins.CapabilityEmail
	case "verification", "verify", "identity":
		return plugins.CapabilityVerification
	case "payment", "pay":
		return plugins.CapabilityPayment
	case "provision", "provider", "product":
		return plugins.CapabilityProvision
	case "auth", "oauth", "login":
		return plugins.CapabilityAuth
	default:
		return ""
	}
}

func isAdminListedPlugin(manifest plugins.Manifest) bool {
	for _, capability := range []string{plugins.CapabilitySMS, plugins.CapabilityEmail, plugins.CapabilityVerification, plugins.CapabilityPayment, plugins.CapabilityProvision, plugins.CapabilityAuth} {
		if manifest.HasCapability(capability) {
			return true
		}
	}
	return false
}

func adminPluginManifestMap(manifest plugins.Manifest) map[string]any {
	pluginType := strings.TrimSpace(manifest.Type)
	if pluginType == "" {
		pluginType = primaryPluginCapability(manifest)
	}
	return map[string]any{
		"schema_version": manifest.SchemaVersion,
		"id":             manifest.ID,
		"plugin_id":      manifest.ID,
		"type":           pluginType,
		"name":           manifest.Name,
		"version":        manifest.Version,
		"runtime":        manifest.Runtime,
		"capabilities":   manifest.Capabilities,
		"description":    manifest.Description,
		"config_schema":  manifest.ConfigSchema,
		"config_fields":  pluginConfigFields(manifest.ConfigSchema),
		"requirements":   manifest.Requirements,
		"permissions":    manifest.Permissions,
		"configurable":   true,
		"config_prefix":  manifest.ID,
		"test_supported": manifest.HasCapability(plugins.CapabilityEmail) || manifest.HasCapability(plugins.CapabilitySMS) || manifest.HasCapability(plugins.CapabilityVerification),
		"available":      manifest.Enabled,
		"enabled":        false,
		"current":        false,
		"status":         map[bool]string{true: "available", false: "unavailable"}[manifest.Enabled],
		"provider":       "plugin:" + manifest.ID,
	}
}

type adminPluginSelection struct {
	featureProviders map[string]string
	featureEnabled   map[string]bool
	paymentEnabled   map[string]bool
	authEnabled      map[string]bool
}

func (s *Service) adminPluginSelection(ctx context.Context) (adminPluginSelection, error) {
	selection := adminPluginSelection{
		featureProviders: map[string]string{},
		featureEnabled:   map[string]bool{},
		paymentEnabled:   map[string]bool{},
		authEnabled:      map[string]bool{},
	}
	if s.db == nil {
		return selection, nil
	}
	keys := []string{
		"notification.email.enabled", "notification.email.provider",
		"notification.sms.enabled", "notification.sms.provider",
		"verification.enabled", "verification.provider",
		"payment.enabled_plugins",
		"auth.enabled_plugins",
	}
	var settings []SystemSetting
	if err := s.db.WithContext(ctx).Where("`key` IN ?", keys).Find(&settings).Error; err != nil {
		return selection, err
	}
	for _, item := range settings {
		switch item.Key {
		case "notification.email.enabled":
			selection.featureEnabled[plugins.CapabilityEmail] = settingBool(item.Value, false)
		case "notification.email.provider":
			selection.featureProviders[plugins.CapabilityEmail] = configuredPluginID(settingStringCommerce(item.Value, ""), plugins.CapabilityEmail)
		case "notification.sms.enabled":
			selection.featureEnabled[plugins.CapabilitySMS] = settingBool(item.Value, false)
		case "notification.sms.provider":
			selection.featureProviders[plugins.CapabilitySMS] = configuredPluginID(settingStringCommerce(item.Value, ""), plugins.CapabilitySMS)
		case "verification.enabled":
			selection.featureEnabled[plugins.CapabilityVerification] = settingBool(item.Value, false)
		case "verification.provider":
			selection.featureProviders[plugins.CapabilityVerification] = configuredPluginID(settingStringCommerce(item.Value, ""), plugins.CapabilityVerification)
		case "payment.enabled_plugins":
			for _, id := range splitPluginIDs(settingStringCommerce(item.Value, "")) {
				selection.paymentEnabled[id] = true
			}
		case "auth.enabled_plugins":
			for _, id := range splitPluginIDs(settingStringCommerce(item.Value, "")) {
				selection.authEnabled[id] = true
			}
		}
	}
	return selection, nil
}

func adminPluginManifestWithSelection(manifest plugins.Manifest, selection adminPluginSelection) map[string]any {
	out := adminPluginManifestMap(manifest)
	capability := primaryPluginCapability(manifest)
	current := false
	enabled := false
	if capability == plugins.CapabilityPayment {
		current = selection.paymentEnabled[manifest.ID]
		enabled = current && manifest.Enabled
	} else if capability == plugins.CapabilityAuth {
		current = selection.authEnabled[manifest.ID]
		enabled = current && manifest.Enabled
	} else if capability != "" {
		current = selection.featureProviders[capability] == manifest.ID
		enabled = current && selection.featureEnabled[capability] && manifest.Enabled
	}
	out["current"] = current
	out["enabled"] = enabled
	switch {
	case enabled:
		out["status"] = "enabled"
	case current:
		out["status"] = "selected_disabled"
	case manifest.Enabled:
		out["status"] = "available"
	default:
		out["status"] = "unavailable"
	}
	return out
}

func (s *Service) setAdminPluginSelection(ctx context.Context, manifest plugins.Manifest, capability string, enabled bool) error {
	if s.db == nil {
		return ServiceUnavailable("database is required to select a plugin")
	}
	return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if capability == plugins.CapabilityPayment || capability == plugins.CapabilityAuth {
			selection, err := s.withDB(tx).adminPluginSelection(ctx)
			if err != nil {
				return err
			}
			selectedPlugins := selection.paymentEnabled
			settingKey, label := "payment.enabled_plugins", "已启用支付插件"
			if capability == plugins.CapabilityAuth {
				selectedPlugins = selection.authEnabled
				settingKey, label = "auth.enabled_plugins", "已启用第三方登录插件"
			}
			selectedPlugins[manifest.ID] = enabled
			ids := make([]string, 0, len(selectedPlugins))
			for id, selected := range selectedPlugins {
				if selected {
					ids = append(ids, id)
				}
			}
			sort.Strings(ids)
			return upsertPluginSetting(ctx, tx, settingKey, strings.Join(ids, ","), label)
		}

		providerKey, enabledKey := featurePluginSettingKeys(capability)
		if providerKey == "" {
			return BadRequest("unsupported single-select plugin capability")
		}
		if enabled {
			if err := upsertPluginSetting(ctx, tx, providerKey, "plugin:"+manifest.ID, "当前插件"); err != nil {
				return err
			}
			if capability == plugins.CapabilityVerification {
				if err := upsertPluginSetting(ctx, tx, "verification.default_mode", preferredVerificationMode(manifest), "默认核验方式"); err != nil {
					return err
				}
			}
			return upsertPluginSetting(ctx, tx, enabledKey, true, "启用插件")
		}

		selection, err := s.withDB(tx).adminPluginSelection(ctx)
		if err != nil {
			return err
		}
		if selection.featureProviders[capability] != manifest.ID {
			return nil
		}
		return upsertPluginSetting(ctx, tx, enabledKey, false, "启用插件")
	})
}

func upsertPluginSetting(ctx context.Context, tx *gorm.DB, key string, value any, label string) error {
	raw, err := json.Marshal(value)
	if err != nil {
		return err
	}
	var item SystemSetting
	err = tx.WithContext(ctx).Where("`key` = ?", key).First(&item).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return tx.WithContext(ctx).Create(&SystemSetting{Key: key, Value: JSONRaw(raw), Label: label}).Error
	}
	if err != nil {
		return err
	}
	return tx.WithContext(ctx).Model(&SystemSetting{}).Where("id = ?", item.ID).Update("value", JSONRaw(raw)).Error
}

func featurePluginSettingKeys(capability string) (string, string) {
	switch capability {
	case plugins.CapabilityEmail:
		return "notification.email.provider", "notification.email.enabled"
	case plugins.CapabilitySMS:
		return "notification.sms.provider", "notification.sms.enabled"
	case plugins.CapabilityVerification:
		return "verification.provider", "verification.enabled"
	default:
		return "", ""
	}
}

func primaryPluginCapability(manifest plugins.Manifest) string {
	for _, capability := range []string{plugins.CapabilityEmail, plugins.CapabilitySMS, plugins.CapabilityVerification, plugins.CapabilityPayment, plugins.CapabilityProvision, plugins.CapabilityAuth} {
		if manifest.HasCapability(capability) {
			return capability
		}
	}
	return ""
}

func configuredPluginID(provider string, capability string) string {
	if id := plugins.PluginIDFromProvider(provider); id != "" {
		return id
	}
	prefix := "mode." + capability + "."
	provider = strings.TrimSpace(provider)
	if !strings.HasPrefix(provider, prefix) {
		return ""
	}
	id := strings.SplitN(strings.TrimPrefix(provider, prefix), ".", 2)[0]
	if plugins.SafeID(id) {
		return id
	}
	return ""
}

func splitPluginIDs(value string) []string {
	out := []string{}
	seen := map[string]bool{}
	for _, item := range strings.Split(value, ",") {
		id := strings.TrimSpace(item)
		if plugins.SafeID(id) && !seen[id] {
			seen[id] = true
			out = append(out, id)
		}
	}
	return out
}

func preferredVerificationMode(manifest plugins.Manifest) string {
	for _, mode := range []string{"two_factor", "three_factor", "face_redirect"} {
		if manifest.HasCapability(mode) {
			return mode
		}
	}
	return "two_factor"
}

func adminPluginPreviewMap(manifest plugins.Manifest, filename string, installPath string, exists bool) map[string]any {
	data := adminPluginManifestMap(manifest)
	data["filename"] = filename
	data["root_dir"] = ""
	data["exists"] = exists
	data["install_path"] = filepath.ToSlash(installPath)
	return data
}

func pluginConfigFields(schema map[string]any) any {
	if schema == nil {
		return []any{}
	}
	if fields, ok := schema["fields"]; ok {
		return fields
	}
	return []any{}
}

func redactAdminPluginConfig(config map[string]any) map[string]any {
	out := map[string]any{}
	for key, value := range config {
		lower := strings.ToLower(key)
		if strings.Contains(lower, "secret") || strings.Contains(lower, "password") || strings.Contains(lower, "token") || lower == "key" || lower == "appcode" {
			if strings.TrimSpace(fmt.Sprint(value)) != "" {
				out[key] = "***"
				continue
			}
		}
		out[key] = value
	}
	return out
}

func mergeMapsAny(base map[string]any, extra map[string]any) map[string]any {
	out := map[string]any{}
	for key, value := range base {
		out[key] = value
	}
	for key, value := range extra {
		out[key] = value
	}
	return out
}

func mergeAdminPluginConfig(base map[string]any, extra map[string]any) map[string]any {
	out := mergeMapsAny(base, nil)
	for key, value := range extra {
		if strings.TrimSpace(fmt.Sprint(value)) == "***" {
			continue
		}
		out[key] = value
	}
	return out
}

func truthyString(value string) bool {
	value = strings.TrimSpace(value)
	return value == "1" || strings.EqualFold(value, "true") || strings.EqualFold(value, "yes") || strings.EqualFold(value, "on")
}

func falseyString(value string) bool {
	value = strings.TrimSpace(value)
	return value == "0" || strings.EqualFold(value, "false") || strings.EqualFold(value, "no") || strings.EqualFold(value, "off")
}

func mapKeys(values map[string]any) []string {
	keys := make([]string, 0, len(values))
	for key := range values {
		keys = append(keys, key)
	}
	return keys
}
