package activityapi

import (
	"net/http"
	"strconv"
	"strings"

	activityv1 "hyapp.local/api/proto/activity/v1"
	userv1 "hyapp.local/api/proto/user/v1"
	"hyapp/services/gateway-service/internal/auth"
	"hyapp/services/gateway-service/internal/client"
	"hyapp/services/gateway-service/internal/transport/http/httpkit"
)

type inviteActivityRewardTierData struct {
	TierID                    int64  `json:"tier_id"`
	RewardType                string `json:"reward_type"`
	TierCode                  string `json:"tier_code"`
	TierName                  string `json:"tier_name"`
	ThresholdCoinAmount       int64  `json:"threshold_coin_amount"`
	ThresholdValidInviteCount int64  `json:"threshold_valid_invite_count"`
	RewardCoinAmount          int64  `json:"reward_coin_amount"`
	Status                    string `json:"status"`
	SortOrder                 int32  `json:"sort_order"`
	Reached                   bool   `json:"reached"`
	Claimable                 bool   `json:"claimable"`
	Claimed                   bool   `json:"claimed"`
}

type inviteActivityRewardProgressData struct {
	CycleKey                  string `json:"cycle_key"`
	UserID                    int64  `json:"user_id"`
	TotalRechargeCoinAmount   int64  `json:"total_recharge_coin_amount"`
	ValidInviteCount          int64  `json:"valid_invite_count"`
	ClaimedRewardCoinAmount   int64  `json:"claimed_reward_coin_amount"`
	UpdatedAtMS               int64  `json:"updated_at_ms"`
	RechargeHighestTierID     int64  `json:"recharge_highest_tier_id"`
	RechargeClaimableTierID   int64  `json:"recharge_claimable_tier_id"`
	RechargeClaimableCoin     int64  `json:"recharge_claimable_coin"`
	ValidInviteClaimableCount int32  `json:"valid_invite_claimable_count"`
}

type inviteActivityRewardClaimData struct {
	ClaimID                   string `json:"claim_id"`
	CycleKey                  string `json:"cycle_key"`
	UserID                    int64  `json:"user_id"`
	RewardType                string `json:"reward_type"`
	CommandID                 string `json:"command_id"`
	TierID                    int64  `json:"tier_id"`
	TierCode                  string `json:"tier_code"`
	TierName                  string `json:"tier_name"`
	ThresholdCoinAmount       int64  `json:"threshold_coin_amount"`
	ThresholdValidInviteCount int64  `json:"threshold_valid_invite_count"`
	ReachedValue              int64  `json:"reached_value"`
	RewardCoinAmount          int64  `json:"reward_coin_amount"`
	Status                    string `json:"status"`
	WalletCommandID           string `json:"wallet_command_id"`
	WalletTransactionID       string `json:"wallet_transaction_id"`
	FailureReason             string `json:"failure_reason"`
	GrantedAtMS               int64  `json:"granted_at_ms"`
	CreatedAtMS               int64  `json:"created_at_ms"`
	UpdatedAtMS               int64  `json:"updated_at_ms"`
}

type inviteActivityRewardStatusData struct {
	Enabled                          bool                               `json:"enabled"`
	PerInviteInviterRewardCoinAmount int64                              `json:"per_invite_inviter_reward_coin_amount"`
	PerInviteInviteeRewardCoinAmount int64                              `json:"per_invite_invitee_reward_coin_amount"`
	CycleKey                         string                             `json:"cycle_key"`
	Period                           cumulativeRechargeRewardPeriodData `json:"period"`
	Tiers                            []inviteActivityRewardTierData     `json:"tiers"`
	Progress                         inviteActivityRewardProgressData   `json:"progress"`
	Claims                           []inviteActivityRewardClaimData    `json:"claims"`
	ServerTimeMS                     int64                              `json:"server_time_ms"`
}

type inviteActivityRewardClaimBody struct {
	RewardType string `json:"reward_type"`
	TierID     int64  `json:"tier_id"`
	CommandID  string `json:"command_id"`
}

type inviteActivityLeaderboardUserData struct {
	UserID        string `json:"user_id"`
	DisplayUserID string `json:"display_user_id"`
	Username      string `json:"username"`
	Avatar        string `json:"avatar"`
	CreatedAtMS   int64  `json:"created_at_ms"`
}

type inviteActivityInviteeData struct {
	UserID             string                             `json:"user_id"`
	RechargeCoinAmount int64                              `json:"recharge_coin_amount"`
	InvitedAtMS        int64                              `json:"invited_at_ms"`
	User               *inviteActivityLeaderboardUserData `json:"user,omitempty"`
}

type inviteActivityInviteeListData struct {
	Invitees     []inviteActivityInviteeData `json:"invitees"`
	Total        int64                       `json:"total"`
	Page         int32                       `json:"page"`
	PageSize     int32                       `json:"page_size"`
	CycleKey     string                      `json:"cycle_key"`
	ServerTimeMS int64                       `json:"server_time_ms"`
}

type inviteActivityLeaderboardEntryData struct {
	RankNo                  int32                              `json:"rank_no"`
	UserID                  string                             `json:"user_id"`
	ValidInviteCount        int64                              `json:"valid_invite_count"`
	TotalRechargeCoinAmount int64                              `json:"total_recharge_coin_amount"`
	User                    *inviteActivityLeaderboardUserData `json:"user,omitempty"`
}

type inviteActivityLeaderboardData struct {
	Entries      []inviteActivityLeaderboardEntryData `json:"entries"`
	Total        int64                                `json:"total"`
	Page         int32                                `json:"page"`
	PageSize     int32                                `json:"page_size"`
	CycleKey     string                               `json:"cycle_key"`
	ServerTimeMS int64                                `json:"server_time_ms"`
}

// getInviteActivityRewardStatus 返回当前用户本 UTC 月邀请活动面板；充值和有效人数都由 activity-service 已消费事实计算。
func (h *Handler) getInviteActivityRewardStatus(writer http.ResponseWriter, request *http.Request) {
	if h.inviteActivityReward == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	resp, err := h.inviteActivityReward.GetInviteActivityRewardStatus(request.Context(), &activityv1.GetInviteActivityRewardStatusRequest{
		Meta:   httpkit.ActivityMeta(request),
		UserId: auth.UserIDFromContext(request.Context()),
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	httpkit.WriteOK(writer, request, inviteActivityRewardStatusFromProto(resp.GetStatus()))
}

// claimInviteActivityReward 只透传当前用户、档位和 command_id；是否达标、最高档差额和发奖幂等在 activity-service 内完成。
func (h *Handler) claimInviteActivityReward(writer http.ResponseWriter, request *http.Request) {
	if h.inviteActivityReward == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	var body inviteActivityRewardClaimBody
	if !httpkit.Decode(writer, request, &body) {
		return
	}
	rewardType := strings.TrimSpace(body.RewardType)
	commandID := strings.TrimSpace(body.CommandID)
	if rewardType == "" || body.TierID <= 0 || commandID == "" {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	resp, err := h.inviteActivityReward.ClaimInviteActivityReward(request.Context(), &activityv1.ClaimInviteActivityRewardRequest{
		Meta:       httpkit.ActivityMeta(request),
		UserId:     auth.UserIDFromContext(request.Context()),
		RewardType: rewardType,
		TierId:     body.TierID,
		CommandId:  commandID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	httpkit.WriteOK(writer, request, map[string]any{
		"claim":  inviteActivityRewardClaimFromProto(resp.GetClaim()),
		"status": inviteActivityRewardStatusFromProto(resp.GetStatus()),
	})
}

// listInviteActivityLeaderboard 返回当前邀请活动有效人数月榜；头像昵称只在 gateway 出口向 user-service 批量补齐。
func (h *Handler) listInviteActivityLeaderboard(writer http.ResponseWriter, request *http.Request) {
	if h.inviteActivityReward == nil || h.userProfileClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	page := int32(queryInt(request, "page", 1))
	pageSize := int32(queryInt(request, "page_size", 50))
	if pageSize > 100 {
		pageSize = 100
	}
	resp, err := h.inviteActivityReward.ListInviteActivityLeaderboard(request.Context(), &activityv1.ListInviteActivityLeaderboardRequest{
		Meta:     httpkit.ActivityMeta(request),
		CycleKey: strings.TrimSpace(request.URL.Query().Get("cycle_key")),
		Page:     page,
		PageSize: pageSize,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	users := h.resolveInviteActivityLeaderboardUsers(request, inviteActivityLeaderboardUserIDs(resp.GetEntries()))
	httpkit.WriteOK(writer, request, inviteActivityLeaderboardData{
		Entries:      inviteActivityLeaderboardEntriesFromProto(resp.GetEntries(), users),
		Total:        resp.GetTotal(),
		Page:         page,
		PageSize:     pageSize,
		CycleKey:     resp.GetCycleKey(),
		ServerTimeMS: resp.GetServerTimeMs(),
	})
}

// listInviteActivityInvitees 只用鉴权上下文里的 user_id 查询当前月明细；URL 不接收目标用户，避免横向读取。
func (h *Handler) listInviteActivityInvitees(writer http.ResponseWriter, request *http.Request) {
	inviteeClient, ok := h.inviteActivityReward.(client.InviteActivityInviteeClient)
	if !ok {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	page := int32(queryInt(request, "page", 1))
	pageSize := int32(queryInt(request, "page_size", 50))
	if pageSize > 100 {
		pageSize = 100
	}
	resp, err := inviteeClient.ListInviteActivityInvitees(request.Context(), &activityv1.ListInviteActivityInviteesRequest{
		Meta:     httpkit.ActivityMeta(request),
		UserId:   auth.UserIDFromContext(request.Context()),
		Page:     page,
		PageSize: pageSize,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	users := h.resolveInviteActivityLeaderboardUsers(request, inviteActivityInviteeUserIDs(resp.GetInvitees()))
	httpkit.WriteOK(writer, request, inviteActivityInviteeListData{
		Invitees:     inviteActivityInviteesFromProto(resp.GetInvitees(), users),
		Total:        resp.GetTotal(),
		Page:         resp.GetPage(),
		PageSize:     resp.GetPageSize(),
		CycleKey:     resp.GetCycleKey(),
		ServerTimeMS: resp.GetServerTimeMs(),
	})
}

func inviteActivityRewardStatusFromProto(item *activityv1.InviteActivityRewardStatus) inviteActivityRewardStatusData {
	if item == nil {
		return inviteActivityRewardStatusData{
			Tiers:  []inviteActivityRewardTierData{},
			Claims: []inviteActivityRewardClaimData{},
		}
	}
	claims := make([]inviteActivityRewardClaimData, 0, len(item.GetClaims()))
	grantedByTier := map[int64]bool{}
	var grantedRechargeReward int64
	for _, claim := range item.GetClaims() {
		converted := inviteActivityRewardClaimFromProto(claim)
		claims = append(claims, converted)
		if converted.Status == "granted" {
			grantedByTier[converted.TierID] = true
			if converted.RewardType == "recharge" {
				grantedRechargeReward += converted.RewardCoinAmount
			}
		}
	}
	progress := inviteActivityRewardProgressFromProto(item.GetProgress())
	tiers := make([]inviteActivityRewardTierData, 0, len(item.GetConfig().GetTiers()))
	for _, tier := range item.GetConfig().GetTiers() {
		converted := inviteActivityRewardTierFromProto(tier, progress, grantedByTier)
		tiers = append(tiers, converted)
		if converted.RewardType == "recharge" && converted.Reached {
			progress.RechargeHighestTierID = converted.TierID
			// 有效人数奖励和累计充值奖励同表发放，但累计充值只能按本类型历史发放额计算最高档差额；否则先领了人数奖励会误抵扣充值档。
			if !converted.Claimed && converted.RewardCoinAmount > grantedRechargeReward {
				progress.RechargeClaimableTierID = converted.TierID
				progress.RechargeClaimableCoin = converted.RewardCoinAmount - grantedRechargeReward
			}
		}
		if converted.RewardType == "valid_invite" && converted.Claimable {
			progress.ValidInviteClaimableCount++
		}
	}
	for index := range tiers {
		if tiers[index].RewardType == "recharge" {
			tiers[index].Claimable = tiers[index].TierID == progress.RechargeClaimableTierID
		}
	}
	return inviteActivityRewardStatusData{
		Enabled:                          item.GetConfig().GetEnabled(),
		PerInviteInviterRewardCoinAmount: item.GetConfig().GetPerInviteInviterRewardCoinAmount(),
		PerInviteInviteeRewardCoinAmount: item.GetConfig().GetPerInviteInviteeRewardCoinAmount(),
		CycleKey:                         item.GetCycleKey(),
		Period: cumulativeRechargeRewardPeriodData{
			StartMS: item.GetPeriodStartMs(),
			EndMS:   item.GetPeriodEndMs(),
		},
		Tiers:        tiers,
		Progress:     progress,
		Claims:       claims,
		ServerTimeMS: item.GetServerTimeMs(),
	}
}

func (h *Handler) resolveInviteActivityLeaderboardUsers(request *http.Request, ids []int64) map[int64]inviteActivityLeaderboardUserData {
	users := make(map[int64]inviteActivityLeaderboardUserData)
	ids = uniquePositiveIDs(ids)
	if len(ids) == 0 || h.userProfileClient == nil {
		return users
	}
	resp, err := h.userProfileClient.BatchGetUsers(request.Context(), &userv1.BatchGetUsersRequest{
		Meta:    httpkit.UserMeta(request, ""),
		UserIds: ids,
	})
	if err != nil {
		return users
	}
	for _, item := range resp.GetUsers() {
		users[item.GetUserId()] = inviteActivityLeaderboardUserData{
			UserID:        strconv.FormatInt(item.GetUserId(), 10),
			DisplayUserID: item.GetDisplayUserId(),
			Username:      item.GetUsername(),
			Avatar:        item.GetAvatar(),
			CreatedAtMS:   item.GetCreatedAtMs(),
		}
	}
	return users
}

func inviteActivityInviteesFromProto(items []*activityv1.InviteActivityRewardInviteeSummary, users map[int64]inviteActivityLeaderboardUserData) []inviteActivityInviteeData {
	invitees := make([]inviteActivityInviteeData, 0, len(items))
	for _, item := range items {
		if item == nil || item.GetInvitedUserId() <= 0 {
			continue
		}
		invitee := inviteActivityInviteeData{
			UserID:             strconv.FormatInt(item.GetInvitedUserId(), 10),
			RechargeCoinAmount: item.GetRechargeCoinAmount(),
			InvitedAtMS:        item.GetInvitedAtMs(),
		}
		if user, ok := users[item.GetInvitedUserId()]; ok {
			userCopy := user
			invitee.User = &userCopy
		}
		invitees = append(invitees, invitee)
	}
	return invitees
}

func inviteActivityInviteeUserIDs(items []*activityv1.InviteActivityRewardInviteeSummary) []int64 {
	ids := make([]int64, 0, len(items))
	for _, item := range items {
		if item != nil && item.GetInvitedUserId() > 0 {
			ids = append(ids, item.GetInvitedUserId())
		}
	}
	return ids
}

func inviteActivityLeaderboardEntriesFromProto(items []*activityv1.InviteActivityLeaderboardEntry, users map[int64]inviteActivityLeaderboardUserData) []inviteActivityLeaderboardEntryData {
	entries := make([]inviteActivityLeaderboardEntryData, 0, len(items))
	for _, item := range items {
		if item == nil {
			continue
		}
		entry := inviteActivityLeaderboardEntryData{
			RankNo:                  item.GetRankNo(),
			UserID:                  strconv.FormatInt(item.GetUserId(), 10),
			ValidInviteCount:        item.GetValidInviteCount(),
			TotalRechargeCoinAmount: item.GetTotalRechargeCoinAmount(),
		}
		if user, ok := users[item.GetUserId()]; ok {
			entry.User = &user
		}
		entries = append(entries, entry)
	}
	return entries
}

func inviteActivityLeaderboardUserIDs(items []*activityv1.InviteActivityLeaderboardEntry) []int64 {
	ids := make([]int64, 0, len(items))
	for _, item := range items {
		if item != nil {
			ids = append(ids, item.GetUserId())
		}
	}
	return ids
}

func inviteActivityRewardTierFromProto(item *activityv1.InviteActivityRewardTier, progress inviteActivityRewardProgressData, grantedByTier map[int64]bool) inviteActivityRewardTierData {
	if item == nil {
		return inviteActivityRewardTierData{}
	}
	reachedValue := progress.ValidInviteCount
	threshold := item.GetThresholdValidInviteCount()
	if item.GetRewardType() == "recharge" {
		reachedValue = progress.TotalRechargeCoinAmount
		threshold = item.GetThresholdCoinAmount()
	}
	reached := threshold > 0 && reachedValue >= threshold
	claimed := grantedByTier[item.GetTierId()]
	return inviteActivityRewardTierData{
		TierID:                    item.GetTierId(),
		RewardType:                item.GetRewardType(),
		TierCode:                  item.GetTierCode(),
		TierName:                  item.GetTierName(),
		ThresholdCoinAmount:       item.GetThresholdCoinAmount(),
		ThresholdValidInviteCount: item.GetThresholdValidInviteCount(),
		RewardCoinAmount:          item.GetRewardCoinAmount(),
		Status:                    item.GetStatus(),
		SortOrder:                 item.GetSortOrder(),
		Reached:                   reached,
		Claimable:                 reached && !claimed,
		Claimed:                   claimed,
	}
}

func inviteActivityRewardProgressFromProto(item *activityv1.InviteActivityRewardProgress) inviteActivityRewardProgressData {
	if item == nil {
		return inviteActivityRewardProgressData{}
	}
	return inviteActivityRewardProgressData{
		CycleKey:                item.GetCycleKey(),
		UserID:                  item.GetUserId(),
		TotalRechargeCoinAmount: item.GetTotalRechargeCoinAmount(),
		ValidInviteCount:        item.GetValidInviteCount(),
		ClaimedRewardCoinAmount: item.GetClaimedRewardCoinAmount(),
		UpdatedAtMS:             item.GetUpdatedAtMs(),
	}
}

func inviteActivityRewardClaimFromProto(item *activityv1.InviteActivityRewardClaim) inviteActivityRewardClaimData {
	if item == nil {
		return inviteActivityRewardClaimData{}
	}
	return inviteActivityRewardClaimData{
		ClaimID:                   item.GetClaimId(),
		CycleKey:                  item.GetCycleKey(),
		UserID:                    item.GetUserId(),
		RewardType:                item.GetRewardType(),
		CommandID:                 item.GetCommandId(),
		TierID:                    item.GetTierId(),
		TierCode:                  item.GetTierCode(),
		TierName:                  item.GetTierName(),
		ThresholdCoinAmount:       item.GetThresholdCoinAmount(),
		ThresholdValidInviteCount: item.GetThresholdValidInviteCount(),
		ReachedValue:              item.GetReachedValue(),
		RewardCoinAmount:          item.GetRewardCoinAmount(),
		Status:                    item.GetStatus(),
		WalletCommandID:           item.GetWalletCommandId(),
		WalletTransactionID:       item.GetWalletTransactionId(),
		FailureReason:             item.GetFailureReason(),
		GrantedAtMS:               item.GetGrantedAtMs(),
		CreatedAtMS:               item.GetCreatedAtMs(),
		UpdatedAtMS:               item.GetUpdatedAtMs(),
	}
}
