package service

import (
	"context"
	"strings"
	"time"

	"hyapp/pkg/appcode"
	"hyapp/pkg/roomid"
	"hyapp/pkg/xerr"

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

const (
	roomContributionRankPeriodDay   = "day"
	roomContributionRankPeriodWeek  = "week"
	roomContributionRankPeriodMonth = "month"

	defaultRoomContributionRankLimit = 20
	maxRoomContributionRankLimit     = 100
)

type RoomContributionRankQuery struct {
	AppCode       string
	RoomID        string
	Period        string
	PeriodStartMS int64
	Limit         int
}

type RoomContributionRankPage struct {
	Items        []RoomContributionRankEntry
	Total        int64
	Period       string
	StartAtMS    int64
	EndAtMS      int64
	ServerTimeMS int64
}

type RoomContributionRankEntry struct {
	Rank        int64
	UserID      int64
	Score       int64
	GiftValue   int64
	UpdatedAtMS int64
}

// ListRoomContributionRank 读取单房间用户贡献周期榜。
// 查询只访问 MySQL 周期读模型，不扫描 Room Cell、command log 或 outbox。
func (s *Service) ListRoomContributionRank(ctx context.Context, req *roomv1.ListRoomContributionRankRequest) (*roomv1.ListRoomContributionRankResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	roomID := strings.TrimSpace(req.GetRoomId())
	viewerUserID := req.GetViewerUserId()
	if !roomid.ValidStringID(roomID) {
		return nil, xerr.New(xerr.InvalidArgument, "room_id is invalid")
	}
	if viewerUserID <= 0 {
		return nil, xerr.New(xerr.InvalidArgument, "viewer_user_id is required")
	}
	period := normalizeRoomContributionRankPeriod(req.GetPeriod())
	if period == "" {
		return nil, xerr.New(xerr.InvalidArgument, "period is invalid")
	}

	// 周期榜是房间内面板数据，必须确认 viewer 仍在该房间，避免用公开接口枚举任意房间用户贡献。
	presence, exists, err := s.repository.GetCurrentRoomPresence(ctx, viewerUserID)
	if err != nil {
		return nil, err
	}
	if !exists || presence.RoomID != roomID || presence.Status != roomPresenceStatusActive {
		return nil, xerr.New(xerr.PermissionDenied, "viewer is not in room")
	}

	limit := int(req.GetLimit())
	if limit <= 0 {
		limit = defaultRoomContributionRankLimit
	}
	if limit > maxRoomContributionRankLimit {
		limit = maxRoomContributionRankLimit
	}
	now := s.clock.Now().UTC()
	start, end := roomContributionRankWindow(period, now)
	result, err := s.repository.ListRoomContributionRank(ctx, RoomContributionRankQuery{
		AppCode:       appcode.FromContext(ctx),
		RoomID:        roomID,
		Period:        period,
		PeriodStartMS: start.UnixMilli(),
		Limit:         limit,
	})
	if err != nil {
		return nil, err
	}

	items := make([]*roomv1.RoomContributionRankItem, 0, len(result.Items))
	for _, item := range result.Items {
		items = append(items, &roomv1.RoomContributionRankItem{
			Rank:        item.Rank,
			UserId:      item.UserID,
			Score:       item.Score,
			GiftValue:   item.GiftValue,
			UpdatedAtMs: item.UpdatedAtMS,
		})
	}
	return &roomv1.ListRoomContributionRankResponse{
		RoomId:       roomID,
		Period:       period,
		StartAtMs:    start.UnixMilli(),
		EndAtMs:      end.UnixMilli(),
		Items:        items,
		Total:        result.Total,
		ServerTimeMs: now.UnixMilli(),
	}, nil
}

func roomContributionPeriodStatIncrements(app string, roomID string, userID int64, giftValue int64, occurredMS int64) []RoomUserContributionPeriodStatIncrement {
	if userID <= 0 || giftValue <= 0 || strings.TrimSpace(roomID) == "" {
		return nil
	}
	occurredAt := time.UnixMilli(occurredMS).UTC()
	if occurredMS <= 0 {
		occurredAt = time.Now().UTC()
		occurredMS = occurredAt.UnixMilli()
	}
	stats := make([]RoomUserContributionPeriodStatIncrement, 0, 3)
	for _, period := range []string{roomContributionRankPeriodDay, roomContributionRankPeriodWeek, roomContributionRankPeriodMonth} {
		start, end := roomContributionRankWindow(period, occurredAt)
		stats = append(stats, RoomUserContributionPeriodStatIncrement{
			AppCode:       app,
			RoomID:        roomID,
			Period:        period,
			PeriodStartMS: start.UnixMilli(),
			PeriodEndMS:   end.UnixMilli(),
			UserID:        userID,
			GiftValue:     giftValue,
			LastGiftAtMS:  occurredMS,
		})
	}
	return stats
}

func normalizeRoomContributionRankPeriod(raw string) string {
	switch strings.ToLower(strings.TrimSpace(raw)) {
	case "", roomContributionRankPeriodDay, "today", "daily":
		return roomContributionRankPeriodDay
	case roomContributionRankPeriodWeek, "weekly":
		return roomContributionRankPeriodWeek
	case roomContributionRankPeriodMonth, "monthly":
		return roomContributionRankPeriodMonth
	default:
		return ""
	}
}

func roomContributionRankWindow(period string, now time.Time) (time.Time, time.Time) {
	dayStart := time.Date(now.UTC().Year(), now.UTC().Month(), now.UTC().Day(), 0, 0, 0, 0, time.UTC)
	switch period {
	case roomContributionRankPeriodWeek:
		weekday := int(dayStart.Weekday())
		if weekday == 0 {
			weekday = 7
		}
		start := dayStart.AddDate(0, 0, 1-weekday)
		return start, start.AddDate(0, 0, 7)
	case roomContributionRankPeriodMonth:
		start := time.Date(dayStart.Year(), dayStart.Month(), 1, 0, 0, 0, 0, time.UTC)
		return start, start.AddDate(0, 1, 0)
	default:
		return dayStart, dayStart.AddDate(0, 0, 1)
	}
}
