package inviteactivityreward

import (
	"context"
	"database/sql"
	"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"`
	PerInviteInviterRewardCoinAmount int64     `json:"per_invite_inviter_reward_coin_amount"`
	PerInviteInviteeRewardCoinAmount int64     `json:"per_invite_invitee_reward_coin_amount"`
	Tiers                            []tierDTO `json:"tiers"`
}

type configDTO struct {
	AppCode                          string    `json:"app_code"`
	Enabled                          bool      `json:"enabled"`
	PerInviteInviterRewardCoinAmount int64     `json:"per_invite_inviter_reward_coin_amount"`
	PerInviteInviteeRewardCoinAmount int64     `json:"per_invite_invitee_reward_coin_amount"`
	Tiers                            []tierDTO `json:"tiers"`
	UpdatedByAdminID                 int64     `json:"updated_by_admin_id"`
	CreatedAtMS                      int64     `json:"created_at_ms"`
	UpdatedAtMS                      int64     `json:"updated_at_ms"`
}

type tierDTO struct {
	TierID                    int64  `json:"tier_id"`
	RewardType                string `json:"reward_type"`
	TierCode                  string `json:"tier_code"`
	TierName                  string `json:"tier_name"`
	ThresholdCoinAmount       int64  `json:"threshold_coin_amount"`
	ThresholdValidInviteCount int64  `json:"threshold_valid_invite_count"`
	RewardCoinAmount          int64  `json:"reward_coin_amount"`
	Status                    string `json:"status"`
	SortOrder                 int32  `json:"sort_order"`
	CreatedAtMS               int64  `json:"created_at_ms"`
	UpdatedAtMS               int64  `json:"updated_at_ms"`
}

type claimDTO struct {
	ClaimID                   string   `json:"claim_id"`
	AppCode                   string   `json:"app_code"`
	CycleKey                  string   `json:"cycle_key"`
	UserID                    int64    `json:"user_id,string"`
	User                      *userDTO `json:"user,omitempty"`
	RewardType                string   `json:"reward_type"`
	CommandID                 string   `json:"command_id"`
	TierID                    int64    `json:"tier_id"`
	TierCode                  string   `json:"tier_code"`
	TierName                  string   `json:"tier_name"`
	ThresholdCoinAmount       int64    `json:"threshold_coin_amount"`
	ThresholdValidInviteCount int64    `json:"threshold_valid_invite_count"`
	ReachedValue              int64    `json:"reached_value"`
	RewardCoinAmount          int64    `json:"reward_coin_amount"`
	Status                    string   `json:"status"`
	WalletCommandID           string   `json:"wallet_command_id"`
	WalletTransactionID       string   `json:"wallet_transaction_id"`
	FailureReason             string   `json:"failure_reason"`
	GrantedAtMS               int64    `json:"granted_at_ms"`
	CreatedAtMS               int64    `json:"created_at_ms"`
	UpdatedAtMS               int64    `json:"updated_at_ms"`
}

type summaryDTO struct {
	AppCode                 string       `json:"app_code"`
	CycleKey                string       `json:"cycle_key"`
	UserID                  int64        `json:"user_id,string"`
	User                    *userDTO     `json:"user,omitempty"`
	InviteCount             int64        `json:"invite_count"`
	ValidInviteCount        int64        `json:"valid_invite_count"`
	TotalRechargeCoinAmount int64        `json:"total_recharge_coin_amount"`
	TotalRewardCoinAmount   int64        `json:"total_reward_coin_amount"`
	UpdatedAtMS             int64        `json:"updated_at_ms"`
	Invitees                []inviteeDTO `json:"invitees"`
}

type inviteeDTO struct {
	UserID             int64    `json:"user_id,string"`
	User               *userDTO `json:"user,omitempty"`
	RechargeCoinAmount int64    `json:"recharge_coin_amount"`
	InvitedAtMS        int64    `json:"invited_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"`
}

func (h *Handler) GetConfig(c *gin.Context) {
	resp, err := h.activity.GetInviteActivityRewardConfig(c.Request.Context(), &activityv1.GetInviteActivityRewardConfigRequest{Meta: h.meta(c)})
	if err != nil {
		response.ServerError(c, "获取邀请活动配置失败")
		return
	}
	response.OK(c, configFromProto(resp.GetConfig()))
}

func (h *Handler) UpdateConfig(c *gin.Context) {
	var req configRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "邀请活动配置参数不正确")
		return
	}
	tiers := make([]*activityv1.InviteActivityRewardTier, 0, len(req.Tiers))
	for _, tier := range req.Tiers {
		// admin-server 只裁剪字符串和注入操作人；档位门槛、奖励金额、最高档领取语义都由 activity-service 统一校验。
		tiers = append(tiers, &activityv1.InviteActivityRewardTier{
			TierId:                    tier.TierID,
			RewardType:                strings.TrimSpace(tier.RewardType),
			TierCode:                  strings.TrimSpace(tier.TierCode),
			TierName:                  strings.TrimSpace(tier.TierName),
			ThresholdCoinAmount:       tier.ThresholdCoinAmount,
			ThresholdValidInviteCount: tier.ThresholdValidInviteCount,
			RewardCoinAmount:          tier.RewardCoinAmount,
			Status:                    strings.TrimSpace(tier.Status),
			SortOrder:                 tier.SortOrder,
		})
	}
	resp, err := h.activity.UpdateInviteActivityRewardConfig(c.Request.Context(), &activityv1.UpdateInviteActivityRewardConfigRequest{
		Meta:                             h.meta(c),
		Enabled:                          req.Enabled,
		PerInviteInviterRewardCoinAmount: req.PerInviteInviterRewardCoinAmount,
		PerInviteInviteeRewardCoinAmount: req.PerInviteInviteeRewardCoinAmount,
		Tiers:                            tiers,
		OperatorAdminId:                  int64(middleware.CurrentUserID(c)),
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	item := configFromProto(resp.GetConfig())
	shared.OperationLogWithResourceID(c, h.audit, "update-invite-activity-reward", "invite_activity_reward_configs", item.AppCode, "success", "")
	response.OK(c, item)
}

func (h *Handler) ListClaims(c *gin.Context) {
	options := shared.ListOptions(c)
	userFilter := shared.UserIdentityFilterFromQuery(c, "")
	userID, matched, ok := h.resolveUserID(c.Request.Context(), strings.TrimSpace(options.Keyword), userFilter)
	if !ok {
		response.ServerError(c, "查询用户信息失败")
		return
	}
	if (options.Keyword != "" || !userFilter.IsEmpty()) && !matched {
		response.OK(c, response.Page{Items: []claimDTO{}, Page: options.Page, PageSize: options.PageSize, Total: 0})
		return
	}
	resp, err := h.activity.ListInviteActivityRewardClaims(c.Request.Context(), &activityv1.ListInviteActivityRewardClaimsRequest{
		Meta:       h.meta(c),
		Status:     strings.TrimSpace(options.Status),
		RewardType: strings.TrimSpace(c.Query("reward_type")),
		UserId:     userID,
		CycleKey:   strings.TrimSpace(c.Query("cycle_key")),
		Page:       int32(options.Page),
		PageSize:   int32(options.PageSize),
	})
	if err != nil {
		response.ServerError(c, "获取邀请活动领取记录失败")
		return
	}
	items := make([]claimDTO, 0, len(resp.GetClaims()))
	for _, claim := range resp.GetClaims() {
		items = append(items, claimFromProto(claim))
	}
	if err := h.fillUsers(c.Request.Context(), items); err != nil {
		response.ServerError(c, "补全用户信息失败")
		return
	}
	response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()})
}

func (h *Handler) ListSummaries(c *gin.Context) {
	options := shared.ListOptions(c)
	userFilter := shared.UserIdentityFilterFromQuery(c, "")
	userID, matched, ok := h.resolveUserID(c.Request.Context(), strings.TrimSpace(options.Keyword), userFilter)
	if !ok {
		response.ServerError(c, "查询用户信息失败")
		return
	}
	if (options.Keyword != "" || !userFilter.IsEmpty()) && !matched {
		response.OK(c, response.Page{Items: []summaryDTO{}, Page: options.Page, PageSize: options.PageSize, Total: 0})
		return
	}
	resp, err := h.activity.ListInviteActivityRewardSummaries(c.Request.Context(), &activityv1.ListInviteActivityRewardSummariesRequest{
		Meta:     h.meta(c),
		UserId:   userID,
		CycleKey: strings.TrimSpace(c.Query("cycle_key")),
		Page:     int32(options.Page),
		PageSize: int32(options.PageSize),
	})
	if err != nil {
		response.ServerError(c, "获取邀请活动列表失败")
		return
	}
	items := make([]summaryDTO, 0, len(resp.GetSummaries()))
	for _, summary := range resp.GetSummaries() {
		items = append(items, summaryFromProto(summary))
	}
	if err := h.fillSummaryUsers(c.Request.Context(), items); err != nil {
		response.ServerError(c, "补全用户信息失败")
		return
	}
	response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()})
}

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) resolveUserID(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
	}
	// 邀请奖励汇总页只把内部 user_id 交给 activity-service；展示 ID 的兼容规则集中在 shared 解析器里。
	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) fillUsers(ctx context.Context, claims []claimDTO) error {
	if len(claims) == 0 {
		return nil
	}
	ids := collectUserIDs(claims)
	if len(ids) == 0 {
		return nil
	}
	users, err := h.loadUsers(ctx, ids)
	if err != nil {
		return err
	}
	for index := range claims {
		if user, ok := users[claims[index].UserID]; ok {
			claims[index].User = &user
			continue
		}
		claims[index].User = &userDTO{UserID: claims[index].UserID}
	}
	return nil
}

func (h *Handler) fillSummaryUsers(ctx context.Context, summaries []summaryDTO) error {
	if len(summaries) == 0 {
		return nil
	}
	ids := collectSummaryUserIDs(summaries)
	if len(ids) == 0 {
		return nil
	}
	users, err := h.loadUsers(ctx, ids)
	if err != nil {
		return err
	}
	for summaryIndex := range summaries {
		if user, ok := users[summaries[summaryIndex].UserID]; ok {
			summaries[summaryIndex].User = &user
		} else {
			summaries[summaryIndex].User = &userDTO{UserID: summaries[summaryIndex].UserID}
		}
		for inviteeIndex := range summaries[summaryIndex].Invitees {
			userID := summaries[summaryIndex].Invitees[inviteeIndex].UserID
			if user, ok := users[userID]; ok {
				summaries[summaryIndex].Invitees[inviteeIndex].User = &user
				continue
			}
			summaries[summaryIndex].Invitees[inviteeIndex].User = &userDTO{UserID: userID}
		}
	}
	return nil
}

func (h *Handler) loadUsers(ctx context.Context, ids []int64) (map[int64]userDTO, error) {
	if h.userDB == nil || len(ids) == 0 {
		return map[int64]userDTO{}, 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 nil, 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 nil, err
		}
		users[user.UserID] = user
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return users, nil
}

func configFromProto(config *activityv1.InviteActivityRewardConfig) configDTO {
	if config == nil {
		return configDTO{Tiers: []tierDTO{}}
	}
	tiers := make([]tierDTO, 0, len(config.GetTiers()))
	for _, tier := range config.GetTiers() {
		tiers = append(tiers, tierFromProto(tier))
	}
	return configDTO{
		AppCode:                          config.GetAppCode(),
		Enabled:                          config.GetEnabled(),
		PerInviteInviterRewardCoinAmount: config.GetPerInviteInviterRewardCoinAmount(),
		PerInviteInviteeRewardCoinAmount: config.GetPerInviteInviteeRewardCoinAmount(),
		Tiers:                            tiers,
		UpdatedByAdminID:                 config.GetUpdatedByAdminId(),
		CreatedAtMS:                      config.GetCreatedAtMs(),
		UpdatedAtMS:                      config.GetUpdatedAtMs(),
	}
}

func tierFromProto(tier *activityv1.InviteActivityRewardTier) tierDTO {
	if tier == nil {
		return tierDTO{}
	}
	return tierDTO{
		TierID:                    tier.GetTierId(),
		RewardType:                tier.GetRewardType(),
		TierCode:                  tier.GetTierCode(),
		TierName:                  tier.GetTierName(),
		ThresholdCoinAmount:       tier.GetThresholdCoinAmount(),
		ThresholdValidInviteCount: tier.GetThresholdValidInviteCount(),
		RewardCoinAmount:          tier.GetRewardCoinAmount(),
		Status:                    tier.GetStatus(),
		SortOrder:                 tier.GetSortOrder(),
		CreatedAtMS:               tier.GetCreatedAtMs(),
		UpdatedAtMS:               tier.GetUpdatedAtMs(),
	}
}

func claimFromProto(claim *activityv1.InviteActivityRewardClaim) claimDTO {
	if claim == nil {
		return claimDTO{}
	}
	return claimDTO{
		ClaimID:                   claim.GetClaimId(),
		AppCode:                   claim.GetAppCode(),
		CycleKey:                  claim.GetCycleKey(),
		UserID:                    claim.GetUserId(),
		RewardType:                claim.GetRewardType(),
		CommandID:                 claim.GetCommandId(),
		TierID:                    claim.GetTierId(),
		TierCode:                  claim.GetTierCode(),
		TierName:                  claim.GetTierName(),
		ThresholdCoinAmount:       claim.GetThresholdCoinAmount(),
		ThresholdValidInviteCount: claim.GetThresholdValidInviteCount(),
		ReachedValue:              claim.GetReachedValue(),
		RewardCoinAmount:          claim.GetRewardCoinAmount(),
		Status:                    claim.GetStatus(),
		WalletCommandID:           claim.GetWalletCommandId(),
		WalletTransactionID:       claim.GetWalletTransactionId(),
		FailureReason:             claim.GetFailureReason(),
		GrantedAtMS:               claim.GetGrantedAtMs(),
		CreatedAtMS:               claim.GetCreatedAtMs(),
		UpdatedAtMS:               claim.GetUpdatedAtMs(),
	}
}

func summaryFromProto(summary *activityv1.InviteActivityRewardSummary) summaryDTO {
	if summary == nil {
		return summaryDTO{Invitees: []inviteeDTO{}}
	}
	invitees := make([]inviteeDTO, 0, len(summary.GetInvitees()))
	for _, invitee := range summary.GetInvitees() {
		invitees = append(invitees, inviteeFromProto(invitee))
	}
	return summaryDTO{
		AppCode:                 summary.GetAppCode(),
		CycleKey:                summary.GetCycleKey(),
		UserID:                  summary.GetUserId(),
		InviteCount:             summary.GetInviteCount(),
		ValidInviteCount:        summary.GetValidInviteCount(),
		TotalRechargeCoinAmount: summary.GetTotalRechargeCoinAmount(),
		TotalRewardCoinAmount:   summary.GetTotalRewardCoinAmount(),
		UpdatedAtMS:             summary.GetUpdatedAtMs(),
		Invitees:                invitees,
	}
}

func inviteeFromProto(invitee *activityv1.InviteActivityRewardInviteeSummary) inviteeDTO {
	if invitee == nil {
		return inviteeDTO{}
	}
	return inviteeDTO{
		UserID:             invitee.GetInvitedUserId(),
		RechargeCoinAmount: invitee.GetRechargeCoinAmount(),
		InvitedAtMS:        invitee.GetInvitedAtMs(),
	}
}

func collectUserIDs(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 collectSummaryUserIDs(summaries []summaryDTO) []int64 {
	seen := make(map[int64]struct{}, len(summaries))
	ids := make([]int64, 0, len(summaries))
	for _, summary := range summaries {
		if summary.UserID > 0 {
			if _, ok := seen[summary.UserID]; !ok {
				seen[summary.UserID] = struct{}{}
				ids = append(ids, summary.UserID)
			}
		}
		for _, invitee := range summary.Invitees {
			if invitee.UserID <= 0 {
				continue
			}
			if _, ok := seen[invitee.UserID]; ok {
				continue
			}
			seen[invitee.UserID] = struct{}{}
			ids = append(ids, invitee.UserID)
		}
	}
	return ids
}

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

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