package activityapi

import (
	"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"
)

type achievementData struct {
	Definition    achievementDefinitionData `json:"definition"`
	CycleKey      string                    `json:"cycle_key"`
	ProgressValue int64                     `json:"progress_value"`
	TargetValue   int64                     `json:"target_value"`
	UserStatus    string                    `json:"user_status"`
	UnlockedAtMS  int64                     `json:"unlocked_at_ms"`
	RewardStatus  string                    `json:"reward_status"`
}

type achievementDefinitionData struct {
	AchievementID          string                        `json:"achievement_id"`
	CollectionID           string                        `json:"collection_id"`
	CollectionType         string                        `json:"collection_type"`
	AchievementType        string                        `json:"achievement_type"`
	Title                  string                        `json:"title"`
	Description            string                        `json:"description"`
	Status                 string                        `json:"status"`
	Version                int64                         `json:"version"`
	PrimaryBadgeResourceID int64                         `json:"primary_badge_resource_id"`
	PrimaryBadgeResource   *achievementResourceData      `json:"primary_badge_resource,omitempty"`
	RewardResourceGroupID  int64                         `json:"reward_resource_group_id"`
	RewardResourceGroup    *achievementResourceGroupData `json:"reward_resource_group,omitempty"`
	AutoPinPolicy          string                        `json:"auto_pin_policy"`
	HonorVisibility        bool                          `json:"honor_visibility"`
	EffectiveFromMS        int64                         `json:"effective_from_ms"`
	EffectiveToMS          int64                         `json:"effective_to_ms"`
	SortOrder              int32                         `json:"sort_order"`
	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"`
	Conditions             []achievementConditionData    `json:"conditions"`
}

type achievementConditionData struct {
	ConditionID         string `json:"condition_id"`
	AchievementID       string `json:"achievement_id"`
	Version             int64  `json:"version"`
	MetricType          string `json:"metric_type"`
	TargetValue         int64  `json:"target_value"`
	TargetUnit          string `json:"target_unit"`
	DimensionFilterJSON string `json:"dimension_filter_json"`
}

type achievementResourceData struct {
	ResourceID        int64    `json:"resource_id"`
	ResourceCode      string   `json:"resource_code"`
	ResourceType      string   `json:"resource_type"`
	Name              string   `json:"name"`
	Status            string   `json:"status"`
	Grantable         bool     `json:"grantable"`
	GrantStrategy     string   `json:"grant_strategy"`
	WalletAssetType   string   `json:"wallet_asset_type,omitempty"`
	WalletAssetAmount int64    `json:"wallet_asset_amount,omitempty"`
	UsageScopes       []string `json:"usage_scopes"`
	AssetURL          string   `json:"asset_url"`
	PreviewURL        string   `json:"preview_url"`
	AnimationURL      string   `json:"animation_url"`
	MetadataJSON      string   `json:"metadata_json"`
	SortOrder         int32    `json:"sort_order"`
	CreatedAtMS       int64    `json:"created_at_ms"`
	UpdatedAtMS       int64    `json:"updated_at_ms"`
}

type achievementResourceGroupItemData struct {
	GroupItemID       int64                    `json:"group_item_id"`
	ItemType          string                   `json:"item_type"`
	ResourceID        int64                    `json:"resource_id"`
	Resource          *achievementResourceData `json:"resource,omitempty"`
	WalletAssetType   string                   `json:"wallet_asset_type,omitempty"`
	WalletAssetAmount int64                    `json:"wallet_asset_amount,omitempty"`
	Quantity          int64                    `json:"quantity"`
	DurationMS        int64                    `json:"duration_ms"`
	SortOrder         int32                    `json:"sort_order"`
}

type achievementResourceGroupData struct {
	GroupID     int64                              `json:"group_id"`
	GroupCode   string                             `json:"group_code"`
	Name        string                             `json:"name"`
	Status      string                             `json:"status"`
	Description string                             `json:"description"`
	SortOrder   int32                              `json:"sort_order"`
	Items       []achievementResourceGroupItemData `json:"items"`
	CreatedAtMS int64                              `json:"created_at_ms"`
	UpdatedAtMS int64                              `json:"updated_at_ms"`
}

type badgeProfileData struct {
	StripBadges       []badgeDisplayItemData `json:"strip_badges"`
	ProfileTileBadges []badgeDisplayItemData `json:"profile_tile_badges"`
	HonorBadges       []badgeDisplayItemData `json:"honor_badges"`
	ServerTimeMS      int64                  `json:"server_time_ms"`
}

type badgeDisplayItemData struct {
	Slot          string                   `json:"slot"`
	Position      int32                    `json:"position"`
	BadgeForm     string                   `json:"badge_form"`
	ResourceID    int64                    `json:"resource_id"`
	Resource      *achievementResourceData `json:"resource,omitempty"`
	EntitlementID string                   `json:"entitlement_id"`
	SourceType    string                   `json:"source_type"`
	SourceID      string                   `json:"source_id"`
	PinMode       string                   `json:"pin_mode"`
	UpdatedAtMS   int64                    `json:"updated_at_ms"`
}

type badgeDisplayRequestBody struct {
	Slot      string                 `json:"slot"`
	CommandID string                 `json:"command_id"`
	Items     []badgeDisplayItemData `json:"items"`
}

func (h *Handler) listAchievements(writer http.ResponseWriter, request *http.Request) {
	if h.achievementClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	page, ok := httpkit.PositiveInt32Query(request, "page", 1)
	if !ok {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	pageSize, ok := httpkit.PositiveInt32Query(request, "page_size", 20)
	if !ok {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	userID := auth.UserIDFromContext(request.Context())
	rawUserID := strings.TrimSpace(request.URL.Query().Get("user_id"))
	if rawUserID != "" {
		targetUserID, err := strconv.ParseInt(rawUserID, 10, 64)
		if err != nil || targetUserID <= 0 {
			httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
			return
		}
		// 成就 H5 支持分享/外部入口按目标用户查看；显式 user_id 是查询目标，不代表操作者身份。
		userID = targetUserID
	}
	if userID <= 0 {
		httpkit.WriteError(writer, request, http.StatusUnauthorized, httpkit.CodeUnauthorized, "unauthorized")
		return
	}
	resp, err := h.achievementClient.ListAchievements(request.Context(), &activityv1.ListAchievementsRequest{
		Meta:           httpkit.ActivityMeta(request),
		UserId:         userID,
		CollectionType: strings.TrimSpace(request.URL.Query().Get("collection_type")),
		CollectionId:   strings.TrimSpace(request.URL.Query().Get("collection_id")),
		Page:           page,
		PageSize:       pageSize,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	resources, ok := h.resolveAchievementResources(writer, request, achievementResourceIDs(resp.GetAchievements()))
	if !ok {
		return
	}
	resourceGroups, ok := h.resolveAchievementResourceGroups(writer, request, achievementResourceGroupIDs(resp.GetAchievements()))
	if !ok {
		return
	}
	items := make([]achievementData, 0, len(resp.GetAchievements()))
	for _, item := range resp.GetAchievements() {
		items = append(items, achievementFromProto(item, resources, resourceGroups))
	}
	httpkit.WriteOK(writer, request, map[string]any{
		"achievements":   items,
		"total":          resp.GetTotal(),
		"page":           page,
		"page_size":      pageSize,
		"server_time_ms": resp.GetServerTimeMs(),
	})
}

func (h *Handler) listMyBadges(writer http.ResponseWriter, request *http.Request) {
	if h.achievementClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	resp, err := h.achievementClient.ListMyBadges(request.Context(), &activityv1.ListMyBadgesRequest{
		Meta:   httpkit.ActivityMeta(request),
		UserId: auth.UserIDFromContext(request.Context()),
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	resources, ok := h.resolveAchievementResources(writer, request, badgeProfileResourceIDs(resp))
	if !ok {
		return
	}
	httpkit.WriteOK(writer, request, badgeProfileFromProto(resp, resources))
}

func (h *Handler) setBadgeDisplay(writer http.ResponseWriter, request *http.Request) {
	if h.achievementClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	var body badgeDisplayRequestBody
	if !httpkit.Decode(writer, request, &body) {
		return
	}
	slot := strings.TrimSpace(body.Slot)
	commandID := strings.TrimSpace(body.CommandID)
	if slot == "" || commandID == "" {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	items := make([]*activityv1.BadgeDisplayItem, 0, len(body.Items))
	for _, item := range body.Items {
		items = append(items, badgeDisplayItemToProto(slot, item))
	}
	resp, err := h.achievementClient.SetBadgeDisplay(request.Context(), &activityv1.SetBadgeDisplayRequest{
		Meta:      httpkit.ActivityMeta(request),
		UserId:    auth.UserIDFromContext(request.Context()),
		Slot:      slot,
		Items:     items,
		CommandId: commandID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	resources, ok := h.resolveAchievementResources(writer, request, badgeProfileResourceIDs(resp.GetProfile()))
	if !ok {
		return
	}
	httpkit.WriteOK(writer, request, badgeProfileFromProto(resp.GetProfile(), resources))
}

func achievementFromProto(item *activityv1.UserAchievement, resources map[int64]achievementResourceData, resourceGroups map[int64]achievementResourceGroupData) achievementData {
	return achievementData{
		Definition:    achievementDefinitionFromProto(item.GetDefinition(), resources, resourceGroups),
		CycleKey:      item.GetCycleKey(),
		ProgressValue: item.GetProgressValue(),
		TargetValue:   item.GetTargetValue(),
		UserStatus:    item.GetUserStatus(),
		UnlockedAtMS:  item.GetUnlockedAtMs(),
		RewardStatus:  item.GetRewardStatus(),
	}
}

func achievementDefinitionFromProto(item *activityv1.AchievementDefinition, resources map[int64]achievementResourceData, resourceGroups map[int64]achievementResourceGroupData) achievementDefinitionData {
	conditions := make([]achievementConditionData, 0, len(item.GetConditions()))
	for _, condition := range item.GetConditions() {
		conditions = append(conditions, achievementConditionFromProto(condition))
	}
	data := achievementDefinitionData{
		AchievementID:          item.GetAchievementId(),
		CollectionID:           item.GetCollectionId(),
		CollectionType:         item.GetCollectionType(),
		AchievementType:        item.GetAchievementType(),
		Title:                  item.GetTitle(),
		Description:            item.GetDescription(),
		Status:                 item.GetStatus(),
		Version:                item.GetVersion(),
		PrimaryBadgeResourceID: item.GetPrimaryBadgeResourceId(),
		RewardResourceGroupID:  item.GetRewardResourceGroupId(),
		AutoPinPolicy:          item.GetAutoPinPolicy(),
		HonorVisibility:        item.GetHonorVisibility(),
		EffectiveFromMS:        item.GetEffectiveFromMs(),
		EffectiveToMS:          item.GetEffectiveToMs(),
		SortOrder:              item.GetSortOrder(),
		DisplayConfigJSON:      item.GetDisplayConfigJson(),
		CreatedByAdminID:       item.GetCreatedByAdminId(),
		UpdatedByAdminID:       item.GetUpdatedByAdminId(),
		CreatedAtMS:            item.GetCreatedAtMs(),
		UpdatedAtMS:            item.GetUpdatedAtMs(),
		Conditions:             conditions,
	}
	if resource, ok := resources[item.GetPrimaryBadgeResourceId()]; ok {
		data.PrimaryBadgeResource = &resource
	}
	if group, ok := resourceGroups[item.GetRewardResourceGroupId()]; ok {
		data.RewardResourceGroup = &group
	}
	return data
}

func achievementConditionFromProto(item *activityv1.AchievementCondition) achievementConditionData {
	return achievementConditionData{
		ConditionID:         item.GetConditionId(),
		AchievementID:       item.GetAchievementId(),
		Version:             item.GetVersion(),
		MetricType:          item.GetMetricType(),
		TargetValue:         item.GetTargetValue(),
		TargetUnit:          item.GetTargetUnit(),
		DimensionFilterJSON: item.GetDimensionFilterJson(),
	}
}

func badgeProfileFromProto(resp *activityv1.ListMyBadgesResponse, resources map[int64]achievementResourceData) badgeProfileData {
	return badgeProfileData{
		StripBadges:       badgeItemsFromProto(resp.GetStripBadges(), resources),
		ProfileTileBadges: badgeItemsFromProto(resp.GetProfileTileBadges(), resources),
		HonorBadges:       badgeItemsFromProto(resp.GetHonorBadges(), resources),
		ServerTimeMS:      resp.GetServerTimeMs(),
	}
}

func badgeItemsFromProto(items []*activityv1.BadgeDisplayItem, resources map[int64]achievementResourceData) []badgeDisplayItemData {
	result := make([]badgeDisplayItemData, 0, len(items))
	for _, item := range items {
		result = append(result, badgeDisplayItemFromProto(item, resources))
	}
	return result
}

func badgeDisplayItemFromProto(item *activityv1.BadgeDisplayItem, resources map[int64]achievementResourceData) badgeDisplayItemData {
	data := badgeDisplayItemData{
		Slot:          item.GetSlot(),
		Position:      item.GetPosition(),
		BadgeForm:     item.GetBadgeForm(),
		ResourceID:    item.GetResourceId(),
		EntitlementID: item.GetEntitlementId(),
		SourceType:    item.GetSourceType(),
		SourceID:      item.GetSourceId(),
		PinMode:       item.GetPinMode(),
		UpdatedAtMS:   item.GetUpdatedAtMs(),
	}
	if resource, ok := resources[item.GetResourceId()]; ok {
		data.Resource = &resource
	}
	return data
}

func badgeDisplayItemToProto(slot string, item badgeDisplayItemData) *activityv1.BadgeDisplayItem {
	return &activityv1.BadgeDisplayItem{
		Slot:          slot,
		Position:      item.Position,
		BadgeForm:     item.BadgeForm,
		ResourceId:    item.ResourceID,
		EntitlementId: strings.TrimSpace(item.EntitlementID),
		SourceType:    strings.TrimSpace(item.SourceType),
		SourceId:      strings.TrimSpace(item.SourceID),
		PinMode:       item.PinMode,
		UpdatedAtMs:   item.UpdatedAtMS,
	}
}

func (h *Handler) resolveAchievementResources(writer http.ResponseWriter, request *http.Request, ids []int64) (map[int64]achievementResourceData, bool) {
	ids = uniquePositiveIDs(ids)
	resources := make(map[int64]achievementResourceData, len(ids))
	if len(ids) == 0 {
		return resources, true
	}
	if h.walletClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return nil, false
	}
	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 {
			httpkit.WriteRPCError(writer, request, err)
			return nil, false
		}
		resource := achievementResourceFromProto(resp.GetResource())
		if resource.ResourceID > 0 {
			resources[resource.ResourceID] = resource
		}
	}
	return resources, true
}

func (h *Handler) resolveAchievementResourceGroups(writer http.ResponseWriter, request *http.Request, ids []int64) (map[int64]achievementResourceGroupData, bool) {
	ids = uniquePositiveIDs(ids)
	groups := make(map[int64]achievementResourceGroupData, len(ids))
	if len(ids) == 0 {
		return groups, true
	}
	if h.walletClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return nil, false
	}
	for _, id := range ids {
		resp, err := h.walletClient.GetResourceGroup(request.Context(), &walletv1.GetResourceGroupRequest{
			RequestId: httpkit.RequestIDFromContext(request.Context()),
			AppCode:   appcode.FromContext(request.Context()),
			GroupId:   id,
		})
		if err != nil {
			httpkit.WriteRPCError(writer, request, err)
			return nil, false
		}
		group := achievementResourceGroupFromProto(resp.GetGroup())
		if group.GroupID > 0 {
			groups[group.GroupID] = group
		}
	}
	return groups, true
}

func achievementResourceIDs(items []*activityv1.UserAchievement) []int64 {
	ids := make([]int64, 0, len(items))
	for _, item := range items {
		if item.GetDefinition().GetPrimaryBadgeResourceId() > 0 {
			ids = append(ids, item.GetDefinition().GetPrimaryBadgeResourceId())
		}
	}
	return ids
}

func achievementResourceGroupIDs(items []*activityv1.UserAchievement) []int64 {
	ids := make([]int64, 0, len(items))
	for _, item := range items {
		if item.GetDefinition().GetRewardResourceGroupId() > 0 {
			ids = append(ids, item.GetDefinition().GetRewardResourceGroupId())
		}
	}
	return ids
}

func badgeProfileResourceIDs(resp *activityv1.ListMyBadgesResponse) []int64 {
	if resp == nil {
		return nil
	}
	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 uniquePositiveIDs(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 _, ok := seen[id]; ok {
			continue
		}
		seen[id] = struct{}{}
		result = append(result, id)
	}
	return result
}

func achievementResourceFromProto(item *walletv1.Resource) achievementResourceData {
	if item == nil {
		return achievementResourceData{}
	}
	return achievementResourceData{
		ResourceID:        item.GetResourceId(),
		ResourceCode:      item.GetResourceCode(),
		ResourceType:      item.GetResourceType(),
		Name:              item.GetName(),
		Status:            item.GetStatus(),
		Grantable:         item.GetGrantable(),
		GrantStrategy:     item.GetGrantStrategy(),
		WalletAssetType:   item.GetWalletAssetType(),
		WalletAssetAmount: item.GetWalletAssetAmount(),
		UsageScopes:       item.GetUsageScopes(),
		AssetURL:          item.GetAssetUrl(),
		PreviewURL:        item.GetPreviewUrl(),
		AnimationURL:      item.GetAnimationUrl(),
		MetadataJSON:      item.GetMetadataJson(),
		SortOrder:         item.GetSortOrder(),
		CreatedAtMS:       item.GetCreatedAtMs(),
		UpdatedAtMS:       item.GetUpdatedAtMs(),
	}
}

func achievementResourceGroupFromProto(group *walletv1.ResourceGroup) achievementResourceGroupData {
	if group == nil {
		return achievementResourceGroupData{}
	}
	items := make([]achievementResourceGroupItemData, 0, len(group.GetItems()))
	for _, item := range group.GetItems() {
		items = append(items, achievementResourceGroupItemData{
			GroupItemID:       item.GetGroupItemId(),
			ItemType:          item.GetItemType(),
			ResourceID:        item.GetResourceId(),
			Resource:          achievementResourcePointerFromProto(item.GetResource()),
			WalletAssetType:   item.GetWalletAssetType(),
			WalletAssetAmount: item.GetWalletAssetAmount(),
			Quantity:          item.GetQuantity(),
			DurationMS:        item.GetDurationMs(),
			SortOrder:         item.GetSortOrder(),
		})
	}
	return achievementResourceGroupData{
		GroupID:     group.GetGroupId(),
		GroupCode:   group.GetGroupCode(),
		Name:        group.GetName(),
		Status:      group.GetStatus(),
		Description: group.GetDescription(),
		SortOrder:   group.GetSortOrder(),
		Items:       items,
		CreatedAtMS: group.GetCreatedAtMs(),
		UpdatedAtMS: group.GetUpdatedAtMs(),
	}
}

func achievementResourcePointerFromProto(item *walletv1.Resource) *achievementResourceData {
	resource := achievementResourceFromProto(item)
	if resource.ResourceID == 0 {
		return nil
	}
	return &resource
}
