package activitytemplate

import (
	"strconv"
	"strings"
	"time"

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/integration/statisticsclient"
	"hyapp-admin-server/internal/integration/userclient"
	"hyapp-admin-server/internal/middleware"
	"hyapp-admin-server/internal/modules/shared"
	"hyapp-admin-server/internal/response"
	activityv1 "hyapp.local/api/proto/activity/v1"

	"github.com/gin-gonic/gin"
)

const maxActivityTemplateDataRange = 366 * 24 * time.Hour

func (h *Handler) Leaderboard(c *gin.Context) {
	options := shared.ListOptions(c)
	versionNo, ok := positiveInt64Query(c, "version_no", true)
	if !ok {
		response.BadRequest(c, "version_no 参数不正确")
		return
	}
	boardType := strings.ToLower(strings.TrimSpace(c.Query("board_type")))
	periodKey := strings.TrimSpace(c.Query("period_key"))
	if boardType != "daily" && boardType != "total" {
		response.BadRequest(c, "board_type 参数不正确")
		return
	}
	if boardType == "daily" {
		if parsed, err := time.Parse("2006-01-02", periodKey); err != nil || parsed.Format("2006-01-02") != periodKey {
			response.BadRequest(c, "daily 榜单必须提供 YYYY-MM-DD 格式的 period_key")
			return
		}
	} else {
		periodKey = "total"
	}
	resp, err := h.activity.ListActivityTemplateLeaderboard(c.Request.Context(), &activityv1.AdminListActivityTemplateLeaderboardRequest{
		Meta: h.meta(c), TemplateId: strings.TrimSpace(c.Param("template_id")), VersionNo: versionNo,
		BoardType: boardType, PeriodKey: periodKey, Page: int32(options.Page), PageSize: int32(options.PageSize),
	})
	if err != nil {
		h.writeGRPCError(c, err, "获取活动模版榜单失败")
		return
	}
	entries := make([]leaderboardEntryDTO, 0, len(resp.GetEntries()))
	users := h.activityTemplateUsers(c, leaderboardProtoUserIDs(resp.GetEntries()))
	for _, item := range resp.GetEntries() {
		entries = append(entries, leaderboardEntryFromProto(item, users))
	}
	response.OK(c, leaderboardPageDTO{
		Period: rankPeriodFromProto(resp.GetPeriod()), Items: entries,
		Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal(),
	})
}

func (h *Handler) RewardJobs(c *gin.Context) {
	options := shared.ListOptions(c)
	versionNo, ok := positiveInt64Query(c, "version_no", true)
	if !ok {
		response.BadRequest(c, "version_no 参数不正确")
		return
	}
	boardType := strings.ToLower(strings.TrimSpace(c.Query("board_type")))
	if boardType != "" && boardType != "daily" && boardType != "total" {
		response.BadRequest(c, "board_type 参数不正确")
		return
	}
	status := strings.ToLower(strings.TrimSpace(c.Query("status")))
	if status != "" && status != "pending" && status != "running" && status != "granted" && status != "failed" && status != "dead" {
		response.BadRequest(c, "status 参数不正确")
		return
	}
	periodKey := strings.TrimSpace(c.Query("period_key"))
	if boardType == "total" && periodKey != "" {
		periodKey = "total"
	}
	resp, err := h.activity.ListActivityTemplateRewardJobs(c.Request.Context(), &activityv1.ListActivityTemplateRewardJobsRequest{
		Meta: h.meta(c), TemplateId: strings.TrimSpace(c.Param("template_id")), VersionNo: versionNo,
		BoardType: boardType, PeriodKey: periodKey, Status: status, Page: int32(options.Page), PageSize: int32(options.PageSize),
	})
	if err != nil {
		h.writeGRPCError(c, err, "获取活动模版奖励发放记录失败")
		return
	}
	items := make([]rewardJobDTO, 0, len(resp.GetJobs()))
	users := h.activityTemplateUsers(c, rewardJobProtoUserIDs(resp.GetJobs()))
	for _, item := range resp.GetJobs() {
		items = append(items, rewardJobFromProto(item, users))
	}
	response.OK(c, pageDTO{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()})
}

func (h *Handler) RetryRewardJob(c *gin.Context) {
	templateID := strings.TrimSpace(c.Param("template_id"))
	jobID := strings.TrimSpace(c.Param("reward_job_id"))
	if templateID == "" || jobID == "" {
		response.BadRequest(c, "活动模版或奖励任务 ID 不正确")
		return
	}
	resp, err := h.activity.RetryActivityTemplateRewardJob(c.Request.Context(), &activityv1.RetryActivityTemplateRewardJobRequest{
		Meta: h.meta(c), TemplateId: templateID, RewardJobId: jobID, OperatorAdminId: int64(middleware.CurrentUserID(c)),
	})
	if err != nil {
		h.writeGRPCError(c, err, "重试活动模版奖励发放失败")
		return
	}
	users := h.activityTemplateUsers(c, []int64{resp.GetJob().GetUserId()})
	item := rewardJobFromProto(resp.GetJob(), users)
	shared.OperationLogWithResourceID(c, h.audit, "retry-activity-template-reward", "activity_template_reward_jobs", jobID, "success", "")
	response.OK(c, item)
}

func (h *Handler) TaskClaims(c *gin.Context) {
	options := shared.ListOptions(c)
	versionNo, versionOK := positiveInt64Query(c, "version_no", false)
	userID, userOK := positiveInt64Query(c, "user_id", false)
	if !versionOK || !userOK {
		response.BadRequest(c, "version_no 或 user_id 参数不正确")
		return
	}
	taskDay := strings.TrimSpace(c.Query("task_day"))
	if taskDay != "" {
		parsed, err := time.Parse("2006-01-02", taskDay)
		if err != nil || parsed.Format("2006-01-02") != taskDay {
			response.BadRequest(c, "task_day 必须为 YYYY-MM-DD")
			return
		}
	}
	status := strings.ToLower(strings.TrimSpace(c.Query("status")))
	if status != "" && status != "pending" && status != "running" && status != "granted" && status != "failed" {
		response.BadRequest(c, "status 参数不正确")
		return
	}
	resp, err := h.activity.ListActivityTemplateTaskClaims(c.Request.Context(), &activityv1.ListActivityTemplateTaskClaimsRequest{
		Meta: h.meta(c), TemplateId: strings.TrimSpace(c.Param("template_id")), VersionNo: versionNo,
		TaskDay: taskDay, TaskKey: strings.TrimSpace(c.Query("task_key")), UserId: userID, Status: status,
		Page: int32(options.Page), PageSize: int32(options.PageSize),
	})
	if err != nil {
		h.writeGRPCError(c, err, "获取活动模版任务领奖记录失败")
		return
	}
	items := make([]taskClaimDTO, 0, len(resp.GetClaims()))
	users := h.activityTemplateUsers(c, taskClaimProtoUserIDs(resp.GetClaims()))
	for _, item := range resp.GetClaims() {
		items = append(items, taskClaimFromProto(item, users))
	}
	response.OK(c, pageDTO{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()})
}

func (h *Handler) RetryTaskClaim(c *gin.Context) {
	templateID := strings.TrimSpace(c.Param("template_id"))
	claimID := strings.TrimSpace(c.Param("claim_id"))
	if templateID == "" || claimID == "" {
		response.BadRequest(c, "活动模版或任务领奖记录 ID 不正确")
		return
	}
	resp, err := h.activity.RetryActivityTemplateTaskClaim(c.Request.Context(), &activityv1.RetryActivityTemplateTaskClaimRequest{
		Meta: h.meta(c), TemplateId: templateID, ClaimId: claimID, OperatorAdminId: int64(middleware.CurrentUserID(c)),
	})
	if err != nil {
		h.writeGRPCError(c, err, "重试活动模版任务奖励发放失败")
		return
	}
	users := h.activityTemplateUsers(c, []int64{resp.GetClaim().GetUserId()})
	item := taskClaimFromProto(resp.GetClaim(), users)
	shared.OperationLogWithResourceID(c, h.audit, "retry-activity-template-task-claim", "activity_template_task_claims", claimID, "success", "")
	response.OK(c, item)
}

func (h *Handler) activityTemplateUsers(c *gin.Context, userIDs []int64) map[int64]*userclient.User {
	if h.users == nil || len(userIDs) == 0 {
		return nil
	}
	unique := make([]int64, 0, len(userIDs))
	seen := make(map[int64]struct{}, len(userIDs))
	for _, userID := range userIDs {
		if userID <= 0 {
			continue
		}
		if _, exists := seen[userID]; exists {
			continue
		}
		seen[userID] = struct{}{}
		unique = append(unique, userID)
	}
	if len(unique) == 0 {
		return nil
	}
	users, err := h.users.BatchGetUsers(c.Request.Context(), userclient.BatchGetUsersRequest{
		RequestID: middleware.CurrentRequestID(c), Caller: "admin-server", UserIDs: unique,
	})
	if err != nil {
		// 用户资料是展示装饰，榜单和发奖事实仍必须返回；不能因 user-service 短暂不可用隐藏结算结果。
		return nil
	}
	return users
}

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

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

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

func (h *Handler) Data(c *gin.Context) {
	if h.stats == nil {
		response.ServerError(c, "活动模版统计服务未配置")
		return
	}
	versionNo, ok := positiveInt64Query(c, "version_no", true)
	if !ok {
		response.BadRequest(c, "version_no 参数不正确")
		return
	}
	startMS, startOK := requiredPositiveInt64Query(c, "start_ms")
	endMS, endOK := requiredPositiveInt64Query(c, "end_ms")
	if !startOK || !endOK || endMS <= startMS {
		response.BadRequest(c, "start_ms 或 end_ms 参数不正确")
		return
	}
	regionID := int64(0)
	if raw, exists := c.GetQuery("region_id"); exists {
		var err error
		regionID, err = strconv.ParseInt(strings.TrimSpace(raw), 10, 64)
		if err != nil || regionID <= 0 {
			response.BadRequest(c, "region_id 参数不正确")
			return
		}
	}
	// statistics-service 按 UTC 日聚合。把任意 UI 时间窗归一为覆盖它的完整 [UTC 00:00, UTC 00:00)，
	// 既避免半日过滤产生“看似精确但实际丢整日”的静默误差，也由响应 start_ms/end_ms 明确回显生效窗口。
	effectiveStartMS := utcDayFloor(startMS)
	effectiveEndMS := utcDayCeil(endMS)
	if effectiveEndMS-effectiveStartMS > maxActivityTemplateDataRange.Milliseconds() {
		response.BadRequest(c, "活动模版数据查询区间不能超过 366 天")
		return
	}
	data, err := h.stats.ActivityTemplateData(c.Request.Context(), statisticsclient.ActivityTemplateDataQuery{
		AppCode: appctx.FromContext(c.Request.Context()), TemplateID: strings.TrimSpace(c.Param("template_id")),
		PublishedVersion: versionNo, RegionID: regionID, StartMS: effectiveStartMS, EndMS: effectiveEndMS,
	})
	if err != nil {
		response.ServerError(c, "获取活动模版统计数据失败")
		return
	}
	response.OK(c, data)
}

func positiveInt64Query(c *gin.Context, key string, required bool) (int64, bool) {
	raw, exists := c.GetQuery(key)
	if !exists || strings.TrimSpace(raw) == "" {
		return 0, !required
	}
	value, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 64)
	return value, err == nil && value > 0
}

func requiredPositiveInt64Query(c *gin.Context, key string) (int64, bool) {
	value, ok := positiveInt64Query(c, key, true)
	return value, ok
}

func utcDayFloor(ms int64) int64 {
	value := time.UnixMilli(ms).UTC()
	return time.Date(value.Year(), value.Month(), value.Day(), 0, 0, 0, 0, time.UTC).UnixMilli()
}

func utcDayCeil(ms int64) int64 {
	floor := utcDayFloor(ms)
	if floor == ms {
		return floor
	}
	return floor + 24*time.Hour.Milliseconds()
}
