package core

import (
	"context"
	"github.com/guo-shaoge/tiflash-autoscale/pkg/metric"
	"github.com/guo-shaoge/tiflash-autoscale/pkg/util/log/logger"
	"go.uber.org/zap"
	"sync"
	"time"
)

type MetricMgr struct {
	ctx context.Context

	podMgr    *PodMgr
	configMgr *ConfigMgr
	logger    *zap.Logger

	cpu     metric.PodsCPUMetricWindow
	oom     metric.PodsOOMMetricWindow
	taskCnt metric.PodsTaskCntMetricWindow
	ru      metric.PodsRUMetricWindow

	collector *metric.Collector
}

// NewMetricMgr return new MetricMgr
func NewMetricMgr(ctx context.Context, podMgr *PodMgr, configMgr *ConfigMgr, parentLogger *zap.Logger) *MetricMgr {
	res := &MetricMgr{
		ctx: ctx,

		configMgr: configMgr,
		podMgr:    podMgr,
		logger:    parentLogger.With(zap.String(logger.LogIDMod, "metricMgr")),

		cpu:     make(metric.PodsCPUMetricWindow),
		oom:     make(metric.PodsOOMMetricWindow),
		taskCnt: make(metric.PodsTaskCntMetricWindow),
		ru:      make(metric.PodsRUMetricWindow),
	}

	// "http://as-prometheus.tiflash-autoscale.svc.cluster.local:16292",
	promAddr := *configMgr.GetConfig().GetClusterConfig().PromAddr
	promQueryTimeoutSec := *configMgr.GetConfig().GetClusterConfig().PromQueryTimeoutSec
	var err error
	if res.collector, err = metric.NewCollector(ctx, promAddr, promQueryTimeoutSec, res.logger); err != nil {
		panic(err)
	}
	return res
}

func (m *MetricMgr) startBgWorker(wg *sync.WaitGroup) {
	wg.Add(1)
	go func() {
		m.collectMetricLoop()
	}()
}

func (m *MetricMgr) collectMetricLoop() {
	for {
		m.logger.Info("collect metric iteration start")

		curConfig := m.configMgr.GetConfig()
		metricIntervalSec := *curConfig.GetClusterConfig().MetricIntervalSec
		metricWindowSec := *curConfig.GetClusterConfig().MetricWindowSec
		autoPauseWindowSec := *curConfig.GetClusterConfig().AutoPauseWindowSec

		start := time.Now()
		if err := m.collector.QueryCPU(m.cpu); err != nil {
			m.logger.Error("collect cpu metric failed, skip", zap.Error(err))
		}
		m.updateCPU(start, metricWindowSec)

		if err := m.collector.QueryOOM(m.oom); err != nil {
			m.logger.Error("collect oom metric failed, skip", zap.Error(err))
		}
		m.updateOOM(start, metricWindowSec)

		if err := m.collector.QueryTaskCnt(m.taskCnt); err != nil {
			m.logger.Error("collect task cnt metric failed, skip", zap.Error(err))
		}
		m.updateTaskCnt(start, autoPauseWindowSec)

		if err := m.collector.QueryRU(m.ru); err != nil {
			m.logger.Error("collect ru metric failed, skip", zap.Error(err))
		}
		m.updateRU(start, metricWindowSec)

		m.podMgr.UpdateMetricsByCopy(m.cpu, m.oom, m.taskCnt, m.ru)

		ticker := time.NewTicker(time.Duration(metricIntervalSec) * time.Second)
		m.logger.Info("collect metric iteration done", zap.Any("elapsed", time.Since(start)))
		select {
		case <-ticker.C:
		case <-m.ctx.Done():
			m.logger.Info("got ctx.Done() and exit")
			return
		}
	}
}

// TODO:
// 1. generics
// 2. map waste
func (m *MetricMgr) updateCPU(now time.Time, windowSec uint) {
	newCPU := make(metric.PodsCPUMetricWindow, len(m.cpu))
	for podName, window := range m.cpu {
		newWindow := metric.RemoveToFitsWindowForCPU(now, windowSec, window)
		if len(newWindow) > 0 {
			newCPU[podName] = newWindow
		}
	}
	m.cpu = newCPU
}
func (m *MetricMgr) updateTaskCnt(now time.Time, windowSec uint) {
	newTaskCnt := make(metric.PodsTaskCntMetricWindow, len(m.taskCnt))
	for podName, window := range m.taskCnt {
		newWindow := metric.RemoveToFitsWindowForTaskCnt(now, windowSec, window)
		if len(newWindow) > 0 {
			newTaskCnt[podName] = newWindow
		}
	}
	m.taskCnt = newTaskCnt
}
func (m *MetricMgr) updateOOM(now time.Time, windowSec uint) {
	newOOM := make(metric.PodsOOMMetricWindow, len(m.oom))
	for podName, window := range m.oom {
		newWindow := metric.RemoveToFitsWindowForOOM(now, windowSec, window)
		if len(newWindow) > 0 {
			newOOM[podName] = newWindow
		}
	}
	m.oom = newOOM
}
func (m *MetricMgr) updateRU(now time.Time, windowSec uint) {
	newRU := make(metric.PodsRUMetricWindow, len(m.ru))
	for podName, window := range m.ru {
		newWindow := metric.RemoveToFitsWindowForRU(now, windowSec, window)
		if len(newWindow) > 0 {
			newRU[podName] = newWindow
		}
	}
	m.ru = newRU
}
