package consumer

import (
	"time"
	"strconv"
	"amuz.es/src/infra/goutils/handler"
	"amuz.es/src/infra/cpu_ctrl/producer"
	"github.com/influxdata/influxdb/client/v2"
	zlog "amuz.es/src/infra/goutils/logger/zap"
	"go.uber.org/zap"
	"unsafe"
	"sync/atomic"
)

type processorData struct {
	Tempeture float64
	FanSpeed  int
}

type influxMetric struct {
	host              string
	processorCount    int
	handler           *handler.Handler
	fanSpeedConsumer  chan producer.FanspeedInfo
	tempetureConsumer chan producer.TempetureInfo
	osMetricConsumer  chan producer.OSMetricInfo
	logger            *zap.SugaredLogger
}

type InfluxMetric interface {
	FanSpeedConsumer() chan<- producer.FanspeedInfo
	TempetureConsumer() chan<- producer.TempetureInfo
	OsMetricConsumer() chan<- producer.OSMetricInfo
	StartLogging()
}

func NewInfluxMetric(host string, processorCount int, handler *handler.Handler) InfluxMetric {
	return &influxMetric{
		host:              host,
		processorCount:    processorCount,
		handler:           handler,
		fanSpeedConsumer:  make(chan producer.FanspeedInfo, processorCount),
		tempetureConsumer: make(chan producer.TempetureInfo, processorCount),
		osMetricConsumer:  make(chan producer.OSMetricInfo, 1),
		logger:            zlog.New(nil, "influx"),
	}
}

func (m *influxMetric) FanSpeedConsumer() chan<- producer.FanspeedInfo   { return m.fanSpeedConsumer }
func (m *influxMetric) TempetureConsumer() chan<- producer.TempetureInfo { return m.tempetureConsumer }
func (m *influxMetric) OsMetricConsumer() chan<- producer.OSMetricInfo   { return m.osMetricConsumer }

func (m *influxMetric) StartLogging() {
	defer m.handler.DecreaseWait()

	defer func() {
		if err := recover(); err != nil {
			m.handler.NotifyError(err.(error))
		}
	}()

	defer m.logger.Info("Metric logging stopped")
	m.logger.Info("Metric logging started")

	var influxDbConn client.Client
	for {
		conn, err := client.NewUDPClient(client.UDPConfig{Addr: m.host,})
		if err != nil {
			m.logger.Error(err)
		} else {
			influxDbConn = conn
			break
		}
		cont := time.After(1 * time.Second)
		select {
		case <-cont:
			continue
		case <-m.handler.Done():
			return
		}
	}

	var (
		ticker      = time.Tick(time.Second)
		osMetricPtr unsafe.Pointer
		metricData  = make([]processorData, m.processorCount)
		sendData    = make([]processorData, m.processorCount)
	)
	go func() {
		for changedSpeed := range m.fanSpeedConsumer {
			metricData[changedSpeed.Id].FanSpeed = changedSpeed.FanSpeed
		}
	}()

	go func() {
		for changedTempeture := range m.tempetureConsumer {
			metricData[changedTempeture.Id].Tempeture = changedTempeture.Tempeture
		}
	}()

	go func() {
		for sampledOsMetric := range m.osMetricConsumer {
			atomic.StorePointer(&osMetricPtr, unsafe.Pointer(&sampledOsMetric))
		}
	}()

	for {
		select {
		case <-ticker:
			swapped := (*producer.OSMetricInfo)(atomic.SwapPointer(&osMetricPtr, nil))
			copy(sendData, metricData)
			go m.sendPoint(influxDbConn, sendData, swapped)
		case <-m.handler.Done():
			return
		}
	}
}
func (m *influxMetric) sendPoint(
	influxDbConn client.Client,
	processorDatas []processorData,
	osMetric *producer.OSMetricInfo) {
	pointList := make([]*client.Point, 0, 0)

	m.getProcessorPoint(&pointList, processorDatas, time.Now())
	if osMetric != nil {
		m.getOsMetric(&pointList, osMetric)
	}
	if len(pointList) == 0 {
		return
	}

	// Create a new point batch
	batchPoint, err := client.NewBatchPoints(client.BatchPointsConfig{
		Database:  "core",
		Precision: "s",
	})
	if err != nil {
		m.logger.Warn(err)
		return
	}
	batchPoint.AddPoints(pointList)
	if err := influxDbConn.Write(batchPoint); err != nil {
		m.logger.Warn(err)
	}

}

func (m *influxMetric) getOsMetric(points *[]*client.Point, info *producer.OSMetricInfo) {
	for _, applier := range info.Applier() {
		name, tagsSlice, fieldsSlice := applier()
		for i := 0; i < len(tagsSlice); i++ {
			var point *client.Point
			tags, fields := tagsSlice[i], fieldsSlice[i]
			point, err := client.NewPoint(name, tags, fields, info.At)
			if err != nil {
				m.logger.Debugf("os matric name %s err %s", name, err)
				continue
			}
			*points = append(*points, point)
		}
	}
	return
}

func (m *influxMetric) getProcessorPoint(points *[]*client.Point, datas []processorData, at time.Time) () {
	for id, data := range datas {
		// Create a point and add to batch
		tags := map[string]string{"processor": strconv.Itoa(id)}
		fields := map[string]interface{}{
			"tempeture": data.Tempeture,
			"fan":       data.FanSpeed,
		}
		point, err := client.NewPoint("processor", tags, fields, at)
		if err != nil {
			m.logger.Debugf("processor id %d err %s", id, err)
			continue
		}
		*points = append(*points, point)
	}
}
