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/registrationreward"
)

const registrationRewardClaimDisplayTimeSQL = "COALESCE(NULLIF(claimed_at_ms, 0), created_at_ms)"

// GetRegistrationRewardConfig 读取注册奖励当前配置；未配置是正常后台首屏状态。
func (r *Repository) GetRegistrationRewardConfig(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, registrationRewardConfigSelectSQL()+`
		WHERE app_code = ?`,
		appcode.FromContext(ctx),
	)
	config, err := scanRegistrationRewardConfig(row)
	if errors.Is(err, sql.ErrNoRows) {
		return domain.Config{}, false, nil
	}
	return config, err == nil, err
}

// UpdateRegistrationRewardConfig 原子 upsert 配置；created_at_ms 只在首次创建时写入。
func (r *Repository) UpdateRegistrationRewardConfig(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")
	}
	_, err := r.db.ExecContext(ctx, `
		INSERT INTO registration_reward_configs (
			app_code, enabled, reward_type, coin_amount, resource_group_id, daily_limit,
			updated_by_admin_id, created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
		ON DUPLICATE KEY UPDATE
			enabled = VALUES(enabled),
			reward_type = VALUES(reward_type),
			coin_amount = VALUES(coin_amount),
			resource_group_id = VALUES(resource_group_id),
			daily_limit = VALUES(daily_limit),
			updated_by_admin_id = VALUES(updated_by_admin_id),
			updated_at_ms = VALUES(updated_at_ms)`,
		appcode.FromContext(ctx), config.Enabled, config.RewardType, config.CoinAmount, config.ResourceGroupID,
		config.DailyLimit, config.UpdatedByAdminID, nowMS, nowMS,
	)
	if err != nil {
		return domain.Config{}, err
	}
	updated, exists, err := r.GetRegistrationRewardConfig(ctx)
	if err != nil {
		return domain.Config{}, err
	}
	if !exists {
		return domain.Config{}, xerr.New(xerr.NotFound, "registration reward config not found")
	}
	return updated, nil
}

// GetRegistrationRewardEligibility 用只读事实判断 App 是否可领取；failed claim 不算“已领过”。
func (r *Repository) GetRegistrationRewardEligibility(ctx context.Context, userID int64, rewardDay string, nowMS int64) (domain.Eligibility, error) {
	if r == nil || r.db == nil {
		return domain.Eligibility{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	config, exists, err := r.GetRegistrationRewardConfig(ctx)
	if err != nil {
		return domain.Eligibility{}, err
	}
	used, err := r.registrationRewardDailyUsed(ctx, rewardDay)
	if err != nil {
		return domain.Eligibility{}, err
	}
	eligibility := eligibilityFromConfig(config, exists, used, rewardDay, nowMS)
	if !eligibility.Enabled {
		return eligibility, nil
	}
	status, err := r.registrationRewardUserClaimStatus(ctx, userID)
	if err != nil {
		return domain.Eligibility{}, err
	}
	if status == domain.StatusGranted || status == domain.StatusPending {
		eligibility.CanReceive = false
		eligibility.Reason = domain.ReasonAlreadyClaimed
		return eligibility, nil
	}
	if eligibility.DailyLimit > 0 && eligibility.DailyUsed >= eligibility.DailyLimit {
		eligibility.CanReceive = false
		eligibility.Reason = domain.ReasonDailyLimitReached
		return eligibility, nil
	}
	if !validRegistrationRewardConfig(config) {
		eligibility.CanReceive = false
		eligibility.Reason = domain.ReasonInvalidConfig
		return eligibility, nil
	}
	eligibility.CanReceive = true
	eligibility.Reason = domain.ReasonEligible
	return eligibility, nil
}

// PrepareRegistrationRewardClaim 在一个 MySQL 事务内完成幂等判断、每日份数预留和 pending claim 写入。
func (r *Repository) PrepareRegistrationRewardClaim(ctx context.Context, command domain.IssueCommand, rewardDay string, 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.getRegistrationRewardConfigForUpdate(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()
	}
	if !validRegistrationRewardConfig(config) {
		return domain.PrepareResult{Reason: domain.ReasonInvalidConfig}, tx.Commit()
	}

	if existing, exists, err := r.getRegistrationRewardClaimByCommandForUpdate(ctx, tx, command.CommandID); err != nil || exists {
		if err != nil {
			return domain.PrepareResult{}, err
		}
		if existing.UserID != command.UserID {
			return domain.PrepareResult{}, xerr.New(xerr.RequestConflict, "registration reward command payload conflicts")
		}
		if existing.Status == domain.StatusGranted {
			if err := tx.Commit(); err != nil {
				return domain.PrepareResult{}, err
			}
			return domain.PrepareResult{Claim: existing, AlreadyGranted: true, Reason: domain.ReasonAlreadyClaimed}, nil
		}
		if existing.Status == domain.StatusFailed {
			ok, reason, err := r.reserveRegistrationRewardDailySlot(ctx, tx, config, rewardDay, nowMS)
			if err != nil || !ok {
				if err != nil {
					return domain.PrepareResult{}, err
				}
				return domain.PrepareResult{Claim: existing, Reason: reason}, tx.Commit()
			}
			existing = claimWithConfig(existing, config, rewardDay, nowMS)
			if err := r.updateRegistrationRewardClaimPending(ctx, tx, existing); err != nil {
				return domain.PrepareResult{}, err
			}
		}
		if err := tx.Commit(); err != nil {
			return domain.PrepareResult{}, err
		}
		return domain.PrepareResult{Claim: existing, Prepared: true}, nil
	}

	if existing, exists, err := r.getRegistrationRewardClaimByUserForUpdate(ctx, tx, command.UserID); err != nil || exists {
		if err != nil {
			return domain.PrepareResult{}, err
		}
		if existing.Status == domain.StatusGranted {
			if err := tx.Commit(); err != nil {
				return domain.PrepareResult{}, err
			}
			return domain.PrepareResult{Claim: existing, AlreadyGranted: true, Reason: domain.ReasonAlreadyClaimed}, nil
		}
		if existing.Status == domain.StatusPending {
			return domain.PrepareResult{Claim: existing, Reason: domain.ReasonAlreadyClaimed}, tx.Commit()
		}
		ok, reason, err := r.reserveRegistrationRewardDailySlot(ctx, tx, config, rewardDay, nowMS)
		if err != nil || !ok {
			if err != nil {
				return domain.PrepareResult{}, err
			}
			return domain.PrepareResult{Claim: existing, Reason: reason}, tx.Commit()
		}
		existing = claimWithConfig(existing, config, rewardDay, nowMS)
		existing.CommandID = command.CommandID
		existing.WalletCommandID = walletRegistrationRewardCommandID(existing.ClaimID)
		if err := r.updateRegistrationRewardClaimPending(ctx, tx, existing); err != nil {
			return domain.PrepareResult{}, err
		}
		if err := tx.Commit(); err != nil {
			return domain.PrepareResult{}, err
		}
		return domain.PrepareResult{Claim: existing, Prepared: true}, nil
	}

	ok, reason, err := r.reserveRegistrationRewardDailySlot(ctx, tx, config, rewardDay, nowMS)
	if err != nil || !ok {
		if err != nil {
			return domain.PrepareResult{}, err
		}
		return domain.PrepareResult{Reason: reason}, tx.Commit()
	}
	claim := domain.Claim{
		ClaimID:         idgen.New("rclaim"),
		CommandID:       command.CommandID,
		UserID:          command.UserID,
		RewardDay:       rewardDay,
		RewardType:      config.RewardType,
		CoinAmount:      config.CoinAmount,
		ResourceGroupID: config.ResourceGroupID,
		Status:          domain.StatusPending,
		CreatedAtMS:     nowMS,
		UpdatedAtMS:     nowMS,
	}
	claim.WalletCommandID = walletRegistrationRewardCommandID(claim.ClaimID)
	if err := r.insertRegistrationRewardClaim(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}, nil
}

// MarkRegistrationRewardClaimGranted 把外部钱包回执写回 activity 事实，并递增当天 granted 计数。
func (r *Repository) MarkRegistrationRewardClaimGranted(ctx context.Context, claimID string, walletTransactionID string, resourceGrantID string, grantedAtMS int64) (domain.Claim, error) {
	if r == nil || r.db == nil {
		return domain.Claim{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return domain.Claim{}, err
	}
	defer func() { _ = tx.Rollback() }()

	claim, err := r.getRegistrationRewardClaimByIDForUpdate(ctx, tx, claimID)
	if err != nil {
		return domain.Claim{}, err
	}
	if claim.Status == domain.StatusGranted {
		if err := tx.Commit(); err != nil {
			return domain.Claim{}, err
		}
		return claim, nil
	}
	if _, err := tx.ExecContext(ctx, `
		UPDATE registration_reward_claims
		SET status = 'granted',
		    wallet_transaction_id = ?,
		    resource_grant_id = ?,
		    failure_reason = '',
		    claimed_at_ms = ?,
		    updated_at_ms = ?
		WHERE app_code = ? AND claim_id = ?`,
		walletTransactionID, resourceGrantID, grantedAtMS, grantedAtMS, appcode.FromContext(ctx), claimID,
	); err != nil {
		return domain.Claim{}, err
	}
	if _, err := tx.ExecContext(ctx, `
		UPDATE registration_reward_daily_counters
		SET granted_count = granted_count + 1, updated_at_ms = ?
		WHERE app_code = ? AND reward_day = ?`,
		grantedAtMS, appcode.FromContext(ctx), claim.RewardDay,
	); err != nil {
		return domain.Claim{}, err
	}
	if err := tx.Commit(); err != nil {
		return domain.Claim{}, err
	}
	claim.Status = domain.StatusGranted
	claim.WalletTransactionID = walletTransactionID
	claim.ResourceGrantID = resourceGrantID
	claim.FailureReason = ""
	claim.ClaimedAtMS = grantedAtMS
	claim.UpdatedAtMS = grantedAtMS
	return claim, nil
}

// MarkRegistrationRewardClaimFailed 释放 pending 预留份数；这样钱包短暂失败不会永久占用每日额度。
func (r *Repository) MarkRegistrationRewardClaimFailed(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")
	}
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer func() { _ = tx.Rollback() }()

	claim, err := r.getRegistrationRewardClaimByIDForUpdate(ctx, tx, claimID)
	if err != nil {
		return err
	}
	if claim.Status == domain.StatusGranted {
		return tx.Commit()
	}
	if claim.Status == domain.StatusPending {
		if _, err := tx.ExecContext(ctx, `
			UPDATE registration_reward_daily_counters
			SET reserved_count = IF(reserved_count > 0, reserved_count - 1, 0),
			    failed_count = failed_count + 1,
			    updated_at_ms = ?
			WHERE app_code = ? AND reward_day = ?`,
			nowMS, appcode.FromContext(ctx), claim.RewardDay,
		); err != nil {
			return err
		}
	}
	if _, err := tx.ExecContext(ctx, `
		UPDATE registration_reward_claims
		SET status = 'failed', failure_reason = ?, updated_at_ms = ?
		WHERE app_code = ? AND claim_id = ?`,
		truncateTaskFailure(failureReason), nowMS, appcode.FromContext(ctx), claimID,
	); err != nil {
		return err
	}
	return tx.Commit()
}

// ListRegistrationRewardClaims 返回后台领取事实列表；按领取/创建时间倒序便于运营复核最近发放。
func (r *Repository) ListRegistrationRewardClaims(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 := registrationRewardClaimWhere(ctx, query)
	var total int64
	if err := r.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM registration_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, registrationRewardClaimSelectSQL()+`
		`+where+`
		ORDER BY `+registrationRewardClaimDisplayTimeSQL+` DESC, claim_id DESC
		LIMIT ? OFFSET ?`, args...)
	if err != nil {
		return nil, 0, err
	}
	defer rows.Close()
	return scanRegistrationRewardClaims(rows, total)
}

// CountRegistrationRewardGrantedByDay 读取 UTC 当天已发放份数；后台只展示成功发放数量，不把 pending 预留量算作已领取。
func (r *Repository) CountRegistrationRewardGrantedByDay(ctx context.Context, rewardDay string) (int64, error) {
	if r == nil || r.db == nil {
		return 0, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	rewardDay = strings.TrimSpace(rewardDay)
	if rewardDay == "" {
		return 0, nil
	}
	var count int64
	err := r.db.QueryRowContext(ctx, `
		SELECT granted_count
		FROM registration_reward_daily_counters
		WHERE app_code = ? AND reward_day = ?`,
		appcode.FromContext(ctx), rewardDay,
	).Scan(&count)
	if errors.Is(err, sql.ErrNoRows) {
		return 0, nil
	}
	return count, err
}

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

func (r *Repository) reserveRegistrationRewardDailySlot(ctx context.Context, tx *sql.Tx, config domain.Config, rewardDay string, nowMS int64) (bool, string, error) {
	if _, err := tx.ExecContext(ctx, `
		INSERT INTO registration_reward_daily_counters (
			app_code, reward_day, reserved_count, granted_count, failed_count, updated_at_ms
		) VALUES (?, ?, 0, 0, 0, ?)
		ON DUPLICATE KEY UPDATE updated_at_ms = updated_at_ms`,
		appcode.FromContext(ctx), rewardDay, nowMS,
	); err != nil {
		return false, "", err
	}
	var reserved int64
	if err := tx.QueryRowContext(ctx, `
		SELECT reserved_count
		FROM registration_reward_daily_counters
		WHERE app_code = ? AND reward_day = ?
		FOR UPDATE`,
		appcode.FromContext(ctx), rewardDay,
	).Scan(&reserved); err != nil {
		return false, "", err
	}
	if config.DailyLimit > 0 && reserved >= config.DailyLimit {
		return false, domain.ReasonDailyLimitReached, nil
	}
	if _, err := tx.ExecContext(ctx, `
		UPDATE registration_reward_daily_counters
		SET reserved_count = reserved_count + 1, updated_at_ms = ?
		WHERE app_code = ? AND reward_day = ?`,
		nowMS, appcode.FromContext(ctx), rewardDay,
	); err != nil {
		return false, "", err
	}
	return true, "", nil
}

func (r *Repository) registrationRewardDailyUsed(ctx context.Context, rewardDay string) (int64, error) {
	var used int64
	err := r.db.QueryRowContext(ctx, `
		SELECT reserved_count
		FROM registration_reward_daily_counters
		WHERE app_code = ? AND reward_day = ?`,
		appcode.FromContext(ctx), rewardDay,
	).Scan(&used)
	if errors.Is(err, sql.ErrNoRows) {
		return 0, nil
	}
	return used, err
}

func (r *Repository) registrationRewardUserClaimStatus(ctx context.Context, userID int64) (string, error) {
	var status string
	err := r.db.QueryRowContext(ctx, `
		SELECT status
		FROM registration_reward_claims
		WHERE app_code = ? AND user_id = ?`,
		appcode.FromContext(ctx), userID,
	).Scan(&status)
	if errors.Is(err, sql.ErrNoRows) {
		return "", nil
	}
	return status, err
}

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

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

func (r *Repository) getRegistrationRewardClaimByIDForUpdate(ctx context.Context, tx *sql.Tx, claimID string) (domain.Claim, error) {
	row := tx.QueryRowContext(ctx, registrationRewardClaimSelectSQL()+`
		WHERE app_code = ? AND claim_id = ?
		FOR UPDATE`,
		appcode.FromContext(ctx), claimID,
	)
	claim, err := scanRegistrationRewardClaim(row)
	if errors.Is(err, sql.ErrNoRows) {
		return domain.Claim{}, xerr.New(xerr.NotFound, "registration reward claim not found")
	}
	return claim, err
}

func (r *Repository) insertRegistrationRewardClaim(ctx context.Context, tx *sql.Tx, claim domain.Claim) error {
	_, err := tx.ExecContext(ctx, `
		INSERT INTO registration_reward_claims (
			app_code, claim_id, command_id, user_id, reward_day, reward_type,
			coin_amount, resource_group_id, status, wallet_command_id,
			wallet_transaction_id, resource_grant_id, failure_reason, claimed_at_ms,
			created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '', '', '', 0, ?, ?)`,
		appcode.FromContext(ctx), claim.ClaimID, claim.CommandID, claim.UserID, claim.RewardDay, claim.RewardType,
		claim.CoinAmount, claim.ResourceGroupID, claim.Status, claim.WalletCommandID, claim.CreatedAtMS, claim.UpdatedAtMS,
	)
	return err
}

func (r *Repository) updateRegistrationRewardClaimPending(ctx context.Context, tx *sql.Tx, claim domain.Claim) error {
	_, err := tx.ExecContext(ctx, `
		UPDATE registration_reward_claims
		SET command_id = ?,
		    reward_day = ?,
		    reward_type = ?,
		    coin_amount = ?,
		    resource_group_id = ?,
		    status = 'pending',
		    wallet_command_id = ?,
		    wallet_transaction_id = '',
		    resource_grant_id = '',
		    failure_reason = '',
		    claimed_at_ms = 0,
		    updated_at_ms = ?
		WHERE app_code = ? AND claim_id = ?`,
		claim.CommandID, claim.RewardDay, claim.RewardType, claim.CoinAmount, claim.ResourceGroupID,
		claim.WalletCommandID, claim.UpdatedAtMS, appcode.FromContext(ctx), claim.ClaimID,
	)
	return err
}

func scanRegistrationRewardClaims(rows *sql.Rows, total int64) ([]domain.Claim, int64, error) {
	items := make([]domain.Claim, 0)
	for rows.Next() {
		item, err := scanRegistrationRewardClaim(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 scanRegistrationRewardConfig(row rowScanner) (domain.Config, error) {
	var config domain.Config
	err := row.Scan(
		&config.AppCode,
		&config.Enabled,
		&config.RewardType,
		&config.CoinAmount,
		&config.ResourceGroupID,
		&config.DailyLimit,
		&config.UpdatedByAdminID,
		&config.CreatedAtMS,
		&config.UpdatedAtMS,
	)
	return config, err
}

func scanRegistrationRewardClaim(row rowScanner) (domain.Claim, error) {
	var claim domain.Claim
	err := row.Scan(
		&claim.ClaimID,
		&claim.CommandID,
		&claim.UserID,
		&claim.RewardDay,
		&claim.RewardType,
		&claim.CoinAmount,
		&claim.ResourceGroupID,
		&claim.Status,
		&claim.WalletCommandID,
		&claim.WalletTransactionID,
		&claim.ResourceGrantID,
		&claim.FailureReason,
		&claim.ClaimedAtMS,
		&claim.CreatedAtMS,
		&claim.UpdatedAtMS,
	)
	return claim, err
}

func registrationRewardConfigSelectSQL() string {
	return `SELECT app_code, enabled, reward_type, coin_amount, resource_group_id, daily_limit,
	               updated_by_admin_id, created_at_ms, updated_at_ms
	        FROM registration_reward_configs `
}

func registrationRewardClaimSelectSQL() string {
	return `SELECT claim_id, command_id, user_id, reward_day, reward_type, coin_amount, resource_group_id,
	               status, wallet_command_id, wallet_transaction_id, resource_grant_id, failure_reason,
	               claimed_at_ms, created_at_ms, updated_at_ms
	        FROM registration_reward_claims `
}

func registrationRewardClaimWhere(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)
	}
	if query.ClaimedStartMS > 0 {
		conditions = append(conditions, registrationRewardClaimDisplayTimeSQL+" >= ?")
		args = append(args, query.ClaimedStartMS)
	}
	if query.ClaimedEndMS > 0 {
		conditions = append(conditions, registrationRewardClaimDisplayTimeSQL+" <= ?")
		args = append(args, query.ClaimedEndMS)
	}
	return "WHERE " + strings.Join(conditions, " AND "), args
}

func eligibilityFromConfig(config domain.Config, exists bool, used int64, rewardDay string, nowMS int64) domain.Eligibility {
	reason := domain.ReasonEligible
	if !exists {
		reason = domain.ReasonNotConfigured
	} else if !config.Enabled {
		reason = domain.ReasonDisabled
	}
	remaining := int64(0)
	if config.DailyLimit > 0 && used < config.DailyLimit {
		remaining = config.DailyLimit - used
	}
	return domain.Eligibility{
		CanReceive:      exists && config.Enabled,
		Reason:          reason,
		Enabled:         exists && config.Enabled,
		RewardType:      config.RewardType,
		CoinAmount:      config.CoinAmount,
		ResourceGroupID: config.ResourceGroupID,
		DailyLimit:      config.DailyLimit,
		DailyUsed:       used,
		DailyRemaining:  remaining,
		RewardDay:       rewardDay,
		ServerTimeMS:    nowMS,
	}
}

func validRegistrationRewardConfig(config domain.Config) bool {
	switch config.RewardType {
	case domain.RewardTypeCoin:
		return config.CoinAmount > 0
	case domain.RewardTypeResourceGroup:
		return config.ResourceGroupID > 0
	default:
		return false
	}
}

func claimWithConfig(claim domain.Claim, config domain.Config, rewardDay string, nowMS int64) domain.Claim {
	claim.RewardDay = rewardDay
	claim.RewardType = config.RewardType
	claim.CoinAmount = config.CoinAmount
	claim.ResourceGroupID = config.ResourceGroupID
	claim.Status = domain.StatusPending
	claim.FailureReason = ""
	claim.ClaimedAtMS = 0
	claim.UpdatedAtMS = nowMS
	return claim
}

func walletRegistrationRewardCommandID(claimID string) string {
	return fmt.Sprintf("wreg_%s", claimID)
}
