package activityapi

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

	"hyapp/pkg/appcode"
	"hyapp/pkg/userleaderboard"
	"hyapp/services/gateway-service/internal/auth"
	"hyapp/services/gateway-service/internal/transport/http/httpkit"

	roomv1 "hyapp.local/api/proto/room/v1"
	userv1 "hyapp.local/api/proto/user/v1"
)

const (
	leaderboardTypeSent     = userleaderboard.BoardSent
	leaderboardTypeReceived = userleaderboard.BoardReceived
	leaderboardTypeRoom     = "room"

	leaderboardPeriodToday = userleaderboard.PeriodToday
	leaderboardPeriodWeek  = userleaderboard.PeriodWeek
	leaderboardPeriodMonth = userleaderboard.PeriodMonth

	userLeaderboardMaxPageSize = 100
)

type userLeaderboardResponse struct {
	Items        []userLeaderboardItem `json:"items"`
	Total        int64                 `json:"total"`
	Page         int32                 `json:"page"`
	PageSize     int32                 `json:"page_size"`
	BoardType    string                `json:"board_type"`
	Period       string                `json:"period"`
	StartAtMS    int64                 `json:"start_at_ms"`
	EndAtMS      int64                 `json:"end_at_ms"`
	ServerTimeMS int64                 `json:"server_time_ms"`
	MyRank       *userLeaderboardItem  `json:"my_rank,omitempty"`
}

type userLeaderboardItem struct {
	Rank             int64                `json:"rank"`
	UserID           string               `json:"user_id,omitempty"`
	RoomID           string               `json:"room_id,omitempty"`
	GiftValue        int64                `json:"gift_value"`
	GiftCount        int64                `json:"gift_count"`
	TransactionCount int64                `json:"transaction_count"`
	LastGiftAtMS     int64                `json:"last_gift_at_ms"`
	User             *userLeaderboardUser `json:"user,omitempty"`
	Room             *userLeaderboardRoom `json:"room,omitempty"`
}

type userLeaderboardUser struct {
	UserID        string `json:"user_id"`
	DisplayUserID string `json:"display_user_id,omitempty"`
	Username      string `json:"username,omitempty"`
	Avatar        string `json:"avatar,omitempty"`
}

type userLeaderboardRoom struct {
	RoomID      string `json:"room_id"`
	RoomShortID string `json:"room_short_id,omitempty"`
	Title       string `json:"title,omitempty"`
	CoverURL    string `json:"cover_url,omitempty"`
	RoomAvatar  string `json:"room_avatar,omitempty"`
}

func (h *Handler) listUserLeaderboards(writer http.ResponseWriter, request *http.Request) {
	boardType := normalizeUserLeaderboardType(request.URL.Query().Get("board_type"))
	if boardType == "" {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		return
	}
	period := normalizeUserLeaderboardPeriod(request.URL.Query().Get("period"))
	if period == "" {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
		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
	}
	if pageSize > userLeaderboardMaxPageSize {
		pageSize = userLeaderboardMaxPageSize
	}
	if boardType == leaderboardTypeRoom {
		h.listRoomGiftLeaderboards(writer, request, period, page, pageSize)
		return
	}
	if h.userLeaderboard == nil {
		httpkit.WriteError(writer, request, http.StatusServiceUnavailable, httpkit.CodeUpstreamError, "upstream service error")
		return
	}

	now := time.Now().UTC()
	app := appcode.FromContext(request.Context())
	query := userleaderboard.Query{
		AppCode:   app,
		BoardType: boardType,
		Period:    period,
		Page:      int(page),
		PageSize:  int(pageSize),
		Now:       now,
	}
	result, err := h.userLeaderboard.List(request.Context(), query)
	if err != nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	items := userLeaderboardItemsFromRedisEntries(result.Items)

	var myRank *userLeaderboardItem
	viewerUserID := auth.UserIDFromContext(request.Context())
	if viewerUserID > 0 && boardType != leaderboardTypeRoom {
		entry, found, err := h.userLeaderboard.RankForUser(request.Context(), query, viewerUserID)
		if err != nil {
			httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
			return
		}
		if found {
			item := userLeaderboardItemFromRedisEntry(entry)
			myRank = &item
		}
	}
	if err := h.enrichUserLeaderboardItems(request, boardType, items, myRank); err != nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}

	httpkit.WriteOK(writer, request, userLeaderboardResponse{
		Items:        items,
		Total:        result.Total,
		Page:         page,
		PageSize:     pageSize,
		BoardType:    result.BoardType,
		Period:       result.Period,
		StartAtMS:    result.StartAtMS,
		EndAtMS:      result.EndAtMS,
		ServerTimeMS: result.ServerTimeMS,
		MyRank:       myRank,
	})
}

func (h *Handler) listRoomGiftLeaderboards(writer http.ResponseWriter, request *http.Request, period string, page int32, pageSize int32) {
	if h.roomQueryClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	resp, err := h.roomQueryClient.ListRoomGiftLeaderboard(request.Context(), &roomv1.ListRoomGiftLeaderboardRequest{
		Meta:     httpkit.RoomMeta(request, "", ""),
		Period:   period,
		Page:     page,
		PageSize: pageSize,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	httpkit.WriteOK(writer, request, userLeaderboardResponse{
		Items:        userLeaderboardItemsFromRoomGiftLeaderboard(resp.GetItems()),
		Total:        resp.GetTotal(),
		Page:         page,
		PageSize:     pageSize,
		BoardType:    leaderboardTypeRoom,
		Period:       resp.GetPeriod(),
		StartAtMS:    resp.GetStartAtMs(),
		EndAtMS:      resp.GetEndAtMs(),
		ServerTimeMS: resp.GetServerTimeMs(),
	})
}

func normalizeUserLeaderboardType(raw string) string {
	switch strings.ToLower(strings.TrimSpace(raw)) {
	case "room", "rooms", "room_gift", "room_gifts":
		return leaderboardTypeRoom
	default:
		return userleaderboard.NormalizeBoardType(raw)
	}
}

func normalizeUserLeaderboardPeriod(raw string) string {
	return userleaderboard.NormalizePeriod(raw)
}

func userLeaderboardPeriodWindow(period string, now time.Time) (time.Time, time.Time) {
	return userleaderboard.QueryWindow(period, now)
}

func userLeaderboardItemsFromRedisEntries(entries []userleaderboard.Entry) []userLeaderboardItem {
	out := make([]userLeaderboardItem, 0, len(entries))
	for _, entry := range entries {
		out = append(out, userLeaderboardItemFromRedisEntry(entry))
	}
	return out
}

func userLeaderboardItemFromRedisEntry(entry userleaderboard.Entry) userLeaderboardItem {
	return userLeaderboardItem{
		Rank:             entry.Rank,
		UserID:           entry.UserID,
		GiftValue:        entry.GiftValue,
		GiftCount:        entry.GiftCount,
		TransactionCount: entry.TransactionCount,
		LastGiftAtMS:     entry.LastGiftAtMS,
		User:             &userLeaderboardUser{UserID: entry.UserID},
	}
}

func (h *Handler) enrichUserLeaderboardItems(request *http.Request, boardType string, items []userLeaderboardItem, myRank *userLeaderboardItem) error {
	if boardType == leaderboardTypeRoom {
		// room 榜已经由 room-service 的 zset + room_list_entries 返回卡片，不再读取完整 RoomSnapshot。
		return nil
	}
	userIDs := make([]int64, 0, len(items)+1)
	for _, item := range items {
		if id, err := strconv.ParseInt(item.UserID, 10, 64); err == nil && id > 0 {
			userIDs = append(userIDs, id)
		}
	}
	if myRank != nil {
		if id, err := strconv.ParseInt(myRank.UserID, 10, 64); err == nil && id > 0 {
			userIDs = append(userIDs, id)
		}
	}
	profiles, err := h.userLeaderboardProfiles(request, userIDs)
	if err != nil {
		return err
	}
	for i := range items {
		applyUserLeaderboardProfile(&items[i], profiles)
	}
	if myRank != nil {
		applyUserLeaderboardProfile(myRank, profiles)
	}
	return nil
}

func userLeaderboardItemsFromRoomGiftLeaderboard(items []*roomv1.RoomGiftLeaderboardItem) []userLeaderboardItem {
	out := make([]userLeaderboardItem, 0, len(items))
	for _, item := range items {
		if item == nil {
			continue
		}
		roomID := item.GetRoomId()
		room := userLeaderboardRoomFromListItem(item.GetRoom())
		if room == nil {
			room = &userLeaderboardRoom{RoomID: roomID}
		}
		out = append(out, userLeaderboardItem{
			Rank:      item.GetRank(),
			RoomID:    roomID,
			GiftValue: item.GetCoinSpent(),
			Room:      room,
		})
	}
	return out
}

func userLeaderboardRoomFromListItem(room *roomv1.RoomListItem) *userLeaderboardRoom {
	if room == nil {
		return nil
	}
	roomID := room.GetRoomId()
	if roomID == "" {
		return nil
	}
	return &userLeaderboardRoom{
		RoomID:      roomID,
		RoomShortID: room.GetRoomShortId(),
		Title:       room.GetTitle(),
		CoverURL:    room.GetCoverUrl(),
		RoomAvatar:  room.GetCoverUrl(),
	}
}

func roomLeaderboardRoomFromSnapshot(roomID string, snapshot *roomv1.RoomSnapshot) *userLeaderboardRoom {
	room := &userLeaderboardRoom{RoomID: roomID}
	if snapshot == nil {
		return room
	}
	if snapshot.GetRoomId() != "" {
		room.RoomID = snapshot.GetRoomId()
	}
	ext := snapshot.GetRoomExt()
	room.RoomShortID = snapshot.GetRoomShortId()
	if room.RoomShortID == "" {
		room.RoomShortID = ext["room_short_id"]
	}
	room.Title = ext["title"]
	room.CoverURL = ext["cover_url"]
	room.RoomAvatar = room.CoverURL
	return room
}

func (h *Handler) userLeaderboardProfiles(request *http.Request, userIDs []int64) (map[int64]*userv1.User, error) {
	result := make(map[int64]*userv1.User)
	if h.userProfileClient == nil || len(userIDs) == 0 {
		return result, nil
	}
	userIDs = uniqueUserLeaderboardInt64s(userIDs)
	resp, err := h.userProfileClient.BatchGetUsers(request.Context(), &userv1.BatchGetUsersRequest{
		Meta:    httpkit.UserMeta(request, ""),
		UserIds: userIDs,
	})
	if err != nil {
		return nil, err
	}
	return resp.GetUsers(), nil
}

func applyUserLeaderboardProfile(item *userLeaderboardItem, profiles map[int64]*userv1.User) {
	if item == nil {
		return
	}
	id, err := strconv.ParseInt(item.UserID, 10, 64)
	if err != nil || id <= 0 {
		return
	}
	profile := profiles[id]
	if profile == nil {
		return
	}
	item.User = &userLeaderboardUser{
		UserID:        strconv.FormatInt(profile.GetUserId(), 10),
		DisplayUserID: profile.GetDisplayUserId(),
		Username:      profile.GetUsername(),
		Avatar:        profile.GetAvatar(),
	}
}

func uniqueUserLeaderboardInt64s(values []int64) []int64 {
	seen := make(map[int64]struct{}, len(values))
	out := make([]int64, 0, len(values))
	for _, value := range values {
		if value <= 0 {
			continue
		}
		if _, ok := seen[value]; ok {
			continue
		}
		seen[value] = struct{}{}
		out = append(out, value)
	}
	return out
}
