package notification

import (
	"context"
	"net/http"
	"sync"
	"time"

	"github.com/artifacthub/hub/internal/hub"
	"github.com/patrickmn/go-cache"
	"github.com/spf13/viper"
)

const (
	defaultNumWorkers      = 2
	cacheDefaultExpiration = 5 * time.Minute
	cacheCleanupInterval   = 10 * time.Minute
)

// Services is a wrapper around several internal services used to handle
// notifications deliveries.
type Services struct {
	DB                  hub.DB
	ES                  hub.EmailSender
	NotificationManager hub.NotificationManager
	SubscriptionManager hub.SubscriptionManager
	PackageManager      hub.PackageManager
}

// Dispatcher handles a group of workers in charge of delivering notifications.
type Dispatcher struct {
	numWorkers int
	workers    []*Worker
}

// NewDispatcher creates a new Dispatcher instance.
func NewDispatcher(cfg *viper.Viper, svc *Services, opts ...func(d *Dispatcher)) *Dispatcher {
	// Setup dispatcher
	d := &Dispatcher{
		numWorkers: defaultNumWorkers,
	}
	for _, o := range opts {
		o(d)
	}

	// Setup and launch workers
	c := cache.New(cacheDefaultExpiration, cacheCleanupInterval)
	baseURL := cfg.GetString("server.baseURL")
	httpClient := &http.Client{Timeout: 10 * time.Second}
	d.workers = make([]*Worker, 0, d.numWorkers)
	for i := 0; i < d.numWorkers; i++ {
		d.workers = append(d.workers, NewWorker(svc, c, baseURL, httpClient))
	}

	return d
}

// WithNumWorkers allows providing a specific number of workers for a
// Dispatcher instance.
func WithNumWorkers(n int) func(d *Dispatcher) {
	return func(d *Dispatcher) {
		d.numWorkers = n
	}
}

// Run starts the workers and lets them run until the dispatcher is asked to
// stop via the context provided.
func (d *Dispatcher) Run(ctx context.Context, wg *sync.WaitGroup) {
	defer wg.Done()

	// Start workers
	wwg := &sync.WaitGroup{}
	wctx, stopWorkers := context.WithCancel(context.Background())
	for _, w := range d.workers {
		wwg.Add(1)
		go w.Run(wctx, wwg)
	}

	// Stop workers when dispatcher is asked to stop
	<-ctx.Done()
	stopWorkers()
	wwg.Wait()
}
