package walletnotice

import (
	"context"
	"log/slog"
	"strings"
	"time"

	"hyapp/pkg/logx"
)

// WalletNoticeWorkerOptions 是钱包私有通知 worker 的运行参数。
type WalletNoticeWorkerOptions struct {
	WorkerID       string
	PollInterval   time.Duration
	BatchSize      int
	LockTTL        time.Duration
	PublishTimeout time.Duration
	MaxRetryCount  int
	InitialBackoff time.Duration
	MaxBackoff     time.Duration
}

// RunWalletBalanceWorker 持续消费 wallet_outbox 的用户私有通知事件。
func (s *Service) RunWalletBalanceWorker(ctx context.Context, options WalletNoticeWorkerOptions) {
	options = normalizeWalletNoticeWorkerOptions(options, s.cfg.NodeID)
	ticker := time.NewTicker(options.PollInterval)
	defer ticker.Stop()
	for {
		processed, err := s.ProcessWalletBalanceNotices(ctx, options)
		if err != nil && ctx.Err() == nil {
			logx.Error(ctx, "notice_wallet_balance_batch_failed", err, slog.String("worker_id", options.WorkerID))
		}
		if processed >= options.BatchSize {
			// 批次打满说明有积压，立即继续 drain，避免固定 tick 放大私有余额通知延迟。
			continue
		}
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
		}
	}
}

func normalizeWalletNoticeWorkerOptions(options WalletNoticeWorkerOptions, nodeID string) WalletNoticeWorkerOptions {
	if strings.TrimSpace(options.WorkerID) == "" {
		if strings.TrimSpace(nodeID) == "" {
			nodeID = "notice"
		}
		options.WorkerID = "wallet-notice-" + nodeID
	}
	if options.PollInterval <= 0 {
		options.PollInterval = time.Second
	}
	if options.BatchSize <= 0 {
		options.BatchSize = 100
	}
	if options.LockTTL <= 0 {
		options.LockTTL = 30 * time.Second
	}
	if options.PublishTimeout <= 0 {
		options.PublishTimeout = 3 * time.Second
	}
	if options.MaxRetryCount <= 0 {
		options.MaxRetryCount = 10
	}
	if options.InitialBackoff <= 0 {
		options.InitialBackoff = 5 * time.Second
	}
	if options.MaxBackoff <= 0 {
		options.MaxBackoff = 5 * time.Minute
	}
	return options
}

func walletNoticeBackoff(retryCount int, options WalletNoticeWorkerOptions) time.Duration {
	if retryCount <= 0 {
		return options.InitialBackoff
	}
	backoff := options.InitialBackoff
	for i := 1; i < retryCount; i++ {
		if backoff >= options.MaxBackoff/2 {
			return options.MaxBackoff
		}
		backoff *= 2
	}
	if backoff > options.MaxBackoff {
		return options.MaxBackoff
	}
	return backoff
}
