package userapi

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"strings"

	activityv1 "hyapp.local/api/proto/activity/v1"
	walletv1 "hyapp.local/api/proto/wallet/v1"
	"hyapp/pkg/appcode"
	"hyapp/services/gateway-service/internal/auth"
	"hyapp/services/gateway-service/internal/transport/http/httpkit"
	"hyapp/services/gateway-service/internal/transport/http/resourceview"
)

const (
	// /appearance 只暴露客户端能直接佩戴或展示的装扮维度；类型值保持和 wallet resource_type 一致。
	appearanceResourceAvatarFrame      = "avatar_frame"
	appearanceResourceProfileCard      = "profile_card"
	appearanceResourceVehicle          = "vehicle"
	appearanceResourceBadge            = "badge"
	appearanceResourceMicSeatIcon      = "mic_seat_icon"
	appearanceResourceMicSeatAnimation = "mic_seat_animation"
)

var errAppearanceUpstream = errors.New("upstream service error")

type appearanceData struct {
	UserID           string              `json:"user_id"`
	AvatarFrame      map[string]any      `json:"avatar_frame"`
	ProfileCard      map[string]any      `json:"profile_card"`
	Vehicle          map[string]any      `json:"vehicle"`
	MicSeatIcon      map[string]any      `json:"mic_seat_icon"`
	MicSeatAnimation map[string]any      `json:"mic_seat_animation"`
	Level            map[string]any      `json:"level"`
	LevelBadges      map[string]any      `json:"level_badges"`
	EquippedBadges   []map[string]any    `json:"equipped_badges"`
	Badges           []map[string]any    `json:"badges"`
	BadgeProfile     appearanceBadgeData `json:"badge_profile"`
	Resources        []map[string]any    `json:"resources"`
	ServerTimeMS     int64               `json:"server_time_ms"`
}

type appearanceBadgeData struct {
	StripBadges       []map[string]any `json:"strip_badges"`
	ProfileTileBadges []map[string]any `json:"profile_tile_badges"`
	HonorBadges       []map[string]any `json:"honor_badges"`
	ServerTimeMS      int64            `json:"server_time_ms"`
}

func (h *Handler) getMyAppearance(writer http.ResponseWriter, request *http.Request) {
	h.writeAppearance(writer, request, auth.UserIDFromContext(request.Context()))
}

func (h *Handler) getUserAppearance(writer http.ResponseWriter, request *http.Request) {
	userID, ok := httpkit.PositiveInt64PathValue(request, "user_id")
	if !ok {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	h.writeAppearance(writer, request, userID)
}

func (h *Handler) writeAppearance(writer http.ResponseWriter, request *http.Request, userID int64) {
	if userID <= 0 {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	// wallet 是装扮权益和当前佩戴的事实源；缺 wallet 时 appearance 不能返回可信数据。
	if h.walletClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	data, err := h.appearanceForUser(request, userID)
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	httpkit.WriteOK(writer, request, data)
}

func (h *Handler) appearanceForUser(request *http.Request, userID int64) (appearanceData, error) {
	// 先初始化为空对象/数组，保证客户端不需要处理 null 和字段缺失两套形态。
	data := appearanceData{
		UserID:           strconv.FormatInt(userID, 10),
		AvatarFrame:      map[string]any{},
		ProfileCard:      map[string]any{},
		Vehicle:          map[string]any{},
		MicSeatIcon:      map[string]any{},
		MicSeatAnimation: map[string]any{},
		Level:            defaultAppearanceLevelDisplay(),
		LevelBadges:      map[string]any{},
		EquippedBadges:   []map[string]any{},
		Badges:           []map[string]any{},
		Resources:        []map[string]any{},
	}
	// 当前佩戴只走 BatchGetUserEquippedResources：
	// 1. wallet 内部会过滤过期佩戴；
	// 2. 后续扩展批量 appearance 时可以复用同一 RPC，不引入 N+1。
	equipped, err := h.walletClient.BatchGetUserEquippedResources(request.Context(), &walletv1.BatchGetUserEquippedResourcesRequest{
		RequestId: httpkit.RequestIDFromContext(request.Context()),
		AppCode:   appcode.FromContext(request.Context()),
		UserIds:   []int64{userID},
		// 图标和声波同时查询并分别投影，客户端替换麦位中心图标时不会覆盖 Voice Wave。
		ResourceTypes: []string{appearanceResourceAvatarFrame, appearanceResourceProfileCard, appearanceResourceVehicle, appearanceResourceBadge, appearanceResourceMicSeatIcon, appearanceResourceMicSeatAnimation},
	})
	if err != nil {
		return data, err
	}
	for _, user := range equipped.GetUsers() {
		if user.GetUserId() != userID {
			// batch RPC 按用户分组返回；这里防御上游误返回其他用户数据。
			continue
		}
		for _, item := range user.GetResources() {
			// Resources 保留完整佩戴列表，便于客户端以后按 resource_type 自行扩展展示位。
			resource := appearanceResourceFromEntitlement(item)
			data.Resources = append(data.Resources, resource)
			switch item.GetResource().GetResourceType() {
			case appearanceResourceAvatarFrame:
				data.AvatarFrame = resource
			case appearanceResourceProfileCard:
				data.ProfileCard = resource
			case appearanceResourceVehicle:
				data.Vehicle = resource
			case appearanceResourceBadge:
				data.EquippedBadges = append(data.EquippedBadges, resource)
			case appearanceResourceMicSeatIcon:
				data.MicSeatIcon = resource
			case appearanceResourceMicSeatAnimation:
				data.MicSeatAnimation = resource
			}
		}
	}

	// badge_display 是 activity-service 的投影；wallet 只提供 badge 资源素材和权益事实。
	badgeProfile, err := h.badgeProfileForUser(request, userID)
	if err != nil {
		return data, err
	}
	data.BadgeProfile = badgeProfile
	// 顶层 badges 给高频展示位直接消费；完整 profile 仍保留在 badge_profile 中。
	data.Badges = badgeProfile.StripBadges
	level, levelBadges := h.levelDisplayForUser(request, userID)
	data.Level = level
	data.LevelBadges = levelBadges
	h.fillAppearanceLevelBadgeEntitlementFallback(request, userID, data.Level, data.LevelBadges)
	data.ServerTimeMS = badgeProfile.ServerTimeMS
	return data, nil
}

func (h *Handler) fillAppearanceLevelBadgeEntitlementFallback(request *http.Request, userID int64, levels map[string]any, levelBadges map[string]any) {
	if h.walletClient == nil || userID <= 0 || !appearanceLevelBadgesNeedFallback(levelBadges) {
		return
	}
	// activity 的等级投影仍是首选来源；这里只在投影缺轨道时读取 wallet active 权益。
	// 注册补发的一等级徽章当前已经落在权益表里，这层兜底让老用户不等等级事件也能展示素材。
	resp, err := h.walletClient.ListUserResources(request.Context(), &walletv1.ListUserResourcesRequest{
		RequestId:    httpkit.RequestIDFromContext(request.Context()),
		AppCode:      appcode.FromContext(request.Context()),
		UserId:       userID,
		ResourceType: appearanceResourceBadge,
		ActiveOnly:   true,
	})
	if err != nil {
		return
	}
	for _, item := range resp.GetResources() {
		appearanceApplyLevelBadgeEntitlement(levels, levelBadges, item)
		if !appearanceLevelBadgesNeedFallback(levelBadges) {
			return
		}
	}
}

func appearanceLevelBadgesNeedFallback(levelBadges map[string]any) bool {
	if levelBadges == nil {
		return true
	}
	for _, track := range []string{"wealth", "game", "charm"} {
		if _, ok := levelBadges[track]; !ok {
			return true
		}
	}
	return false
}

func appearanceApplyLevelBadgeEntitlement(levels map[string]any, levelBadges map[string]any, item *walletv1.UserResourceEntitlement) {
	if item == nil || levelBadges == nil {
		return
	}
	resource := appearanceResourceFromEntitlement(item)
	track := strings.TrimSpace(fmt.Sprint(resource["level_track"]))
	if track == "" || fmt.Sprint(resource["badge_kind"]) != "level" || fmt.Sprint(resource["badge_form"]) != "strip" {
		return
	}
	if _, exists := levelBadges[track]; exists {
		return
	}
	resourceID, _ := resource["resource_id"].(int64)
	if resourceID <= 0 {
		return
	}
	levelBadges[track] = map[string]any{
		"track":          track,
		"source_type":    "wallet_entitlement",
		"source_level":   appearanceLevelValue(levels, track),
		"badge_kind":     "level",
		"resource_id":    resourceID,
		"entitlement_id": item.GetEntitlementId(),
		"updated_at_ms":  item.GetUpdatedAtMs(),
		"resource":       resource,
	}
}

func appearanceLevelValue(levels map[string]any, track string) int64 {
	trackLevel, ok := levels[track].(map[string]any)
	if !ok {
		return 0
	}
	switch value := trackLevel["level"].(type) {
	case int64:
		return value
	case int32:
		return int64(value)
	case int:
		return int64(value)
	case float64:
		return int64(value)
	default:
		return 0
	}
}

func (h *Handler) levelDisplayForUser(request *http.Request, userID int64) (map[string]any, map[string]any) {
	levels := defaultAppearanceLevelDisplay()
	levelBadges := map[string]any{}
	if h.growthLevelClient == nil {
		return levels, levelBadges
	}
	resp, err := h.growthLevelClient.BatchGetUserLevelDisplayProfiles(request.Context(), &activityv1.BatchGetUserLevelDisplayProfilesRequest{
		Meta:    httpkit.ActivityMeta(request),
		UserIds: []int64{userID},
	})
	if err != nil {
		// 等级徽章只是展示增强，activity 短暂失败时保留装扮和普通徽章结果。
		return levels, levelBadges
	}
	resourceIDs := make([]int64, 0, 3)
	var profile *activityv1.UserLevelDisplayProfile
	for _, item := range resp.GetProfiles() {
		if item.GetUserId() == userID {
			profile = item
			break
		}
	}
	if profile == nil {
		return levels, levelBadges
	}
	resourceIDs = append(resourceIDs, profile.GetWealth().GetBadgeResourceId(), profile.GetGame().GetBadgeResourceId(), profile.GetCharm().GetBadgeResourceId())
	resources := map[int64]map[string]any{}
	if resolved, err := h.appearanceResourcesByID(request, resourceIDs); err == nil {
		resources = resolved
	}
	applyAppearanceLevelTrack(levels, levelBadges, profile.GetWealth(), resources)
	applyAppearanceLevelTrack(levels, levelBadges, profile.GetGame(), resources)
	applyAppearanceLevelTrack(levels, levelBadges, profile.GetCharm(), resources)
	return levels, levelBadges
}

func applyAppearanceLevelTrack(levels map[string]any, levelBadges map[string]any, item *activityv1.LevelDisplayTrackProfile, resources map[int64]map[string]any) {
	if item == nil || strings.TrimSpace(item.GetTrack()) == "" {
		return
	}
	track := strings.TrimSpace(item.GetTrack())
	levels[track] = map[string]any{
		"level":                item.GetLevel(),
		"tier_id":              item.GetTierId(),
		"badge_resource_id":    item.GetBadgeResourceId(),
		"badge_source_level":   item.GetBadgeSourceLevel(),
		"avatar_frame_id":      item.GetAvatarFrameResourceId(),
		"updated_at_ms":        item.GetUpdatedAtMs(),
		"display_badge_source": "level_rule",
	}
	if item.GetBadgeResourceId() <= 0 {
		return
	}
	badge := map[string]any{
		"track":         track,
		"source_type":   "level_rule",
		"source_level":  item.GetBadgeSourceLevel(),
		"badge_kind":    "level",
		"resource_id":   item.GetBadgeResourceId(),
		"updated_at_ms": item.GetUpdatedAtMs(),
	}
	if resource, ok := resources[item.GetBadgeResourceId()]; ok {
		badge["resource"] = resource
	}
	levelBadges[track] = badge
}

func defaultAppearanceLevelDisplay() map[string]any {
	return map[string]any{
		"wealth": map[string]any{"level": int32(0), "tier_id": int64(0), "badge_resource_id": int64(0), "badge_source_level": int32(0), "avatar_frame_id": int64(0), "updated_at_ms": int64(0), "display_badge_source": "level_rule"},
		"game":   map[string]any{"level": int32(0), "tier_id": int64(0), "badge_resource_id": int64(0), "badge_source_level": int32(0), "avatar_frame_id": int64(0), "updated_at_ms": int64(0), "display_badge_source": "level_rule"},
		"charm":  map[string]any{"level": int32(0), "tier_id": int64(0), "badge_resource_id": int64(0), "badge_source_level": int32(0), "avatar_frame_id": int64(0), "updated_at_ms": int64(0), "display_badge_source": "level_rule"},
	}
}

func (h *Handler) badgeProfileForUser(request *http.Request, userID int64) (appearanceBadgeData, error) {
	result := appearanceBadgeData{
		StripBadges:       []map[string]any{},
		ProfileTileBadges: []map[string]any{},
		HonorBadges:       []map[string]any{},
	}
	if h.achievementClient == nil {
		// 未接 activity 时保留装扮数据返回，badge 投影为空；这不会影响头像框/座驾展示。
		return result, nil
	}
	resp, err := h.achievementClient.ListMyBadges(request.Context(), &activityv1.ListMyBadgesRequest{
		Meta:   httpkit.ActivityMeta(request),
		UserId: userID,
	})
	if err != nil {
		return result, err
	}
	// activity 的 badge item 只保存 resource_id；资源图标/动画素材仍从 wallet resource 表解析。
	resources, err := h.appearanceResourcesByID(request, badgeResourceIDs(resp))
	if err != nil {
		return result, err
	}
	result.StripBadges = badgeItemsFromProto(resp.GetStripBadges(), resources)
	result.ProfileTileBadges = badgeItemsFromProto(resp.GetProfileTileBadges(), resources)
	result.HonorBadges = badgeItemsFromProto(resp.GetHonorBadges(), resources)
	result.ServerTimeMS = resp.GetServerTimeMs()
	return result, nil
}

func (h *Handler) appearanceResourcesByID(request *http.Request, ids []int64) (map[int64]map[string]any, error) {
	ids = uniqueAppearanceResourceIDs(ids)
	resources := make(map[int64]map[string]any, len(ids))
	if len(ids) == 0 {
		return resources, nil
	}
	if h.walletClient == nil {
		// 只有 badge 素材解析会走到这里；缺 wallet 表示无法把 badge_id 转成可展示资源。
		return nil, errAppearanceUpstream
	}
	for _, id := range ids {
		resp, err := h.walletClient.GetResource(request.Context(), &walletv1.GetResourceRequest{
			RequestId:  httpkit.RequestIDFromContext(request.Context()),
			AppCode:    appcode.FromContext(request.Context()),
			ResourceId: id,
		})
		if err != nil {
			return nil, err
		}
		resource := appearanceResourceFromProto(resp.GetResource(), "", 0)
		if resourceID, ok := resource["resource_id"].(int64); ok && resourceID > 0 {
			resources[resourceID] = resource
		}
	}
	return resources, nil
}

func appearanceResourceFromEntitlement(item *walletv1.UserResourceEntitlement) map[string]any {
	if item == nil {
		return map[string]any{}
	}
	// entitlement_id/expires_at_ms 是佩戴态字段，resource 本身不带这两个用户维度。
	resource := appearanceResourceFromProto(item.GetResource(), item.GetEntitlementId(), item.GetExpiresAtMs())
	if len(resource) > 0 && len(item.GetVipBenefitCodes()) > 0 {
		// 与房间资料保持同一资源级分类契约，完整个人页不得再次按 badge 图片或名称猜 VIP 身份。
		resource["vip_benefit_codes"] = append([]string(nil), item.GetVipBenefitCodes()...)
	}
	return resource
}

func appearanceResourceFromProto(item *walletv1.Resource, entitlementID string, expiresAtMS int64) map[string]any {
	if item == nil || item.GetResourceId() <= 0 {
		return map[string]any{}
	}
	resource := map[string]any{
		"resource_id":    item.GetResourceId(),
		"resource_code":  item.GetResourceCode(),
		"resource_type":  item.GetResourceType(),
		"name":           item.GetName(),
		"asset_url":      item.GetAssetUrl(),
		"preview_url":    item.GetPreviewUrl(),
		"animation_url":  item.GetAnimationUrl(),
		"metadata_json":  item.GetMetadataJson(),
		"entitlement_id": entitlementID,
		"expires_at_ms":  expiresAtMS,
	}
	if format := resourceview.FormatFromMetadata(item.GetMetadataJson()); format != "" {
		// 佩戴后的 appearance 仍要携带与资源列表相同的显式 renderer；Flutter 不解析 URL 后缀，
		// 缺少后台格式配置时保持字段省略，让客户端明确走“不支持”分支。
		resource["format"] = format
	}
	if item.GetResourceType() == appearanceResourceBadge {
		badgeForm, badgeKind, levelTrack := appearanceBadgeMetadataFields(item.GetMetadataJson())
		resource["badge_form"] = badgeForm
		resource["badge_kind"] = badgeKind
		resource["level_track"] = levelTrack
	}
	return resource
}

func appearanceBadgeMetadataFields(raw string) (string, string, string) {
	payload := map[string]any{}
	if err := json.Unmarshal([]byte(strings.TrimSpace(raw)), &payload); err != nil {
		return "", "normal", ""
	}
	badgeForm := strings.ToLower(strings.TrimSpace(fmt.Sprint(payload["badge_form"])))
	if badgeForm != "strip" && badgeForm != "tile" {
		badgeForm = ""
	}
	badgeKind := strings.ToLower(strings.TrimSpace(fmt.Sprint(payload["badge_kind"])))
	if badgeKind != "level" {
		badgeKind = "normal"
	}
	levelTrack := strings.ToLower(strings.TrimSpace(fmt.Sprint(payload["level_track"])))
	switch levelTrack {
	case "wealth", "game", "charm":
	default:
		levelTrack = ""
	}
	return badgeForm, badgeKind, levelTrack
}

func badgeItemsFromProto(items []*activityv1.BadgeDisplayItem, resources map[int64]map[string]any) []map[string]any {
	result := make([]map[string]any, 0, len(items))
	for _, item := range items {
		if item == nil {
			continue
		}
		// badge item 保留 activity 的展示位字段，同时在 resource 内嵌 wallet 资源素材。
		data := map[string]any{
			"slot":           item.GetSlot(),
			"position":       item.GetPosition(),
			"badge_form":     item.GetBadgeForm(),
			"resource_id":    item.GetResourceId(),
			"entitlement_id": item.GetEntitlementId(),
			"source_type":    item.GetSourceType(),
			"source_id":      item.GetSourceId(),
			"pin_mode":       item.GetPinMode(),
			"updated_at_ms":  item.GetUpdatedAtMs(),
		}
		if resource, ok := resources[item.GetResourceId()]; ok {
			data["resource"] = resource
		}
		result = append(result, data)
	}
	return result
}

func badgeResourceIDs(resp *activityv1.ListMyBadgesResponse) []int64 {
	if resp == nil {
		return nil
	}
	// 三个展示区都可能引用 badge 资源；统一收集后去重解析素材。
	ids := make([]int64, 0, len(resp.GetStripBadges())+len(resp.GetProfileTileBadges())+len(resp.GetHonorBadges()))
	for _, item := range resp.GetStripBadges() {
		ids = append(ids, item.GetResourceId())
	}
	for _, item := range resp.GetProfileTileBadges() {
		ids = append(ids, item.GetResourceId())
	}
	for _, item := range resp.GetHonorBadges() {
		ids = append(ids, item.GetResourceId())
	}
	return ids
}

func uniqueAppearanceResourceIDs(ids []int64) []int64 {
	seen := make(map[int64]struct{}, len(ids))
	result := make([]int64, 0, len(ids))
	for _, id := range ids {
		if id <= 0 {
			continue
		}
		if _, exists := seen[id]; exists {
			continue
		}
		seen[id] = struct{}{}
		result = append(result, id)
	}
	return result
}
