package activityapi

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

	activityv1 "hyapp.local/api/proto/activity/v1"
	userv1 "hyapp.local/api/proto/user/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 weeklyStarCycleData struct {
	CycleID      string                 `json:"cycle_id"`
	ActivityCode string                 `json:"activity_code"`
	RegionID     int64                  `json:"region_id"`
	Title        string                 `json:"title"`
	Status       string                 `json:"status"`
	StartMS      int64                  `json:"start_ms"`
	EndMS        int64                  `json:"end_ms"`
	Gifts        []weeklyStarGiftData   `json:"gifts"`
	Rewards      []weeklyStarRewardData `json:"rewards"`
	SettledAtMS  int64                  `json:"settled_at_ms,omitempty"`
	CreatedAtMS  int64                  `json:"created_at_ms,omitempty"`
	UpdatedAtMS  int64                  `json:"updated_at_ms,omitempty"`
}

type weeklyStarGiftData struct {
	GiftID       string                   `json:"gift_id"`
	ResourceID   int64                    `json:"resource_id,omitempty"`
	Resource     *achievementResourceData `json:"resource,omitempty"`
	Name         string                   `json:"name,omitempty"`
	ImageURL     string                   `json:"image_url,omitempty"`
	CoverURL     string                   `json:"cover_url,omitempty"`
	AssetURL     string                   `json:"asset_url,omitempty"`
	PreviewURL   string                   `json:"preview_url,omitempty"`
	AnimationURL string                   `json:"animation_url,omitempty"`
	SortOrder    int32                    `json:"sort_order"`
}

type weeklyStarRewardData struct {
	RankNo          int32                         `json:"rank_no"`
	ResourceGroupID int64                         `json:"resource_group_id"`
	ResourceGroup   *achievementResourceGroupData `json:"resource_group,omitempty"`
}

type weeklyStarEntryData struct {
	RankNo          int32               `json:"rank_no"`
	UserID          string              `json:"user_id"`
	Score           int64               `json:"score"`
	FirstScoredAtMS int64               `json:"first_scored_at_ms"`
	LastScoredAtMS  int64               `json:"last_scored_at_ms"`
	User            *weeklyStarUserData `json:"user,omitempty"`
}

type weeklyStarUserData struct {
	UserID        string `json:"user_id"`
	DisplayUserID string `json:"display_user_id"`
	Username      string `json:"username"`
	Avatar        string `json:"avatar"`
	RegionID      int64  `json:"region_id,omitempty"`
}

type weeklyStarCurrentData struct {
	Cycle        *weeklyStarCycleData  `json:"cycle,omitempty"`
	TopEntries   []weeklyStarEntryData `json:"top_entries"`
	MyEntry      *weeklyStarEntryData  `json:"my_entry,omitempty"`
	ServerTimeMS int64                 `json:"server_time_ms"`
}

type weeklyStarLeaderboardData struct {
	Cycle         *weeklyStarCycleData  `json:"cycle,omitempty"`
	Entries       []weeklyStarEntryData `json:"entries"`
	NextPageToken string                `json:"next_page_token,omitempty"`
	Total         int64                 `json:"total"`
}

type weeklyStarHistoryData struct {
	Cycles       []weeklyStarHistoryCycleData `json:"cycles"`
	ServerTimeMS int64                        `json:"server_time_ms"`
}

type weeklyStarHistoryCycleData struct {
	Cycle      *weeklyStarCycleData  `json:"cycle,omitempty"`
	TopEntries []weeklyStarEntryData `json:"top_entries"`
}

// getWeeklyStarCurrent returns the current region/default weekly-star cycle for the logged-in user.
func (h *Handler) getWeeklyStarCurrent(writer http.ResponseWriter, request *http.Request) {
	if h.weeklyStar == nil || h.userProfileClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	userID := auth.UserIDFromContext(request.Context())
	regionID, ok := h.resolveWeeklyStarRegionID(writer, request, userID)
	if !ok {
		return
	}
	resp, err := h.weeklyStar.GetWeeklyStarCurrent(request.Context(), &activityv1.GetWeeklyStarCurrentRequest{
		Meta:     httpkit.ActivityMeta(request),
		UserId:   userID,
		RegionId: regionID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	users := h.resolveWeeklyStarUsers(request, weeklyStarUserIDs(resp.GetTopEntries(), resp.GetMyEntry()))
	resourceGroups, ok := h.resolveWeeklyStarResourceGroups(writer, request, resp.GetCycle())
	if !ok {
		return
	}
	gifts, ok := h.resolveWeeklyStarGifts(writer, request, resp.GetCycle())
	if !ok {
		return
	}
	data := weeklyStarCurrentData{
		Cycle:        weeklyStarCycleFromProto(resp.GetCycle(), gifts, resourceGroups),
		TopEntries:   weeklyStarEntriesFromProto(resp.GetTopEntries(), users),
		ServerTimeMS: resp.GetServerTimeMs(),
	}
	if resp.GetMyEntry() != nil {
		entry := weeklyStarEntryFromProto(resp.GetMyEntry(), users)
		data.MyEntry = &entry
	}
	httpkit.WriteOK(writer, request, data)
}

// listWeeklyStarLeaderboard pages through the selected cycle or current region cycle.
func (h *Handler) listWeeklyStarLeaderboard(writer http.ResponseWriter, request *http.Request) {
	if h.weeklyStar == nil || h.userProfileClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	userID := auth.UserIDFromContext(request.Context())
	regionID, ok := h.resolveWeeklyStarRegionID(writer, request, userID)
	if !ok {
		return
	}
	resp, err := h.weeklyStar.ListWeeklyStarLeaderboard(request.Context(), &activityv1.ListWeeklyStarLeaderboardRequest{
		Meta:      httpkit.ActivityMeta(request),
		CycleId:   strings.TrimSpace(request.URL.Query().Get("cycle_id")),
		RegionId:  regionID,
		PageSize:  int32(queryInt(request, "page_size", 50)),
		PageToken: strings.TrimSpace(request.URL.Query().Get("page_token")),
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	users := h.resolveWeeklyStarUsers(request, weeklyStarUserIDs(resp.GetEntries(), nil))
	resourceGroups, ok := h.resolveWeeklyStarResourceGroups(writer, request, resp.GetCycle())
	if !ok {
		return
	}
	gifts, ok := h.resolveWeeklyStarGifts(writer, request, resp.GetCycle())
	if !ok {
		return
	}
	httpkit.WriteOK(writer, request, weeklyStarLeaderboardData{
		Cycle:         weeklyStarCycleFromProto(resp.GetCycle(), gifts, resourceGroups),
		Entries:       weeklyStarEntriesFromProto(resp.GetEntries(), users),
		NextPageToken: resp.GetNextPageToken(),
		Total:         resp.GetTotal(),
	})
}

// listWeeklyStarHistory returns recent settled cycles for the current user's region with default fallback.
func (h *Handler) listWeeklyStarHistory(writer http.ResponseWriter, request *http.Request) {
	if h.weeklyStar == nil || h.userProfileClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	userID := auth.UserIDFromContext(request.Context())
	regionID, ok := h.resolveWeeklyStarRegionID(writer, request, userID)
	if !ok {
		return
	}
	resp, err := h.weeklyStar.ListWeeklyStarHistory(request.Context(), &activityv1.ListWeeklyStarHistoryRequest{
		Meta:     httpkit.ActivityMeta(request),
		RegionId: regionID,
		Limit:    int32(queryInt(request, "limit", 10)),
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	ids := make([]int64, 0)
	cycleItems := make([]*activityv1.WeeklyStarCycle, 0, len(resp.GetCycles()))
	for _, cycle := range resp.GetCycles() {
		ids = append(ids, weeklyStarUserIDs(cycle.GetTopEntries(), nil)...)
		cycleItems = append(cycleItems, cycle.GetCycle())
	}
	users := h.resolveWeeklyStarUsers(request, ids)
	resourceGroups, ok := h.resolveWeeklyStarResourceGroups(writer, request, cycleItems...)
	if !ok {
		return
	}
	gifts, ok := h.resolveWeeklyStarGifts(writer, request, cycleItems...)
	if !ok {
		return
	}
	cycles := make([]weeklyStarHistoryCycleData, 0, len(resp.GetCycles()))
	for _, item := range resp.GetCycles() {
		cycles = append(cycles, weeklyStarHistoryCycleData{
			Cycle:      weeklyStarCycleFromProto(item.GetCycle(), gifts, resourceGroups),
			TopEntries: weeklyStarEntriesFromProto(item.GetTopEntries(), users),
		})
	}
	httpkit.WriteOK(writer, request, weeklyStarHistoryData{Cycles: cycles, ServerTimeMS: resp.GetServerTimeMs()})
}

func (h *Handler) resolveWeeklyStarRegionID(writer http.ResponseWriter, request *http.Request, userID int64) (int64, bool) {
	resp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
		Meta:   httpkit.UserMeta(request, ""),
		UserId: userID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return 0, false
	}
	return resp.GetUser().GetRegionId(), true
}

func (h *Handler) resolveWeeklyStarUsers(request *http.Request, ids []int64) map[int64]weeklyStarUserData {
	users := make(map[int64]weeklyStarUserData)
	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()] = weeklyStarUserData{
			UserID:        strconv.FormatInt(item.GetUserId(), 10),
			DisplayUserID: item.GetDisplayUserId(),
			Username:      item.GetUsername(),
			Avatar:        item.GetAvatar(),
			RegionID:      item.GetRegionId(),
		}
	}
	return users
}

func (h *Handler) resolveWeeklyStarResourceGroups(writer http.ResponseWriter, request *http.Request, cycles ...*activityv1.WeeklyStarCycle) (map[int64]achievementResourceGroupData, bool) {
	// 周星周期只存奖励资源组 ID，资源名称、类型、素材和数量仍由 wallet-service 资源库统一维护。
	// gateway 在 App 出口补齐这些展示字段，避免 activity-service 复制资源库快照，也让后台改资源组后 App 读取保持一致。
	return h.resolveAchievementResourceGroups(writer, request, weeklyStarResourceGroupIDs(cycles...))
}

func (h *Handler) resolveWeeklyStarGifts(writer http.ResponseWriter, request *http.Request, cycles ...*activityv1.WeeklyStarCycle) (map[string]weeklyStarGiftData, bool) {
	ids := weeklyStarGiftIDs(cycles...)
	gifts := make(map[string]weeklyStarGiftData, len(ids))
	if len(ids) == 0 {
		return gifts, true
	}
	if h.walletClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return nil, false
	}

	// 周星只保存活动礼物 ID，封面图和名称来自 wallet-service 的礼物配置。
	// ListGiftConfigs 当前没有按 gift_id 批量查询，所以这里分页拉取 active 礼物，并在命中全部活动礼物后立即停止。
	const pageSize int32 = 500
	remaining := make(map[string]struct{}, len(ids))
	for _, id := range ids {
		remaining[id] = struct{}{}
	}
	for page := int32(1); ; page++ {
		resp, err := h.walletClient.ListGiftConfigs(request.Context(), &walletv1.ListGiftConfigsRequest{
			RequestId:  httpkit.RequestIDFromContext(request.Context()),
			AppCode:    appcode.FromContext(request.Context()),
			Page:       page,
			PageSize:   pageSize,
			ActiveOnly: true,
		})
		if err != nil {
			httpkit.WriteRPCError(writer, request, err)
			return nil, false
		}
		for _, gift := range resp.GetGifts() {
			id := strings.TrimSpace(gift.GetGiftId())
			if _, needed := remaining[id]; !needed {
				continue
			}
			gifts[id] = weeklyStarGiftFromProto(gift)
			delete(remaining, id)
		}
		if len(remaining) == 0 || len(resp.GetGifts()) == 0 || int64(page)*int64(pageSize) >= resp.GetTotal() {
			return gifts, true
		}
	}
}

func weeklyStarCycleFromProto(item *activityv1.WeeklyStarCycle, gifts map[string]weeklyStarGiftData, resourceGroups map[int64]achievementResourceGroupData) *weeklyStarCycleData {
	if item == nil {
		return nil
	}
	cycle := &weeklyStarCycleData{
		CycleID:      item.GetCycleId(),
		ActivityCode: item.GetActivityCode(),
		RegionID:     item.GetRegionId(),
		Title:        item.GetTitle(),
		Status:       item.GetStatus(),
		StartMS:      item.GetStartMs(),
		EndMS:        item.GetEndMs(),
		Gifts:        make([]weeklyStarGiftData, 0, len(item.GetGifts())),
		Rewards:      make([]weeklyStarRewardData, 0, len(item.GetRewards())),
		SettledAtMS:  item.GetSettledAtMs(),
		CreatedAtMS:  item.GetCreatedAtMs(),
		UpdatedAtMS:  item.GetUpdatedAtMs(),
	}
	for _, gift := range item.GetGifts() {
		giftData := weeklyStarGiftData{GiftID: gift.GetGiftId(), SortOrder: gift.GetSortOrder()}
		if enriched, ok := gifts[strings.TrimSpace(gift.GetGiftId())]; ok {
			giftData = enriched
			giftData.SortOrder = gift.GetSortOrder()
		}
		cycle.Gifts = append(cycle.Gifts, giftData)
	}
	for _, reward := range item.GetRewards() {
		rewardData := weeklyStarRewardData{RankNo: reward.GetRankNo(), ResourceGroupID: reward.GetResourceGroupId()}
		if group, ok := resourceGroups[reward.GetResourceGroupId()]; ok {
			rewardData.ResourceGroup = &group
		}
		cycle.Rewards = append(cycle.Rewards, rewardData)
	}
	return cycle
}

func weeklyStarGiftFromProto(gift *walletv1.GiftConfig) weeklyStarGiftData {
	resource := achievementResourceFromProto(gift.GetResource())
	imageURL := weeklyStarFirstNonEmpty(resource.PreviewURL, resource.AssetURL, resource.AnimationURL)
	return weeklyStarGiftData{
		GiftID:       gift.GetGiftId(),
		ResourceID:   gift.GetResourceId(),
		Resource:     achievementResourcePointerFromProto(gift.GetResource()),
		Name:         gift.GetName(),
		ImageURL:     imageURL,
		CoverURL:     imageURL,
		AssetURL:     resource.AssetURL,
		PreviewURL:   resource.PreviewURL,
		AnimationURL: resource.AnimationURL,
		SortOrder:    gift.GetSortOrder(),
	}
}

func weeklyStarGiftIDs(cycles ...*activityv1.WeeklyStarCycle) []string {
	seen := make(map[string]struct{})
	ids := make([]string, 0)
	for _, cycle := range cycles {
		for _, gift := range cycle.GetGifts() {
			id := strings.TrimSpace(gift.GetGiftId())
			if id == "" {
				continue
			}
			if _, ok := seen[id]; ok {
				continue
			}
			seen[id] = struct{}{}
			ids = append(ids, id)
		}
	}
	return ids
}

func weeklyStarEntriesFromProto(items []*activityv1.WeeklyStarLeaderboardEntry, users map[int64]weeklyStarUserData) []weeklyStarEntryData {
	entries := make([]weeklyStarEntryData, 0, len(items))
	for _, item := range items {
		entries = append(entries, weeklyStarEntryFromProto(item, users))
	}
	return entries
}

func weeklyStarEntryFromProto(item *activityv1.WeeklyStarLeaderboardEntry, users map[int64]weeklyStarUserData) weeklyStarEntryData {
	entry := weeklyStarEntryData{
		RankNo:          item.GetRankNo(),
		UserID:          strconv.FormatInt(item.GetUserId(), 10),
		Score:           item.GetScore(),
		FirstScoredAtMS: item.GetFirstScoredAtMs(),
		LastScoredAtMS:  item.GetLastScoredAtMs(),
	}
	if user, ok := users[item.GetUserId()]; ok {
		entry.User = &user
	}
	return entry
}

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

func weeklyStarResourceGroupIDs(cycles ...*activityv1.WeeklyStarCycle) []int64 {
	ids := make([]int64, 0)
	for _, cycle := range cycles {
		for _, reward := range cycle.GetRewards() {
			ids = append(ids, reward.GetResourceGroupId())
		}
	}
	return ids
}

func weeklyStarFirstNonEmpty(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return value
		}
	}
	return ""
}

func queryInt(request *http.Request, key string, fallback int) int {
	value, err := strconv.Atoi(strings.TrimSpace(request.URL.Query().Get(key)))
	if err != nil || value <= 0 {
		return fallback
	}
	return value
}
