package levelconfig

import (
	"bytes"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/integration/activityclient"
	"hyapp-admin-server/internal/integration/walletclient"
	"hyapp-admin-server/internal/middleware"
	"hyapp-admin-server/internal/modules/shared"
	"hyapp-admin-server/internal/response"
	activityv1 "hyapp.local/api/proto/activity/v1"
	walletv1 "hyapp.local/api/proto/wallet/v1"

	"github.com/gin-gonic/gin"
)

const (
	statusActive   = "active"
	statusDisabled = "disabled"

	longBadgeResourceIDKey   = "long_badge_resource_id"
	avatarFrameResourceIDKey = "avatar_frame_resource_id"
	shortBadgeResourceIDKey  = "short_badge_resource_id"
)

type Handler struct {
	activity activityclient.Client
	wallet   walletclient.Client
	audit    shared.OperationLogger
}

func New(activity activityclient.Client, wallet walletclient.Client, audit shared.OperationLogger) *Handler {
	return &Handler{activity: activity, wallet: wallet, audit: audit}
}

type levelConfigDTO struct {
	Tracks       []trackDTO `json:"tracks"`
	ServerTimeMS int64      `json:"server_time_ms"`
}

type trackDTO struct {
	Track             string    `json:"track"`
	Name              string    `json:"name"`
	Status            string    `json:"status"`
	SortOrder         int32     `json:"sort_order"`
	DisplayConfigJSON string    `json:"display_config_json"`
	Rules             []ruleDTO `json:"rules"`
	Tiers             []tierDTO `json:"tiers"`
	CreatedAtMS       int64     `json:"created_at_ms"`
	UpdatedAtMS       int64     `json:"updated_at_ms"`
}

type ruleDTO struct {
	Track                 string `json:"track"`
	Level                 int32  `json:"level"`
	RequiredValue         int64  `json:"required_value"`
	Name                  string `json:"name"`
	Status                string `json:"status"`
	RewardResourceGroupID int64  `json:"reward_resource_group_id"`
	SortOrder             int32  `json:"sort_order"`
	LongBadgeResourceID   int64  `json:"long_badge_resource_id"`
	AvatarFrameResourceID int64  `json:"avatar_frame_resource_id"`
	ShortBadgeResourceID  int64  `json:"short_badge_resource_id"`
	DisplayConfigJSON     string `json:"display_config_json"`
	CreatedByAdminID      int64  `json:"created_by_admin_id"`
	UpdatedByAdminID      int64  `json:"updated_by_admin_id"`
	CreatedAtMS           int64  `json:"created_at_ms"`
	UpdatedAtMS           int64  `json:"updated_at_ms"`
}

type tierDTO struct {
	TierID                       int64  `json:"tier_id"`
	Track                        string `json:"track"`
	MinLevel                     int32  `json:"min_level"`
	MaxLevel                     int32  `json:"max_level"`
	Name                         string `json:"name"`
	DisplayAvatarFrameResourceID int64  `json:"display_avatar_frame_resource_id"`
	DisplayBadgeResourceID       int64  `json:"display_badge_resource_id"`
	RewardResourceGroupID        int64  `json:"reward_resource_group_id"`
	Status                       string `json:"status"`
	DisplayConfigJSON            string `json:"display_config_json"`
	CreatedByAdminID             int64  `json:"created_by_admin_id"`
	UpdatedByAdminID             int64  `json:"updated_by_admin_id"`
	CreatedAtMS                  int64  `json:"created_at_ms"`
	UpdatedAtMS                  int64  `json:"updated_at_ms"`
}

type trackRequest struct {
	Name              *string `json:"name"`
	Status            *string `json:"status"`
	SortOrder         *int32  `json:"sort_order"`
	DisplayConfigJSON *string `json:"display_config_json"`
}

type ruleRequest struct {
	RequiredValue         *int64  `json:"required_value"`
	Name                  *string `json:"name"`
	Status                *string `json:"status"`
	RewardResourceGroupID *int64  `json:"reward_resource_group_id"`
	SortOrder             *int32  `json:"sort_order"`
	LongBadgeResourceID   *int64  `json:"long_badge_resource_id"`
	AvatarFrameResourceID *int64  `json:"avatar_frame_resource_id"`
	ShortBadgeResourceID  *int64  `json:"short_badge_resource_id"`
	DisplayConfigJSON     *string `json:"display_config_json"`
}

type tierRequest struct {
	Track                        *string `json:"track"`
	MinLevel                     *int32  `json:"min_level"`
	MaxLevel                     *int32  `json:"max_level"`
	Name                         *string `json:"name"`
	DisplayAvatarFrameResourceID *int64  `json:"display_avatar_frame_resource_id"`
	DisplayBadgeResourceID       *int64  `json:"display_badge_resource_id"`
	RewardResourceGroupID        *int64  `json:"reward_resource_group_id"`
	Status                       *string `json:"status"`
	DisplayConfigJSON            *string `json:"display_config_json"`
}

type batchLevelConfigRequest struct {
	Rules []batchRuleRequest `json:"rules"`
	Tiers []batchTierRequest `json:"tiers"`
}

type batchRuleRequest struct {
	Track string `json:"track"`
	Level int32  `json:"level"`
	ruleRequest
}

type batchTierRequest struct {
	TierID int64 `json:"tier_id"`
	tierRequest
}

// ListLevelConfig 返回用户管理 > 等级配置页面需要的三条等级轨道完整配置。
func (h *Handler) ListLevelConfig(c *gin.Context) {
	track := normalizeTrack(c.Query("track"))
	status := normalizeStatusFilter(c.Query("status"))
	if track != "" && !validTrack(track) {
		response.BadRequest(c, "等级类型不正确")
		return
	}
	if status != "" && !validStatus(status) {
		response.BadRequest(c, "状态参数不正确")
		return
	}
	resp, err := h.activity.ListLevelConfig(c.Request.Context(), &activityv1.ListLevelConfigRequest{
		Meta:   h.meta(c),
		Track:  track,
		Status: status,
	})
	if err != nil {
		response.ServerError(c, "获取等级配置失败")
		return
	}
	response.OK(c, configFromProto(resp))
}

// UpdateTrack 更新 wealth/game/charm 三条固定等级轨道的基础展示配置。
func (h *Handler) UpdateTrack(c *gin.Context) {
	track := normalizeTrack(c.Param("track"))
	if !validTrack(track) {
		response.BadRequest(c, "等级类型不正确")
		return
	}
	var req trackRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "等级轨道参数不正确")
		return
	}
	current, ok, err := h.currentTrack(c, track)
	if err != nil {
		response.ServerError(c, "读取等级轨道失败")
		return
	}
	name := current.Name
	if req.Name != nil {
		name = strings.TrimSpace(*req.Name)
	}
	if name == "" {
		name = defaultTrackName(track)
	}
	status := current.Status
	if req.Status != nil {
		status = normalizeStatus(*req.Status)
	}
	if status == "" {
		status = statusActive
	}
	if !validStatus(status) {
		response.BadRequest(c, "状态参数不正确")
		return
	}
	sortOrder := current.SortOrder
	if !ok {
		sortOrder = defaultTrackSort(track)
	}
	if req.SortOrder != nil {
		sortOrder = *req.SortOrder
	}
	displayConfig := current.DisplayConfigJSON
	if req.DisplayConfigJSON != nil {
		displayConfig = strings.TrimSpace(*req.DisplayConfigJSON)
	}
	displayConfig, err = normalizeJSONObject(displayConfig)
	if err != nil {
		response.BadRequest(c, "展示配置 JSON 不正确")
		return
	}
	resp, err := h.activity.UpsertLevelTrack(c.Request.Context(), &activityv1.UpsertLevelTrackRequest{
		Meta:              h.meta(c),
		Track:             track,
		Name:              name,
		Status:            status,
		SortOrder:         sortOrder,
		DisplayConfigJson: displayConfig,
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	item := trackFromProto(resp.GetTrack(), nil, nil)
	h.auditLog(c, "update-level-track", "growth_level_tracks", track, fmt.Sprintf("status=%s", item.Status))
	response.OK(c, item)
}

// UpsertRule 配置某条轨道某一级的升级阈值、等级奖励资源组和每级长徽章素材。
func (h *Handler) UpsertRule(c *gin.Context) {
	track := normalizeTrack(c.Param("track"))
	level, ok := parseNonNegativeInt32(c.Param("level"))
	if !ok || !validTrack(track) {
		response.BadRequest(c, "等级参数不正确")
		return
	}
	var req ruleRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "等级规则参数不正确")
		return
	}
	config, err := h.loadConfig(c, track, "")
	if err != nil {
		response.ServerError(c, "读取等级配置失败")
		return
	}
	current, exists := findRule(config.GetRules(), track, level)
	trackName := defaultTrackName(track)
	if currentTrack, found := findTrack(config.GetTracks(), track); found {
		trackName = currentTrack.GetName()
	}

	requiredValue := current.GetRequiredValue()
	if req.RequiredValue != nil {
		requiredValue = *req.RequiredValue
	}
	if requiredValue < 0 {
		response.BadRequest(c, "升级所需数值不能小于 0")
		return
	}
	name := current.GetName()
	if req.Name != nil {
		name = strings.TrimSpace(*req.Name)
	}
	if name == "" {
		name = fmt.Sprintf("%s Lv.%d", trackName, level)
	}
	status := current.GetStatus()
	if req.Status != nil {
		status = normalizeStatus(*req.Status)
	}
	if status == "" {
		status = statusActive
	}
	if !validStatus(status) {
		response.BadRequest(c, "状态参数不正确")
		return
	}
	rewardResourceGroupID := current.GetRewardResourceGroupId()
	if req.RewardResourceGroupID != nil {
		rewardResourceGroupID = *req.RewardResourceGroupID
	}
	if rewardResourceGroupID < 0 {
		response.BadRequest(c, "奖励资源组 ID 不正确")
		return
	}
	sortOrder := current.GetSortOrder()
	if !exists {
		sortOrder = level
	}
	if req.SortOrder != nil {
		sortOrder = *req.SortOrder
	}
	displayConfig := current.GetDisplayConfigJson()
	if req.DisplayConfigJSON != nil {
		displayConfig = strings.TrimSpace(*req.DisplayConfigJSON)
	}
	displayConfig, err = mergeRuleDisplayResources(displayConfig, req.LongBadgeResourceID, req.AvatarFrameResourceID, req.ShortBadgeResourceID)
	if err != nil {
		response.BadRequest(c, "展示配置 JSON 不正确")
		return
	}
	if badgeID := longBadgeResourceID(displayConfig); badgeID > 0 {
		if err := h.validateLevelLongBadgeResource(c, track, badgeID); err != nil {
			response.BadRequest(c, err.Error())
			return
		}
	}
	if avatarFrameID := avatarFrameResourceID(displayConfig); avatarFrameID > 0 {
		if err := h.validateLevelAvatarFrameResource(c, track, avatarFrameID); err != nil {
			response.BadRequest(c, err.Error())
			return
		}
	}
	if shortBadgeID := shortBadgeResourceID(displayConfig); shortBadgeID > 0 {
		if err := h.validateLevelShortBadgeResource(c, track, shortBadgeID); err != nil {
			response.BadRequest(c, err.Error())
			return
		}
	}
	resp, err := h.activity.UpsertLevelRule(c.Request.Context(), &activityv1.UpsertLevelRuleRequest{
		Meta:                  h.meta(c),
		Track:                 track,
		Level:                 level,
		RequiredValue:         requiredValue,
		Name:                  name,
		Status:                status,
		RewardResourceGroupId: rewardResourceGroupID,
		SortOrder:             sortOrder,
		DisplayConfigJson:     displayConfig,
		OperatorAdminId:       int64(middleware.CurrentUserID(c)),
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	item := ruleFromProto(resp.GetRule())
	h.auditLog(c, "upsert-level-rule", "growth_level_rules", fmt.Sprintf("%s:%d", track, level), fmt.Sprintf("required_value=%d long_badge_resource_id=%d avatar_frame_resource_id=%d short_badge_resource_id=%d", item.RequiredValue, item.LongBadgeResourceID, item.AvatarFrameResourceID, item.ShortBadgeResourceID))
	response.OK(c, item)
}

func (h *Handler) CreateTier(c *gin.Context) {
	h.upsertTier(c, 0)
}

func (h *Handler) UpdateTier(c *gin.Context) {
	tierID, ok := parsePositiveInt64(c.Param("tier_id"))
	if !ok {
		response.BadRequest(c, "等级段 ID 不正确")
		return
	}
	h.upsertTier(c, tierID)
}

// BatchUpsertLevelConfig 把上下两张配置表的一次编辑合并提交；任一行失败时 activity-service 会整体回滚。
func (h *Handler) BatchUpsertLevelConfig(c *gin.Context) {
	var req batchLevelConfigRequest
	if err := c.ShouldBindJSON(&req); err != nil || (len(req.Rules) == 0 && len(req.Tiers) == 0) || len(req.Rules)+len(req.Tiers) > 500 {
		response.BadRequest(c, "批量等级配置参数不正确")
		return
	}
	config, err := h.loadConfig(c, "", "")
	if err != nil {
		response.ServerError(c, "读取等级配置失败")
		return
	}
	grpcRequest := &activityv1.BatchUpsertLevelConfigRequest{
		Meta: h.meta(c), Rules: make([]*activityv1.LevelRule, 0, len(req.Rules)), Tiers: make([]*activityv1.LevelTier, 0, len(req.Tiers)),
		OperatorAdminId: int64(middleware.CurrentUserID(c)),
	}
	validatedResources := make(map[string]error)
	for _, input := range req.Rules {
		track := normalizeTrack(input.Track)
		current, exists := findRule(config.GetRules(), track, input.Level)
		if !validTrack(track) || input.Level < 0 || !exists {
			response.BadRequest(c, "批量编辑包含不存在的等级规则")
			return
		}
		item, err := h.batchRuleProto(c, track, input.Level, current, input.ruleRequest, validatedResources)
		if err != nil {
			response.BadRequest(c, err.Error())
			return
		}
		grpcRequest.Rules = append(grpcRequest.Rules, item)
	}
	for _, input := range req.Tiers {
		current, exists := findTier(config.GetTiers(), input.TierID)
		if input.TierID <= 0 || !exists {
			response.BadRequest(c, "批量编辑包含不存在的等级层")
			return
		}
		item, err := batchTierProto(input.TierID, current, input.tierRequest)
		if err != nil {
			response.BadRequest(c, err.Error())
			return
		}
		grpcRequest.Tiers = append(grpcRequest.Tiers, item)
	}
	resp, err := h.activity.BatchUpsertLevelConfig(c.Request.Context(), grpcRequest)
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	h.auditLog(c, "batch-upsert-level-config", "growth_level_config", appctx.FromContext(c.Request.Context()), fmt.Sprintf("rules=%d tiers=%d", len(req.Rules), len(req.Tiers)))
	response.OK(c, map[string]any{"rules": rulesFromProto(resp.GetRules()), "tiers": tiersFromProto(resp.GetTiers()), "server_time_ms": resp.GetServerTimeMs()})
}

func (h *Handler) batchRuleProto(c *gin.Context, track string, level int32, current *activityv1.LevelRule, req ruleRequest, validatedResources map[string]error) (*activityv1.LevelRule, error) {
	requiredValue, name, status := current.GetRequiredValue(), current.GetName(), current.GetStatus()
	rewardResourceGroupID, sortOrder := current.GetRewardResourceGroupId(), current.GetSortOrder()
	if req.RequiredValue != nil {
		requiredValue = *req.RequiredValue
	}
	if req.Name != nil {
		name = strings.TrimSpace(*req.Name)
	}
	if req.Status != nil {
		status = normalizeStatus(*req.Status)
	}
	if req.RewardResourceGroupID != nil {
		rewardResourceGroupID = *req.RewardResourceGroupID
	}
	if req.SortOrder != nil {
		sortOrder = *req.SortOrder
	}
	if requiredValue < 0 || name == "" || !validStatus(status) || rewardResourceGroupID < 0 {
		return nil, fmt.Errorf("等级规则参数不正确")
	}
	displayConfig := current.GetDisplayConfigJson()
	if req.DisplayConfigJSON != nil {
		displayConfig = strings.TrimSpace(*req.DisplayConfigJSON)
	}
	displayConfig, err := mergeRuleDisplayResources(displayConfig, req.LongBadgeResourceID, req.AvatarFrameResourceID, req.ShortBadgeResourceID)
	if err != nil {
		return nil, fmt.Errorf("展示配置 JSON 不正确")
	}
	if err := h.validateBatchRuleResources(c, track, current.GetDisplayConfigJson(), displayConfig, req, validatedResources); err != nil {
		return nil, err
	}
	return &activityv1.LevelRule{Track: track, Level: level, RequiredValue: requiredValue, Name: name, Status: status, RewardResourceGroupId: rewardResourceGroupID, SortOrder: sortOrder, DisplayConfigJson: displayConfig}, nil
}

func (h *Handler) validateBatchRuleResources(c *gin.Context, track string, currentDisplayConfig string, nextDisplayConfig string, req ruleRequest, cache map[string]error) error {
	checks := []struct {
		changed bool
		kind    string
		id      int64
		check   func(*gin.Context, string, int64) error
	}{
		{changed: req.LongBadgeResourceID != nil && longBadgeResourceID(currentDisplayConfig) != longBadgeResourceID(nextDisplayConfig), kind: "long-badge", id: longBadgeResourceID(nextDisplayConfig), check: h.validateLevelLongBadgeResource},
		{changed: req.AvatarFrameResourceID != nil && avatarFrameResourceID(currentDisplayConfig) != avatarFrameResourceID(nextDisplayConfig), kind: "avatar-frame", id: avatarFrameResourceID(nextDisplayConfig), check: h.validateLevelAvatarFrameResource},
		{changed: req.ShortBadgeResourceID != nil && shortBadgeResourceID(currentDisplayConfig) != shortBadgeResourceID(nextDisplayConfig), kind: "short-badge", id: shortBadgeResourceID(nextDisplayConfig), check: h.validateLevelShortBadgeResource},
	}
	for _, item := range checks {
		if !item.changed || item.id <= 0 {
			continue
		}
		key := fmt.Sprintf("%s:%s:%d", item.kind, track, item.id)
		if cached, ok := cache[key]; ok {
			if cached != nil {
				return cached
			}
			continue
		}
		err := item.check(c, track, item.id)
		cache[key] = err
		if err != nil {
			return err
		}
	}
	return nil
}

func batchTierProto(tierID int64, current *activityv1.LevelTier, req tierRequest) (*activityv1.LevelTier, error) {
	track, minLevel, maxLevel, name, status := current.GetTrack(), current.GetMinLevel(), current.GetMaxLevel(), current.GetName(), current.GetStatus()
	avatarFrameID, badgeID, rewardGroupID := current.GetDisplayAvatarFrameResourceId(), current.GetDisplayBadgeResourceId(), current.GetRewardResourceGroupId()
	if req.Track != nil {
		track = normalizeTrack(*req.Track)
	}
	if req.MinLevel != nil {
		minLevel = *req.MinLevel
	}
	if req.MaxLevel != nil {
		maxLevel = *req.MaxLevel
	}
	if req.Name != nil {
		name = strings.TrimSpace(*req.Name)
	}
	if req.Status != nil {
		status = normalizeStatus(*req.Status)
	}
	if req.DisplayAvatarFrameResourceID != nil {
		avatarFrameID = *req.DisplayAvatarFrameResourceID
	}
	if req.DisplayBadgeResourceID != nil {
		badgeID = *req.DisplayBadgeResourceID
	}
	if req.RewardResourceGroupID != nil {
		rewardGroupID = *req.RewardResourceGroupID
	}
	if !validTrack(track) || minLevel < 0 || maxLevel < minLevel || name == "" || !validStatus(status) || avatarFrameID < 0 || badgeID < 0 || rewardGroupID < 0 {
		return nil, fmt.Errorf("等级层参数不正确")
	}
	displayConfig := current.GetDisplayConfigJson()
	if req.DisplayConfigJSON != nil {
		displayConfig = strings.TrimSpace(*req.DisplayConfigJSON)
	}
	displayConfig, err := normalizeJSONObject(displayConfig)
	if err != nil {
		return nil, fmt.Errorf("展示配置 JSON 不正确")
	}
	return &activityv1.LevelTier{TierId: tierID, Track: track, MinLevel: minLevel, MaxLevel: maxLevel, Name: name, DisplayAvatarFrameResourceId: avatarFrameID, DisplayBadgeResourceId: badgeID, RewardResourceGroupId: rewardGroupID, Status: status, DisplayConfigJson: displayConfig}, nil
}

// upsertTier 复用已有等级段配置能力，保持 1-10、11-20 这类展示徽章仍由 activity-service 拥有。
func (h *Handler) upsertTier(c *gin.Context, tierID int64) {
	var req tierRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "等级段参数不正确")
		return
	}
	var current *activityv1.LevelTier
	if tierID > 0 {
		config, err := h.loadConfig(c, "", "")
		if err != nil {
			response.ServerError(c, "读取等级段配置失败")
			return
		}
		var found bool
		current, found = findTier(config.GetTiers(), tierID)
		if !found {
			response.NotFound(c, "等级段不存在")
			return
		}
	}
	track := ""
	if current != nil {
		track = current.GetTrack()
	}
	if req.Track != nil {
		track = normalizeTrack(*req.Track)
	}
	if !validTrack(track) {
		response.BadRequest(c, "等级类型不正确")
		return
	}
	minLevel := int32(0)
	if current != nil {
		minLevel = current.GetMinLevel()
	}
	if req.MinLevel != nil {
		minLevel = *req.MinLevel
	}
	maxLevel := int32(0)
	if current != nil {
		maxLevel = current.GetMaxLevel()
	}
	if req.MaxLevel != nil {
		maxLevel = *req.MaxLevel
	}
	if minLevel < 0 || maxLevel < minLevel {
		response.BadRequest(c, "等级段范围不正确")
		return
	}
	name := ""
	if current != nil {
		name = current.GetName()
	}
	if req.Name != nil {
		name = strings.TrimSpace(*req.Name)
	}
	if name == "" {
		name = fmt.Sprintf("%s Lv.%d-%d", defaultTrackName(track), minLevel, maxLevel)
	}
	status := statusActive
	if current != nil {
		status = current.GetStatus()
	}
	if req.Status != nil {
		status = normalizeStatus(*req.Status)
	}
	if !validStatus(status) {
		response.BadRequest(c, "状态参数不正确")
		return
	}
	avatarFrameResourceID := int64(0)
	if current != nil {
		avatarFrameResourceID = current.GetDisplayAvatarFrameResourceId()
	}
	if req.DisplayAvatarFrameResourceID != nil {
		avatarFrameResourceID = *req.DisplayAvatarFrameResourceID
	}
	badgeResourceID := int64(0)
	if current != nil {
		badgeResourceID = current.GetDisplayBadgeResourceId()
	}
	if req.DisplayBadgeResourceID != nil {
		badgeResourceID = *req.DisplayBadgeResourceID
	}
	rewardResourceGroupID := int64(0)
	if current != nil {
		rewardResourceGroupID = current.GetRewardResourceGroupId()
	}
	if req.RewardResourceGroupID != nil {
		rewardResourceGroupID = *req.RewardResourceGroupID
	}
	if avatarFrameResourceID < 0 || badgeResourceID < 0 || rewardResourceGroupID < 0 {
		response.BadRequest(c, "等级段资源 ID 不正确")
		return
	}
	displayConfig := "{}"
	if current != nil {
		displayConfig = current.GetDisplayConfigJson()
	}
	if req.DisplayConfigJSON != nil {
		displayConfig = strings.TrimSpace(*req.DisplayConfigJSON)
	}
	var err error
	displayConfig, err = normalizeJSONObject(displayConfig)
	if err != nil {
		response.BadRequest(c, "展示配置 JSON 不正确")
		return
	}
	resp, err := h.activity.UpsertLevelTier(c.Request.Context(), &activityv1.UpsertLevelTierRequest{
		Meta:                         h.meta(c),
		TierId:                       tierID,
		Track:                        track,
		MinLevel:                     minLevel,
		MaxLevel:                     maxLevel,
		Name:                         name,
		DisplayAvatarFrameResourceId: avatarFrameResourceID,
		DisplayBadgeResourceId:       badgeResourceID,
		RewardResourceGroupId:        rewardResourceGroupID,
		Status:                       status,
		DisplayConfigJson:            displayConfig,
		OperatorAdminId:              int64(middleware.CurrentUserID(c)),
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	item := tierFromProto(resp.GetTier())
	h.auditLog(c, "upsert-level-tier", "growth_level_tiers", strconv.FormatInt(item.TierID, 10), fmt.Sprintf("%s:%d-%d", item.Track, item.MinLevel, item.MaxLevel))
	response.OK(c, item)
}

func (h *Handler) loadConfig(c *gin.Context, track string, status string) (*activityv1.ListLevelConfigResponse, error) {
	return h.activity.ListLevelConfig(c.Request.Context(), &activityv1.ListLevelConfigRequest{
		Meta:   h.meta(c),
		Track:  track,
		Status: status,
	})
}

func (h *Handler) validateLevelLongBadgeResource(c *gin.Context, track string, resourceID int64) error {
	if resourceID <= 0 {
		return nil
	}
	if h.wallet == nil {
		return fmt.Errorf("资源服务不可用")
	}
	resp, err := h.wallet.GetResource(c.Request.Context(), &walletv1.GetResourceRequest{
		RequestId:  middleware.CurrentRequestID(c),
		AppCode:    appctx.FromContext(c.Request.Context()),
		ResourceId: resourceID,
	})
	if err != nil {
		return fmt.Errorf("等级徽章资源不存在")
	}
	resource := resp.GetResource()
	if resource.GetResourceType() != "badge" {
		return fmt.Errorf("等级长徽章必须选择徽章资源")
	}
	metadata := levelBadgeMetadataFromJSON(resource.GetMetadataJson())
	if metadata.BadgeForm != "strip" {
		return fmt.Errorf("等级长徽章必须选择长徽章素材")
	}
	if metadata.BadgeKind != "level" {
		return fmt.Errorf("等级长徽章必须选择等级徽章素材")
	}
	if metadata.LevelTrack != track {
		return fmt.Errorf("等级长徽章所属等级不匹配")
	}
	return nil
}

func (h *Handler) validateLevelAvatarFrameResource(c *gin.Context, track string, resourceID int64) error {
	if resourceID <= 0 {
		return nil
	}
	if h.wallet == nil {
		return fmt.Errorf("资源服务不可用")
	}
	resp, err := h.wallet.GetResource(c.Request.Context(), &walletv1.GetResourceRequest{
		RequestId:  middleware.CurrentRequestID(c),
		AppCode:    appctx.FromContext(c.Request.Context()),
		ResourceId: resourceID,
	})
	if err != nil {
		return fmt.Errorf("等级头像框资源不存在")
	}
	resource := resp.GetResource()
	if resource.GetResourceType() != "avatar_frame" {
		return fmt.Errorf("等级头像框必须选择头像框资源")
	}
	metadata := levelAvatarFrameMetadataFromJSON(resource.GetMetadataJson())
	if metadata.AvatarFrameKind != "level" {
		return fmt.Errorf("等级头像框必须选择等级头像框素材")
	}
	if metadata.LevelTrack != track {
		return fmt.Errorf("等级头像框所属等级不匹配")
	}
	return nil
}

func (h *Handler) validateLevelShortBadgeResource(c *gin.Context, track string, resourceID int64) error {
	if resourceID <= 0 {
		return nil
	}
	if h.wallet == nil {
		return fmt.Errorf("资源服务不可用")
	}
	resp, err := h.wallet.GetResource(c.Request.Context(), &walletv1.GetResourceRequest{
		RequestId:  middleware.CurrentRequestID(c),
		AppCode:    appctx.FromContext(c.Request.Context()),
		ResourceId: resourceID,
	})
	if err != nil {
		return fmt.Errorf("等级短徽章资源不存在")
	}
	resource := resp.GetResource()
	if resource.GetResourceType() != "badge" {
		return fmt.Errorf("等级短徽章必须选择徽章资源")
	}
	metadata := levelBadgeMetadataFromJSON(resource.GetMetadataJson())
	if metadata.BadgeForm != "tile" {
		return fmt.Errorf("等级短徽章必须选择短徽章素材")
	}
	if metadata.BadgeKind != "level" {
		return fmt.Errorf("等级短徽章必须选择等级徽章素材")
	}
	if metadata.LevelTrack != track {
		return fmt.Errorf("等级短徽章所属等级不匹配")
	}
	return nil
}

func (h *Handler) currentTrack(c *gin.Context, track string) (trackDTO, bool, error) {
	config, err := h.loadConfig(c, track, "")
	if err != nil {
		return trackDTO{}, false, err
	}
	item, ok := findTrack(config.GetTracks(), track)
	if !ok {
		return trackDTO{}, false, nil
	}
	return trackFromProto(item, nil, nil), true, nil
}

func (h *Handler) meta(c *gin.Context) *activityv1.RequestMeta {
	return &activityv1.RequestMeta{
		RequestId: middleware.CurrentRequestID(c),
		Caller:    "admin-server",
		AppCode:   appctx.FromContext(c.Request.Context()),
		SentAtMs:  time.Now().UnixMilli(),
	}
}

func (h *Handler) auditLog(c *gin.Context, action string, resource string, resourceID string, detail string) {
	shared.OperationLogWithResourceID(c, h.audit, action, resource, resourceID, "success", detail)
}

func configFromProto(resp *activityv1.ListLevelConfigResponse) levelConfigDTO {
	if resp == nil {
		return levelConfigDTO{Tracks: []trackDTO{}}
	}
	rulesByTrack := make(map[string][]ruleDTO)
	for _, rule := range resp.GetRules() {
		item := ruleFromProto(rule)
		rulesByTrack[item.Track] = append(rulesByTrack[item.Track], item)
	}
	tiersByTrack := make(map[string][]tierDTO)
	for _, tier := range resp.GetTiers() {
		item := tierFromProto(tier)
		tiersByTrack[item.Track] = append(tiersByTrack[item.Track], item)
	}
	tracks := make([]trackDTO, 0, len(resp.GetTracks()))
	for _, track := range resp.GetTracks() {
		code := track.GetTrack()
		tracks = append(tracks, trackFromProto(track, rulesByTrack[code], tiersByTrack[code]))
	}
	return levelConfigDTO{Tracks: tracks, ServerTimeMS: resp.GetServerTimeMs()}
}

func trackFromProto(item *activityv1.LevelTrack, rules []ruleDTO, tiers []tierDTO) trackDTO {
	if item == nil {
		return trackDTO{Rules: []ruleDTO{}, Tiers: []tierDTO{}}
	}
	if rules == nil {
		rules = []ruleDTO{}
	}
	if tiers == nil {
		tiers = []tierDTO{}
	}
	return trackDTO{
		Track:             item.GetTrack(),
		Name:              item.GetName(),
		Status:            item.GetStatus(),
		SortOrder:         item.GetSortOrder(),
		DisplayConfigJSON: item.GetDisplayConfigJson(),
		Rules:             rules,
		Tiers:             tiers,
		CreatedAtMS:       item.GetCreatedAtMs(),
		UpdatedAtMS:       item.GetUpdatedAtMs(),
	}
}

func ruleFromProto(item *activityv1.LevelRule) ruleDTO {
	if item == nil {
		return ruleDTO{}
	}
	return ruleDTO{
		Track:                 item.GetTrack(),
		Level:                 item.GetLevel(),
		RequiredValue:         item.GetRequiredValue(),
		Name:                  item.GetName(),
		Status:                item.GetStatus(),
		RewardResourceGroupID: item.GetRewardResourceGroupId(),
		SortOrder:             item.GetSortOrder(),
		LongBadgeResourceID:   longBadgeResourceID(item.GetDisplayConfigJson()),
		AvatarFrameResourceID: avatarFrameResourceID(item.GetDisplayConfigJson()),
		ShortBadgeResourceID:  shortBadgeResourceID(item.GetDisplayConfigJson()),
		DisplayConfigJSON:     item.GetDisplayConfigJson(),
		CreatedByAdminID:      item.GetCreatedByAdminId(),
		UpdatedByAdminID:      item.GetUpdatedByAdminId(),
		CreatedAtMS:           item.GetCreatedAtMs(),
		UpdatedAtMS:           item.GetUpdatedAtMs(),
	}
}

func tierFromProto(item *activityv1.LevelTier) tierDTO {
	if item == nil {
		return tierDTO{}
	}
	return tierDTO{
		TierID:                       item.GetTierId(),
		Track:                        item.GetTrack(),
		MinLevel:                     item.GetMinLevel(),
		MaxLevel:                     item.GetMaxLevel(),
		Name:                         item.GetName(),
		DisplayAvatarFrameResourceID: item.GetDisplayAvatarFrameResourceId(),
		DisplayBadgeResourceID:       item.GetDisplayBadgeResourceId(),
		RewardResourceGroupID:        item.GetRewardResourceGroupId(),
		Status:                       item.GetStatus(),
		DisplayConfigJSON:            item.GetDisplayConfigJson(),
		CreatedByAdminID:             item.GetCreatedByAdminId(),
		UpdatedByAdminID:             item.GetUpdatedByAdminId(),
		CreatedAtMS:                  item.GetCreatedAtMs(),
		UpdatedAtMS:                  item.GetUpdatedAtMs(),
	}
}

func rulesFromProto(items []*activityv1.LevelRule) []ruleDTO {
	result := make([]ruleDTO, 0, len(items))
	for _, item := range items {
		result = append(result, ruleFromProto(item))
	}
	return result
}

func tiersFromProto(items []*activityv1.LevelTier) []tierDTO {
	result := make([]tierDTO, 0, len(items))
	for _, item := range items {
		result = append(result, tierFromProto(item))
	}
	return result
}

func findTrack(items []*activityv1.LevelTrack, track string) (*activityv1.LevelTrack, bool) {
	for _, item := range items {
		if item.GetTrack() == track {
			return item, true
		}
	}
	return nil, false
}

func findRule(items []*activityv1.LevelRule, track string, level int32) (*activityv1.LevelRule, bool) {
	for _, item := range items {
		if item.GetTrack() == track && item.GetLevel() == level {
			return item, true
		}
	}
	return nil, false
}

func findTier(items []*activityv1.LevelTier, tierID int64) (*activityv1.LevelTier, bool) {
	for _, item := range items {
		if item.GetTierId() == tierID {
			return item, true
		}
	}
	return nil, false
}

func mergeRuleDisplayResources(raw string, longBadgeResourceID *int64, avatarFrameResourceID *int64, shortBadgeResourceID *int64) (string, error) {
	payload, err := objectFromJSON(raw)
	if err != nil {
		return "", err
	}
	if longBadgeResourceID != nil {
		if *longBadgeResourceID < 0 {
			return "", fmt.Errorf("long badge resource id is invalid")
		}
		// 长徽章是每一级展示素材；沿用 display_config_json 可避免为展示扩展反复改表。
		payload[longBadgeResourceIDKey] = *longBadgeResourceID
	}
	if avatarFrameResourceID != nil {
		if *avatarFrameResourceID < 0 {
			return "", fmt.Errorf("avatar frame resource id is invalid")
		}
		// 等级头像框也是每一级独立物料；和长徽章共用 display_config_json，避免为等级展示资源反复扩表。
		payload[avatarFrameResourceIDKey] = *avatarFrameResourceID
	}
	if shortBadgeResourceID != nil {
		if *shortBadgeResourceID < 0 {
			return "", fmt.Errorf("short badge resource id is invalid")
		}
		// 短徽章是每一级发放给用户的等级奖励；放在 display_config_json 内保持规则表结构稳定。
		payload[shortBadgeResourceIDKey] = *shortBadgeResourceID
	}
	data, err := json.Marshal(payload)
	if err != nil {
		return "", err
	}
	return string(data), nil
}

func normalizeJSONObject(raw string) (string, error) {
	payload, err := objectFromJSON(raw)
	if err != nil {
		return "", err
	}
	data, err := json.Marshal(payload)
	if err != nil {
		return "", err
	}
	return string(data), nil
}

func objectFromJSON(raw string) (map[string]any, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		raw = "{}"
	}
	decoder := json.NewDecoder(bytes.NewBufferString(raw))
	decoder.UseNumber()
	var payload map[string]any
	if err := decoder.Decode(&payload); err != nil {
		return nil, err
	}
	if payload == nil {
		payload = map[string]any{}
	}
	return payload, nil
}

func longBadgeResourceID(raw string) int64 {
	return positiveInt64FromDisplayConfig(raw, longBadgeResourceIDKey)
}

func avatarFrameResourceID(raw string) int64 {
	return positiveInt64FromDisplayConfig(raw, avatarFrameResourceIDKey)
}

func shortBadgeResourceID(raw string) int64 {
	return positiveInt64FromDisplayConfig(raw, shortBadgeResourceIDKey)
}

func positiveInt64FromDisplayConfig(raw string, key string) int64 {
	payload, err := objectFromJSON(raw)
	if err != nil {
		return 0
	}
	value, ok := payload[key]
	if !ok {
		return 0
	}
	switch typed := value.(type) {
	case json.Number:
		parsed, err := typed.Int64()
		if err == nil && parsed > 0 {
			return parsed
		}
	case float64:
		if typed > 0 {
			return int64(typed)
		}
	case string:
		parsed, err := strconv.ParseInt(strings.TrimSpace(typed), 10, 64)
		if err == nil && parsed > 0 {
			return parsed
		}
	}
	return 0
}

type levelBadgeMetadata struct {
	BadgeForm  string `json:"badge_form"`
	BadgeKind  string `json:"badge_kind"`
	LevelTrack string `json:"level_track"`
}

type levelAvatarFrameMetadata struct {
	AvatarFrameKind string `json:"avatar_frame_kind"`
	LevelTrack      string `json:"level_track"`
}

func levelBadgeMetadataFromJSON(raw string) levelBadgeMetadata {
	payload := levelBadgeMetadata{}
	if err := json.Unmarshal([]byte(strings.TrimSpace(raw)), &payload); err != nil {
		return levelBadgeMetadata{}
	}
	payload.BadgeForm = strings.ToLower(strings.TrimSpace(payload.BadgeForm))
	payload.BadgeKind = strings.ToLower(strings.TrimSpace(payload.BadgeKind))
	payload.LevelTrack = normalizeTrack(payload.LevelTrack)
	return payload
}

func levelAvatarFrameMetadataFromJSON(raw string) levelAvatarFrameMetadata {
	payload := levelAvatarFrameMetadata{}
	if err := json.Unmarshal([]byte(strings.TrimSpace(raw)), &payload); err != nil {
		return levelAvatarFrameMetadata{}
	}
	payload.AvatarFrameKind = strings.ToLower(strings.TrimSpace(payload.AvatarFrameKind))
	payload.LevelTrack = normalizeTrack(payload.LevelTrack)
	return payload
}

func parseNonNegativeInt32(raw string) (int32, bool) {
	value, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 32)
	if err != nil || value < 0 {
		return 0, false
	}
	return int32(value), true
}

func parsePositiveInt64(raw string) (int64, bool) {
	value, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 64)
	if err != nil || value <= 0 {
		return 0, false
	}
	return value, true
}

func normalizeTrack(value string) string {
	return strings.ToLower(strings.TrimSpace(value))
}

func normalizeStatus(value string) string {
	return strings.ToLower(strings.TrimSpace(value))
}

func normalizeStatusFilter(value string) string {
	value = normalizeStatus(value)
	if value == "all" {
		return ""
	}
	return value
}

func validTrack(value string) bool {
	switch value {
	case "wealth", "game", "charm":
		return true
	default:
		return false
	}
}

func validStatus(value string) bool {
	return value == statusActive || value == statusDisabled
}

func defaultTrackName(track string) string {
	switch track {
	case "wealth":
		return "财富等级"
	case "game":
		return "游戏等级"
	case "charm":
		return "魅力等级"
	default:
		return "等级"
	}
}

func defaultTrackSort(track string) int32 {
	switch track {
	case "wealth":
		return 10
	case "game":
		return 20
	case "charm":
		return 30
	default:
		return 0
	}
}
