package registrationreward

import (
	"context"
	"database/sql"
	"strconv"
	"strings"
	"time"

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/integration/activityclient"
	"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"
)

type Handler struct {
	activity activityclient.Client
	userDB   *sql.DB
	audit    shared.OperationLogger
}

func New(activity activityclient.Client, userDB *sql.DB, audit shared.OperationLogger) *Handler {
	return &Handler{activity: activity, userDB: userDB, audit: audit}
}

type configRequest struct {
	Enabled         bool   `json:"enabled"`
	RewardType      string `json:"reward_type"`
	CoinAmount      int64  `json:"coin_amount"`
	ResourceGroupID int64  `json:"resource_group_id"`
	DailyLimit      int64  `json:"daily_limit"`
}

type configDTO struct {
	AppCode          string `json:"app_code"`
	Enabled          bool   `json:"enabled"`
	RewardType       string `json:"reward_type"`
	CoinAmount       int64  `json:"coin_amount"`
	ResourceGroupID  int64  `json:"resource_group_id"`
	DailyLimit       int64  `json:"daily_limit"`
	UpdatedByAdminID int64  `json:"updated_by_admin_id"`
	CreatedAtMS      int64  `json:"created_at_ms"`
	UpdatedAtMS      int64  `json:"updated_at_ms"`
}

type claimDTO struct {
	ClaimID             string   `json:"claim_id"`
	CommandID           string   `json:"command_id"`
	UserID              int64    `json:"user_id,string"`
	User                *userDTO `json:"user,omitempty"`
	RewardDay           string   `json:"reward_day"`
	RewardType          string   `json:"reward_type"`
	CoinAmount          int64    `json:"coin_amount"`
	ResourceGroupID     int64    `json:"resource_group_id"`
	Status              string   `json:"status"`
	WalletCommandID     string   `json:"wallet_command_id"`
	WalletTransactionID string   `json:"wallet_transaction_id"`
	ResourceGrantID     string   `json:"resource_grant_id"`
	FailureReason       string   `json:"failure_reason"`
	ClaimedAtMS         int64    `json:"claimed_at_ms"`
	CreatedAtMS         int64    `json:"created_at_ms"`
	UpdatedAtMS         int64    `json:"updated_at_ms"`
}

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

type claimsPageDTO struct {
	Items             []claimDTO `json:"items"`
	Page              int        `json:"page"`
	PageSize          int        `json:"pageSize"`
	Total             int64      `json:"total"`
	TodayClaimedCount int64      `json:"today_claimed_count"`
}

func (h *Handler) GetRegistrationRewardConfig(c *gin.Context) {
	resp, err := h.activity.GetRegistrationRewardConfig(c.Request.Context(), &activityv1.GetRegistrationRewardConfigRequest{Meta: h.meta(c)})
	if err != nil {
		response.ServerError(c, "获取注册奖励配置失败")
		return
	}
	response.OK(c, configFromProto(resp.GetConfig()))
}

func (h *Handler) UpdateRegistrationRewardConfig(c *gin.Context) {
	var req configRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "注册奖励配置参数不正确")
		return
	}
	resp, err := h.activity.UpdateRegistrationRewardConfig(c.Request.Context(), &activityv1.UpdateRegistrationRewardConfigRequest{
		Meta:            h.meta(c),
		Enabled:         req.Enabled,
		RewardType:      strings.TrimSpace(req.RewardType),
		CoinAmount:      req.CoinAmount,
		ResourceGroupId: req.ResourceGroupID,
		DailyLimit:      req.DailyLimit,
		OperatorAdminId: int64(middleware.CurrentUserID(c)),
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	item := configFromProto(resp.GetConfig())
	shared.OperationLogWithResourceID(c, h.audit, "update-registration-reward", "registration_reward_configs", item.AppCode, "success", item.RewardType)
	response.OK(c, item)
}

func (h *Handler) ListRegistrationRewardClaims(c *gin.Context) {
	options := shared.ListOptions(c)
	claimedStartMS := queryInt64(c, "claimed_start_ms", "claimedStartMs", "start_ms", "startMs")
	claimedEndMS := queryInt64(c, "claimed_end_ms", "claimedEndMs", "end_ms", "endMs")
	userFilter := shared.UserIdentityFilterFromQuery(c, "")
	userID, matched, ok := h.resolveClaimUserID(c.Request.Context(), strings.TrimSpace(options.Keyword), userFilter)
	if !ok {
		response.ServerError(c, "查询用户信息失败")
		return
	}
	if (options.Keyword != "" || !userFilter.IsEmpty()) && !matched {
		todayClaimedCount, ok := h.loadTodayClaimedCount(c)
		if !ok {
			return
		}
		response.OK(c, claimsPageDTO{Items: []claimDTO{}, Page: options.Page, PageSize: options.PageSize, Total: 0, TodayClaimedCount: todayClaimedCount})
		return
	}
	resp, err := h.activity.ListRegistrationRewardClaims(c.Request.Context(), &activityv1.ListRegistrationRewardClaimsRequest{
		Meta:           h.meta(c),
		Status:         strings.TrimSpace(options.Status),
		UserId:         userID,
		ClaimedStartMs: claimedStartMS,
		ClaimedEndMs:   claimedEndMS,
		Page:           int32(options.Page),
		PageSize:       int32(options.PageSize),
	})
	if err != nil {
		response.ServerError(c, "获取注册奖励领取记录失败")
		return
	}
	items := make([]claimDTO, 0, len(resp.GetClaims()))
	for _, item := range resp.GetClaims() {
		items = append(items, claimFromProto(item))
	}
	if err := h.fillClaimUsers(c.Request.Context(), items); err != nil {
		response.ServerError(c, "补全用户信息失败")
		return
	}
	response.OK(c, claimsPageDTO{
		Items:             items,
		Page:              options.Page,
		PageSize:          options.PageSize,
		Total:             resp.GetTotal(),
		TodayClaimedCount: resp.GetTodayClaimedCount(),
	})
}

func (h *Handler) loadTodayClaimedCount(c *gin.Context) (int64, bool) {
	resp, err := h.activity.ListRegistrationRewardClaims(c.Request.Context(), &activityv1.ListRegistrationRewardClaimsRequest{
		Meta:     h.meta(c),
		Page:     1,
		PageSize: 1,
	})
	if err != nil {
		response.ServerError(c, "获取注册奖励今日领取数量失败")
		return 0, false
	}
	return resp.GetTodayClaimedCount(), true
}

func (h *Handler) meta(c *gin.Context) *activityv1.RequestMeta {
	return &activityv1.RequestMeta{
		RequestId: middleware.CurrentRequestID(c),
		Caller:    "admin-server",
		AppCode:   appctx.FromContext(c.Request.Context()),
		SentAtMs:  time.Now().UnixMilli(),
	}
}

func (h *Handler) resolveClaimUserID(ctx context.Context, keyword string, filter shared.UserIdentityFilter) (int64, bool, bool) {
	if filter.IsEmpty() && keyword == "" {
		return 0, false, true
	}
	if !filter.IsEmpty() {
		userID, matched, err := shared.ResolveUserIdentityFilter(ctx, h.userDB, appctx.FromContext(ctx), filter, time.Now().UnixMilli())
		return userID, matched, err == nil
	}
	// 注册奖励领取记录的 owner 是 activity-service；列表关键词先解析成内部 user_id，再保持下游分页和 total 同源。
	userID, matched, err := shared.ResolveExactUserID(ctx, h.userDB, appctx.FromContext(ctx), keyword, time.Now().UnixMilli())
	if err != nil {
		return 0, false, false
	}
	return userID, matched, true
}

func (h *Handler) fillClaimUsers(ctx context.Context, claims []claimDTO) error {
	if h.userDB == nil || len(claims) == 0 {
		return nil
	}
	ids := collectClaimUserIDs(claims)
	if len(ids) == 0 {
		return nil
	}
	args := append([]any{appctx.FromContext(ctx)}, int64Args(ids)...)
	rows, err := h.userDB.QueryContext(ctx, `
		SELECT user_id, current_display_user_id, COALESCE(username, ''), COALESCE(avatar, '')
		FROM users
		WHERE app_code = ? AND user_id IN (`+placeholders(len(ids))+`)`,
		args...,
	)
	if err != nil {
		return err
	}
	defer rows.Close()
	users := make(map[int64]userDTO, len(ids))
	for rows.Next() {
		var user userDTO
		if err := rows.Scan(&user.UserID, &user.DisplayUserID, &user.Username, &user.Avatar); err != nil {
			return err
		}
		users[user.UserID] = user
	}
	if err := rows.Err(); err != nil {
		return err
	}
	for index := range claims {
		if user, ok := users[claims[index].UserID]; ok {
			claims[index].User = &user
			continue
		}
		// 领取事实必须可追踪；用户资料缺失时仍返回 user_id 让后台能定位数据问题。
		claims[index].User = &userDTO{UserID: claims[index].UserID}
	}
	return nil
}

func configFromProto(item *activityv1.RegistrationRewardConfig) configDTO {
	if item == nil {
		return configDTO{RewardType: "coin"}
	}
	return configDTO{
		AppCode:          item.GetAppCode(),
		Enabled:          item.GetEnabled(),
		RewardType:       item.GetRewardType(),
		CoinAmount:       item.GetCoinAmount(),
		ResourceGroupID:  item.GetResourceGroupId(),
		DailyLimit:       item.GetDailyLimit(),
		UpdatedByAdminID: item.GetUpdatedByAdminId(),
		CreatedAtMS:      item.GetCreatedAtMs(),
		UpdatedAtMS:      item.GetUpdatedAtMs(),
	}
}

func claimFromProto(item *activityv1.RegistrationRewardClaim) claimDTO {
	if item == nil {
		return claimDTO{}
	}
	return claimDTO{
		ClaimID:             item.GetClaimId(),
		CommandID:           item.GetCommandId(),
		UserID:              item.GetUserId(),
		RewardDay:           item.GetRewardDay(),
		RewardType:          item.GetRewardType(),
		CoinAmount:          item.GetCoinAmount(),
		ResourceGroupID:     item.GetResourceGroupId(),
		Status:              item.GetStatus(),
		WalletCommandID:     item.GetWalletCommandId(),
		WalletTransactionID: item.GetWalletTransactionId(),
		ResourceGrantID:     item.GetResourceGrantId(),
		FailureReason:       item.GetFailureReason(),
		ClaimedAtMS:         item.GetClaimedAtMs(),
		CreatedAtMS:         item.GetCreatedAtMs(),
		UpdatedAtMS:         item.GetUpdatedAtMs(),
	}
}

func collectClaimUserIDs(claims []claimDTO) []int64 {
	seen := make(map[int64]struct{}, len(claims))
	ids := make([]int64, 0, len(claims))
	for _, claim := range claims {
		if claim.UserID <= 0 {
			continue
		}
		if _, ok := seen[claim.UserID]; ok {
			continue
		}
		seen[claim.UserID] = struct{}{}
		ids = append(ids, claim.UserID)
	}
	return ids
}

func placeholders(count int) string {
	if count <= 0 {
		return ""
	}
	items := make([]string, count)
	for index := range items {
		items[index] = "?"
	}
	return strings.Join(items, ",")
}

func int64Args(values []int64) []any {
	args := make([]any, 0, len(values))
	for _, value := range values {
		args = append(args, value)
	}
	return args
}

func queryInt64(c *gin.Context, names ...string) int64 {
	for _, name := range names {
		value := strings.TrimSpace(c.Query(name))
		if value == "" {
			continue
		}
		parsed, err := strconv.ParseInt(value, 10, 64)
		if err == nil && parsed > 0 {
			return parsed
		}
	}
	return 0
}
