package rpc

import (
	"context"
	"sync"
	"sync/atomic"
	"time"

	"github.com/JekaMas/workerpool"
	"github.com/ethereum/go-ethereum/metrics"
)

type SafePool struct {
	executionPool atomic.Pointer[workerpool.WorkerPool]

	sync.RWMutex

	size    int
	timeout time.Duration

	service   string       // the service using ep
	processed atomic.Int64 // keeps count of total processed requests

	close     chan struct{}
	closeOnce sync.Once

	// Skip sending task to execution pool
	fastPath bool
}

func NewExecutionPool(initialSize int, timeout time.Duration, service string, report bool) *SafePool {
	sp := &SafePool{
		size:    initialSize,
		timeout: timeout,
		service: service,
		close:   make(chan struct{}),
	}

	if initialSize == 0 {
		sp.fastPath = true

		return sp
	}

	sp.executionPool.Store(workerpool.New(initialSize))

	if metrics.Enabled() && report {
		go sp.reportMetrics(3 * time.Second)
	}

	return sp
}

func (s *SafePool) Submit(ctx context.Context, fn func() error) (<-chan error, bool) {
	if s.fastPath {
		go func() {
			_ = fn()
		}()

		return nil, true
	}

	pool := s.executionPool.Load()
	if pool == nil {
		return nil, false
	}

	return pool.Submit(ctx, fn, s.Timeout()), true
}

func (s *SafePool) ChangeSize(n int) {
	oldPool := s.executionPool.Swap(workerpool.New(n))

	if oldPool != nil {
		go func() {
			oldPool.StopWait()
		}()
	}

	s.Lock()
	s.size = n
	s.Unlock()
}

func (s *SafePool) ChangeTimeout(n time.Duration) {
	s.Lock()
	defer s.Unlock()

	s.timeout = n
}

func (s *SafePool) Timeout() time.Duration {
	s.RLock()
	defer s.RUnlock()

	return s.timeout
}

func (s *SafePool) Size() int {
	s.RLock()
	defer s.RUnlock()

	return s.size
}

func (s *SafePool) Stop() {
	s.closeOnce.Do(func() {
		close(s.close)
	})

	if s.executionPool.Load() != nil {
		s.executionPool.Load().Stop()
	}
}

// reportMetrics reports the metrics after every `refresh` time interval
// regarding the execution pool.
func (s *SafePool) reportMetrics(refresh time.Duration) {
	var (
		epWorkerCountGuage           *metrics.Gauge
		epWaitingQueueGuage          *metrics.Gauge
		epProcessedRequestsHistogram metrics.Histogram
	)

	ticker := time.NewTicker(refresh)

	for {
		select {
		case <-ticker.C:
			ep := s.executionPool.Load()

			epWorkerCountGuage, epWaitingQueueGuage, epProcessedRequestsHistogram = newEpMetrics(s.service)

			epWorkerCountGuage.Update(ep.GetWorkerCount())
			epWaitingQueueGuage.Update(int64(ep.WaitingQueueSize()))
			epProcessedRequestsHistogram.Update(s.processed.Load())

			s.processed.Store(0)
		case <-s.close:
			ticker.Stop()

			return
		}
	}
}
