package mysql

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"strings"

	"hyapp/pkg/appcode"
	"hyapp/pkg/idgen"
	"hyapp/pkg/xerr"
	domain "hyapp/services/activity-service/internal/domain/firstrecharge"
)

// GetFirstRechargeRewardConfig 读取首冲奖励当前配置；未配置是后台首屏正常状态。
func (r *Repository) GetFirstRechargeRewardConfig(ctx context.Context) (domain.Config, bool, error) {
	if r == nil || r.db == nil {
		return domain.Config{}, false, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	row := r.db.QueryRowContext(ctx, firstRechargeRewardConfigSelectSQL()+`
		WHERE app_code = ?`,
		appcode.FromContext(ctx),
	)
	config, err := scanFirstRechargeRewardConfig(row)
	if errors.Is(err, sql.ErrNoRows) {
		return domain.Config{}, false, nil
	}
	if err != nil {
		return domain.Config{}, false, err
	}
	tiers, err := r.listFirstRechargeRewardTiers(ctx, nil)
	if err != nil {
		return domain.Config{}, false, err
	}
	config.Tiers = tiers
	return config, true, nil
}

// UpdateFirstRechargeRewardConfig 原子替换配置档位；claim 保留命中档位快照，不受后续配置变更影响。
func (r *Repository) UpdateFirstRechargeRewardConfig(ctx context.Context, config domain.Config, nowMS int64) (domain.Config, error) {
	if r == nil || r.db == nil {
		return domain.Config{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return domain.Config{}, err
	}
	defer func() { _ = tx.Rollback() }()

	_, err = tx.ExecContext(ctx, `
		INSERT INTO first_recharge_reward_configs (
			app_code, enabled, updated_by_admin_id, created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, ?, ?)
		ON DUPLICATE KEY UPDATE
			enabled = VALUES(enabled),
			updated_by_admin_id = VALUES(updated_by_admin_id),
			updated_at_ms = VALUES(updated_at_ms)`,
		appcode.FromContext(ctx), config.Enabled, config.UpdatedByAdminID, nowMS, nowMS,
	)
	if err != nil {
		return domain.Config{}, err
	}
	if _, err := tx.ExecContext(ctx, `
		DELETE FROM first_recharge_reward_tiers
		WHERE app_code = ?`,
		appcode.FromContext(ctx),
	); err != nil {
		return domain.Config{}, err
	}
	for i := range config.Tiers {
		tier := config.Tiers[i]
		tier.CreatedAtMS = nowMS
		tier.UpdatedAtMS = nowMS
		if tier.TierID > 0 {
			if _, err := tx.ExecContext(ctx, `
				INSERT INTO first_recharge_reward_tiers (
					tier_id, app_code, tier_code, tier_name, min_coin_amount, max_coin_amount,
					resource_group_id, status, sort_order, created_at_ms, updated_at_ms,
					usd_minor_amount, google_product_id, discount_percent
				) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
				tier.TierID, appcode.FromContext(ctx), tier.TierCode, tier.TierName, tier.MinCoinAmount,
				tier.MaxCoinAmount, tier.ResourceGroupID, tier.Status, tier.SortOrder, tier.CreatedAtMS, tier.UpdatedAtMS,
				tier.USDMinorAmount, tier.GoogleProductID, tier.DiscountPercent,
			); err != nil {
				return domain.Config{}, err
			}
			continue
		}
		result, err := tx.ExecContext(ctx, `
			INSERT INTO first_recharge_reward_tiers (
				app_code, tier_code, tier_name, min_coin_amount, max_coin_amount,
				resource_group_id, status, sort_order, created_at_ms, updated_at_ms,
				usd_minor_amount, google_product_id, discount_percent
			) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
			appcode.FromContext(ctx), tier.TierCode, tier.TierName, tier.MinCoinAmount, tier.MaxCoinAmount,
			tier.ResourceGroupID, tier.Status, tier.SortOrder, tier.CreatedAtMS, tier.UpdatedAtMS,
			tier.USDMinorAmount, tier.GoogleProductID, tier.DiscountPercent,
		)
		if err != nil {
			return domain.Config{}, err
		}
		tierID, err := result.LastInsertId()
		if err != nil {
			return domain.Config{}, err
		}
		config.Tiers[i].TierID = tierID
	}
	if err := tx.Commit(); err != nil {
		return domain.Config{}, err
	}
	updated, exists, err := r.GetFirstRechargeRewardConfig(ctx)
	if err != nil {
		return domain.Config{}, err
	}
	if !exists {
		return domain.Config{}, xerr.New(xerr.NotFound, "first recharge reward config not found")
	}
	return updated, nil
}

// ListFirstRechargeRewardClaimsByUser 返回 App 查询需要的用户各档位首冲奖励事实。
func (r *Repository) ListFirstRechargeRewardClaimsByUser(ctx context.Context, userID int64) ([]domain.Claim, error) {
	if r == nil || r.db == nil {
		return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	rows, err := r.db.QueryContext(ctx, firstRechargeRewardClaimSelectSQL()+`
		WHERE app_code = ? AND user_id = ?
		ORDER BY created_at_ms DESC, claim_id DESC`,
		appcode.FromContext(ctx), userID,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	claims, _, err := scanFirstRechargeRewardClaims(rows, 0)
	return claims, err
}

// GetFirstRechargeRewardClaimByUser 保留给旧测试和内部兼容；返回最近一条首充奖励事实。
func (r *Repository) GetFirstRechargeRewardClaimByUser(ctx context.Context, userID int64) (domain.Claim, bool, error) {
	if r == nil || r.db == nil {
		return domain.Claim{}, false, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	row := r.db.QueryRowContext(ctx, firstRechargeRewardClaimSelectSQL()+`
		WHERE app_code = ? AND user_id = ?
		ORDER BY created_at_ms DESC, claim_id DESC
		LIMIT 1`,
		appcode.FromContext(ctx), userID,
	)
	claim, err := scanFirstRechargeRewardClaim(row)
	if errors.Is(err, sql.ErrNoRows) {
		return domain.Claim{}, false, nil
	}
	return claim, err == nil, err
}

// PrepareFirstRechargeRewardClaim 在 activity 事务内完成配置匹配、幂等判断和 pending claim 写入。
func (r *Repository) PrepareFirstRechargeRewardClaim(ctx context.Context, event domain.RechargeEvent, nowMS int64) (domain.PrepareResult, error) {
	if r == nil || r.db == nil {
		return domain.PrepareResult{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return domain.PrepareResult{}, err
	}
	defer func() { _ = tx.Rollback() }()

	config, exists, err := r.getFirstRechargeRewardConfigForUpdate(ctx, tx)
	if err != nil {
		return domain.PrepareResult{}, err
	}
	if !exists {
		return domain.PrepareResult{Reason: domain.ReasonNotConfigured}, tx.Commit()
	}
	if !config.Enabled {
		return domain.PrepareResult{Reason: domain.ReasonDisabled}, tx.Commit()
	}
	tiers, err := r.listFirstRechargeRewardTiersForUpdate(ctx, tx)
	if err != nil {
		return domain.PrepareResult{}, err
	}
	tier, matched := matchFirstRechargeRewardTier(tiers, event.GoogleProductID)
	if !matched {
		return domain.PrepareResult{Reason: domain.ReasonTierNotMatched}, tx.Commit()
	}

	existing, exists, err := r.getFirstRechargeRewardClaimByEventForUpdate(ctx, tx, event.EventID)
	if err != nil || exists {
		return r.prepareExistingFirstRechargeRewardClaim(ctx, tx, existing, exists, err, event, nowMS)
	}
	existing, exists, err = r.getFirstRechargeRewardClaimByTransactionForUpdate(ctx, tx, event.TransactionID)
	if err != nil || exists {
		return r.prepareExistingFirstRechargeRewardClaim(ctx, tx, existing, exists, err, event, nowMS)
	}
	existing, exists, err = r.getFirstRechargeRewardClaimByUserTierForUpdate(ctx, tx, event.UserID, tier.TierID)
	if err != nil || exists {
		return r.prepareExistingFirstRechargeRewardClaim(ctx, tx, existing, exists, err, event, nowMS)
	}

	claim := domain.Claim{
		ClaimID:            idgen.New("frclaim"),
		EventID:            event.EventID,
		TransactionID:      event.TransactionID,
		CommandID:          event.CommandID,
		UserID:             event.UserID,
		TierID:             tier.TierID,
		TierCode:           tier.TierCode,
		ResourceGroupID:    tier.ResourceGroupID,
		RechargeCoinAmount: event.RechargeCoinAmount,
		RechargeSequence:   event.RechargeSequence,
		RechargeType:       event.RechargeType,
		Status:             domain.StatusPending,
		RechargedAtMS:      event.OccurredAtMS,
		CreatedAtMS:        nowMS,
		UpdatedAtMS:        nowMS,
		TierUSDMinorAmount: tier.USDMinorAmount,
		GoogleProductID:    tier.GoogleProductID,
		RechargeUSDMinor:   event.RechargeUSDMinor,
	}
	claim.WalletCommandID = walletFirstRechargeRewardCommandID(claim.ClaimID)
	if err := r.insertFirstRechargeRewardClaim(ctx, tx, claim); err != nil {
		return domain.PrepareResult{}, err
	}
	if err := tx.Commit(); err != nil {
		return domain.PrepareResult{}, err
	}
	return domain.PrepareResult{Claim: claim, Prepared: true, Reason: domain.ReasonEligible}, nil
}

func (r *Repository) prepareExistingFirstRechargeRewardClaim(ctx context.Context, tx *sql.Tx, claim domain.Claim, exists bool, sourceErr error, event domain.RechargeEvent, nowMS int64) (domain.PrepareResult, error) {
	if sourceErr != nil {
		return domain.PrepareResult{}, sourceErr
	}
	if !exists {
		return domain.PrepareResult{}, nil
	}
	if claim.UserID != event.UserID {
		return domain.PrepareResult{}, xerr.New(xerr.RequestConflict, "first recharge reward event payload conflicts")
	}
	if claim.GoogleProductID != "" && event.GoogleProductID != "" && !strings.EqualFold(claim.GoogleProductID, event.GoogleProductID) {
		return domain.PrepareResult{}, xerr.New(xerr.RequestConflict, "first recharge reward product payload conflicts")
	}
	if claim.Status == domain.StatusGranted {
		if err := tx.Commit(); err != nil {
			return domain.PrepareResult{}, err
		}
		return domain.PrepareResult{Claim: claim, AlreadyGranted: true, Reason: domain.ReasonAlreadyGranted}, nil
	}
	if claim.Status == domain.StatusFailed {
		if err := r.updateFirstRechargeRewardClaimPending(ctx, tx, claim.ClaimID, nowMS); err != nil {
			return domain.PrepareResult{}, err
		}
		claim.Status = domain.StatusPending
		claim.FailureReason = ""
		claim.UpdatedAtMS = nowMS
	}
	if err := tx.Commit(); err != nil {
		return domain.PrepareResult{}, err
	}
	return domain.PrepareResult{Claim: claim, Prepared: true, Reason: domain.ReasonPendingReward}, nil
}

// MarkFirstRechargeRewardClaimGranted 写回 wallet-service 资源组发放回执。
func (r *Repository) MarkFirstRechargeRewardClaimGranted(ctx context.Context, claimID string, walletGrantID string, grantedAtMS int64) (domain.Claim, error) {
	if r == nil || r.db == nil {
		return domain.Claim{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	_, err := r.db.ExecContext(ctx, `
		UPDATE first_recharge_reward_claims
		SET status = 'granted',
		    wallet_grant_id = ?,
		    failure_reason = '',
		    granted_at_ms = ?,
		    updated_at_ms = ?
		WHERE app_code = ? AND claim_id = ? AND status <> 'granted'`,
		walletGrantID, grantedAtMS, grantedAtMS, appcode.FromContext(ctx), claimID,
	)
	if err != nil {
		return domain.Claim{}, err
	}
	claim, exists, err := r.getFirstRechargeRewardClaimByID(ctx, claimID)
	if err != nil {
		return domain.Claim{}, err
	}
	if !exists {
		return domain.Claim{}, xerr.New(xerr.NotFound, "first recharge reward claim not found")
	}
	return claim, nil
}

// MarkFirstRechargeRewardClaimFailed 保留 claim，后续同一 wallet 事实重试会复用原钱包命令。
func (r *Repository) MarkFirstRechargeRewardClaimFailed(ctx context.Context, claimID string, failureReason string, nowMS int64) error {
	if r == nil || r.db == nil {
		return xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	_, err := r.db.ExecContext(ctx, `
		UPDATE first_recharge_reward_claims
		SET status = 'failed', failure_reason = ?, updated_at_ms = ?
		WHERE app_code = ? AND claim_id = ? AND status <> 'granted'`,
		truncateTaskFailure(failureReason), nowMS, appcode.FromContext(ctx), claimID,
	)
	return err
}

// ListFirstRechargeRewardClaims 返回后台发放记录，按创建时间倒序。
func (r *Repository) ListFirstRechargeRewardClaims(ctx context.Context, query domain.ClaimQuery) ([]domain.Claim, int64, error) {
	if r == nil || r.db == nil {
		return nil, 0, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	where, args := firstRechargeRewardClaimWhere(ctx, query)
	var total int64
	if err := r.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM first_recharge_reward_claims `+where, args...).Scan(&total); err != nil {
		return nil, 0, err
	}
	pageSize := normalizePageSize(query.PageSize)
	page := query.Page
	if page <= 0 {
		page = 1
	}
	args = append(args, pageSize, (page-1)*pageSize)
	rows, err := r.db.QueryContext(ctx, firstRechargeRewardClaimSelectSQL()+`
		`+where+`
		ORDER BY created_at_ms DESC, claim_id DESC
		LIMIT ? OFFSET ?`, args...)
	if err != nil {
		return nil, 0, err
	}
	defer rows.Close()
	return scanFirstRechargeRewardClaims(rows, total)
}

func (r *Repository) getFirstRechargeRewardConfigForUpdate(ctx context.Context, tx *sql.Tx) (domain.Config, bool, error) {
	row := tx.QueryRowContext(ctx, firstRechargeRewardConfigSelectSQL()+`
		WHERE app_code = ?
		FOR UPDATE`,
		appcode.FromContext(ctx),
	)
	config, err := scanFirstRechargeRewardConfig(row)
	if errors.Is(err, sql.ErrNoRows) {
		return domain.Config{}, false, nil
	}
	return config, err == nil, err
}

func (r *Repository) listFirstRechargeRewardTiers(ctx context.Context, tx *sql.Tx) ([]domain.Tier, error) {
	var rows *sql.Rows
	var err error
	if tx != nil {
		rows, err = tx.QueryContext(ctx, firstRechargeRewardTierSelectSQL()+`
			WHERE app_code = ?
			ORDER BY sort_order DESC, usd_minor_amount ASC, tier_id ASC`,
			appcode.FromContext(ctx),
		)
	} else {
		rows, err = r.db.QueryContext(ctx, firstRechargeRewardTierSelectSQL()+`
			WHERE app_code = ?
			ORDER BY sort_order DESC, usd_minor_amount ASC, tier_id ASC`,
			appcode.FromContext(ctx),
		)
	}
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	return scanFirstRechargeRewardTiers(rows)
}

func (r *Repository) listFirstRechargeRewardTiersForUpdate(ctx context.Context, tx *sql.Tx) ([]domain.Tier, error) {
	rows, err := tx.QueryContext(ctx, firstRechargeRewardTierSelectSQL()+`
		WHERE app_code = ?
		ORDER BY sort_order DESC, usd_minor_amount ASC, tier_id ASC
		FOR UPDATE`,
		appcode.FromContext(ctx),
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	return scanFirstRechargeRewardTiers(rows)
}

func (r *Repository) getFirstRechargeRewardClaimByEventForUpdate(ctx context.Context, tx *sql.Tx, eventID string) (domain.Claim, bool, error) {
	row := tx.QueryRowContext(ctx, firstRechargeRewardClaimSelectSQL()+`
		WHERE app_code = ? AND event_id = ?
		FOR UPDATE`,
		appcode.FromContext(ctx), eventID,
	)
	claim, err := scanFirstRechargeRewardClaim(row)
	if errors.Is(err, sql.ErrNoRows) {
		return domain.Claim{}, false, nil
	}
	return claim, err == nil, err
}

func (r *Repository) getFirstRechargeRewardClaimByTransactionForUpdate(ctx context.Context, tx *sql.Tx, transactionID string) (domain.Claim, bool, error) {
	row := tx.QueryRowContext(ctx, firstRechargeRewardClaimSelectSQL()+`
		WHERE app_code = ? AND transaction_id = ?
		FOR UPDATE`,
		appcode.FromContext(ctx), transactionID,
	)
	claim, err := scanFirstRechargeRewardClaim(row)
	if errors.Is(err, sql.ErrNoRows) {
		return domain.Claim{}, false, nil
	}
	return claim, err == nil, err
}

func (r *Repository) getFirstRechargeRewardClaimByUserTierForUpdate(ctx context.Context, tx *sql.Tx, userID int64, tierID int64) (domain.Claim, bool, error) {
	row := tx.QueryRowContext(ctx, firstRechargeRewardClaimSelectSQL()+`
		WHERE app_code = ? AND user_id = ? AND tier_id = ?
		FOR UPDATE`,
		appcode.FromContext(ctx), userID, tierID,
	)
	claim, err := scanFirstRechargeRewardClaim(row)
	if errors.Is(err, sql.ErrNoRows) {
		return domain.Claim{}, false, nil
	}
	return claim, err == nil, err
}

func (r *Repository) getFirstRechargeRewardClaimByID(ctx context.Context, claimID string) (domain.Claim, bool, error) {
	row := r.db.QueryRowContext(ctx, firstRechargeRewardClaimSelectSQL()+`
		WHERE app_code = ? AND claim_id = ?`,
		appcode.FromContext(ctx), claimID,
	)
	claim, err := scanFirstRechargeRewardClaim(row)
	if errors.Is(err, sql.ErrNoRows) {
		return domain.Claim{}, false, nil
	}
	return claim, err == nil, err
}

func (r *Repository) insertFirstRechargeRewardClaim(ctx context.Context, tx *sql.Tx, claim domain.Claim) error {
	_, err := tx.ExecContext(ctx, `
		INSERT INTO first_recharge_reward_claims (
			app_code, claim_id, event_id, transaction_id, command_id, user_id,
			tier_id, tier_code, resource_group_id, recharge_coin_amount, recharge_sequence,
			recharge_type, status, wallet_command_id, wallet_grant_id, failure_reason,
			recharged_at_ms, granted_at_ms, created_at_ms, updated_at_ms,
			tier_usd_minor_amount, google_product_id, recharge_usd_minor
		) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '', '', ?, 0, ?, ?, ?, ?, ?)`,
		appcode.FromContext(ctx), claim.ClaimID, claim.EventID, claim.TransactionID, claim.CommandID, claim.UserID,
		claim.TierID, claim.TierCode, claim.ResourceGroupID, claim.RechargeCoinAmount, claim.RechargeSequence,
		claim.RechargeType, claim.Status, claim.WalletCommandID, claim.RechargedAtMS, claim.CreatedAtMS, claim.UpdatedAtMS,
		claim.TierUSDMinorAmount, claim.GoogleProductID, claim.RechargeUSDMinor,
	)
	return err
}

func (r *Repository) updateFirstRechargeRewardClaimPending(ctx context.Context, tx *sql.Tx, claimID string, nowMS int64) error {
	_, err := tx.ExecContext(ctx, `
		UPDATE first_recharge_reward_claims
		SET status = 'pending',
		    wallet_grant_id = '',
		    failure_reason = '',
		    granted_at_ms = 0,
		    updated_at_ms = ?
		WHERE app_code = ? AND claim_id = ? AND status = 'failed'`,
		nowMS, appcode.FromContext(ctx), claimID,
	)
	return err
}

func scanFirstRechargeRewardConfig(row rowScanner) (domain.Config, error) {
	var config domain.Config
	err := row.Scan(&config.AppCode, &config.Enabled, &config.UpdatedByAdminID, &config.CreatedAtMS, &config.UpdatedAtMS)
	return config, err
}

func scanFirstRechargeRewardTiers(rows *sql.Rows) ([]domain.Tier, error) {
	items := make([]domain.Tier, 0)
	for rows.Next() {
		var item domain.Tier
		if err := rows.Scan(
			&item.TierID,
			&item.TierCode,
			&item.TierName,
			&item.MinCoinAmount,
			&item.MaxCoinAmount,
			&item.ResourceGroupID,
			&item.Status,
			&item.SortOrder,
			&item.CreatedAtMS,
			&item.UpdatedAtMS,
			&item.USDMinorAmount,
			&item.GoogleProductID,
			&item.DiscountPercent,
		); err != nil {
			return nil, err
		}
		items = append(items, item)
	}
	return items, rows.Err()
}

func scanFirstRechargeRewardClaims(rows *sql.Rows, total int64) ([]domain.Claim, int64, error) {
	items := make([]domain.Claim, 0)
	for rows.Next() {
		item, err := scanFirstRechargeRewardClaim(rows)
		if err != nil {
			return nil, 0, err
		}
		items = append(items, item)
	}
	if err := rows.Err(); err != nil {
		return nil, 0, err
	}
	return items, total, nil
}

func scanFirstRechargeRewardClaim(row rowScanner) (domain.Claim, error) {
	var claim domain.Claim
	err := row.Scan(
		&claim.ClaimID,
		&claim.EventID,
		&claim.TransactionID,
		&claim.CommandID,
		&claim.UserID,
		&claim.TierID,
		&claim.TierCode,
		&claim.ResourceGroupID,
		&claim.RechargeCoinAmount,
		&claim.RechargeSequence,
		&claim.RechargeType,
		&claim.Status,
		&claim.WalletCommandID,
		&claim.WalletGrantID,
		&claim.FailureReason,
		&claim.RechargedAtMS,
		&claim.GrantedAtMS,
		&claim.CreatedAtMS,
		&claim.UpdatedAtMS,
		&claim.TierUSDMinorAmount,
		&claim.GoogleProductID,
		&claim.RechargeUSDMinor,
	)
	return claim, err
}

func firstRechargeRewardConfigSelectSQL() string {
	return `SELECT app_code, enabled, updated_by_admin_id, created_at_ms, updated_at_ms
	        FROM first_recharge_reward_configs `
}

func firstRechargeRewardTierSelectSQL() string {
	return `SELECT tier_id, tier_code, tier_name, min_coin_amount, max_coin_amount,
	               resource_group_id, status, sort_order, created_at_ms, updated_at_ms,
	               usd_minor_amount, google_product_id, discount_percent
	        FROM first_recharge_reward_tiers `
}

func firstRechargeRewardClaimSelectSQL() string {
	return `SELECT claim_id, event_id, transaction_id, command_id, user_id, tier_id, tier_code,
	               resource_group_id, recharge_coin_amount, recharge_sequence, recharge_type,
	               status, wallet_command_id, wallet_grant_id, failure_reason, recharged_at_ms,
	               granted_at_ms, created_at_ms, updated_at_ms,
	               tier_usd_minor_amount, google_product_id, recharge_usd_minor
	        FROM first_recharge_reward_claims `
}

func firstRechargeRewardClaimWhere(ctx context.Context, query domain.ClaimQuery) (string, []any) {
	conditions := []string{"app_code = ?"}
	args := []any{appcode.FromContext(ctx)}
	if query.Status != "" {
		conditions = append(conditions, "status = ?")
		args = append(args, query.Status)
	}
	if query.UserID > 0 {
		conditions = append(conditions, "user_id = ?")
		args = append(args, query.UserID)
	}
	return "WHERE " + strings.Join(conditions, " AND "), args
}

func matchFirstRechargeRewardTier(tiers []domain.Tier, googleProductID string) (domain.Tier, bool) {
	normalizedGoogleProductID := strings.ToLower(strings.TrimSpace(googleProductID))
	if normalizedGoogleProductID == "" {
		return domain.Tier{}, false
	}
	for _, tier := range tiers {
		if tier.Status != domain.TierStatusActive || tier.ResourceGroupID <= 0 {
			continue
		}
		if strings.ToLower(strings.TrimSpace(tier.GoogleProductID)) == normalizedGoogleProductID {
			return tier, true
		}
	}
	return domain.Tier{}, false
}

func walletFirstRechargeRewardCommandID(claimID string) string {
	return fmt.Sprintf("wfr_%s", claimID)
}
