package producer

import (
	"time"
	"sync"
	zlog "amuz.es/src/infra/goutils/logger/zap"
	"amuz.es/src/infra/goutils/handler"
	"runtime"
	"go.uber.org/zap"
	"go.uber.org/multierr"
	"context"
	"os"
)

type osMetric struct {
	handler        *handler.Handler
	sampleDuration time.Duration
	metricChanged  chan<- OSMetricInfo
	info           OSMetricInfo
	logger         *zap.SugaredLogger
}

type osMetricApplier func(*OSMetricInfo, chan<- error, *sync.WaitGroup)
type OSMetricPointGen func() (string, []map[string]string, []map[string]interface{})

type OsMetric interface {
	StartMonitoring()
}

func NewOsMetric(
	mainHandler *handler.Handler,
	sampleDuration time.Duration,
) (<-chan OSMetricInfo) {

	mainHandler.IncreaseWait()
	aggHandler := handler.NewHandler(context.Background())

	var (
		oin, oout = NewOSMetricQueue()
	)

	go func() {
		<-mainHandler.Done()
		aggHandler.GracefulWait()
		close(oin)
		mainHandler.DecreaseWait()
	}()

	reader := &osMetric{
		handler:        aggHandler,
		sampleDuration: sampleDuration,
		metricChanged:  oin,
		logger:         zlog.New(nil, "os"),
	}
	aggHandler.IncreaseWait()
	go reader.StartMonitoring()
	return oout
}

func (p *osMetric) StartMonitoring() {
	defer func() {
		if err := recover(); err != nil {
			p.handler.NotifyError(err.(error))
		}
		p.handler.DecreaseWait()
	}()

	ticker := time.Tick(p.sampleDuration)
	defer p.logger.Info("os metric monitor stopped")
	p.logger.Infof("os metric monitor started with %s", p.sampleDuration)

	metricRecorders := p.availableMetrics()

	p.info.Host, _ = os.Hostname()
	for {
		select {
		case now := <-ticker:
			/*
			SELECT mean("tempeture") as tempeture FROM "processor_tempeture" WHERE $timeFilter GROUP BY "processor", time(5s) fill(previous)
			*/

			if err := p.applyMetrics(&p.info, metricRecorders...); err != nil {
				p.logger.Errorf("reading os metric error occoured!: ", err)
				continue
			}

			p.info.At = now

			if p.metricChanged != nil {
				select {
				case p.metricChanged <- p.info:
				default:
					runtime.Gosched()
				}
			}
		case <-p.handler.Done():
			return
		}
	}
}
func (p *osMetric) applyMetrics(info *OSMetricInfo, appliers ...osMetricApplier) (err error) {
	var (
		osMetricReadWaiter sync.WaitGroup
		errorQueue         = make(chan error, len(appliers))
	)

	for _, applier := range appliers {
		osMetricReadWaiter.Add(1)
		go applier(info, errorQueue, &osMetricReadWaiter)
	}
	osMetricReadWaiter.Wait()
	close(errorQueue)
	for newErr := range errorQueue {
		err = multierr.Append(err, newErr)
	}

	return
}
