package tidbupgrader

import (
	"context"
	"time"

	"github.com/go-logr/logr"
	"github.com/tidbcloud/aws-shared-provider/pkg/metrics"
	"github.com/tidbcloud/aws-shared-provider/pkg/tidbmanager"
	"github.com/tidbcloud/aws-shared-provider/pkg/tidbpool"
	"github.com/tidbcloud/aws-shared-provider/pkg/types"
	"github.com/tidbcloud/aws-shared-provider/pkg/utils"
	"github.com/tidbcloud/aws-shared-provider/pkg/utils/tidb"
	corev1 "k8s.io/api/core/v1"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
	trivialPoolCheckInterval  = 8 * time.Second  //nolint: gomnd // by design
	criticalPoolCheckInterval = 30 * time.Second //nolint: gomnd // by design
)

type TidbUpgrader struct {
	logr.Logger

	tidbPoolNamespace string

	ctx    context.Context
	client client.Client
	pool   tidbpool.Pool

	record            upgradeRecord
	tidbPool          *tidbpool.TidbPool
	freeCacheSize     int
	oldPodInFreeCache []string

	tManager       tidbmanager.TidbManager
	tidbController tidb.Controller
	lastSuccess    time.Time
}

func New(client client.Client, tManager tidbmanager.TidbManager, tidbController tidb.Controller) *TidbUpgrader {
	pool := tManager.GetTidbPool()
	return &TidbUpgrader{
		Logger: ctrl.Log.WithName("tidbupgrader").WithName(pool.Namespace()),

		tidbPoolNamespace: pool.Namespace(),

		client: client,
		pool:   pool,

		tManager:       tManager,
		tidbController: tidbController,
		lastSuccess:    time.Now(),
	}
}

func (tu *TidbUpgrader) Start(ctx context.Context) error {
	tu.ctx = ctx
	// we should sleep for a few seconds, in order to stagger with Scaler
	utils.SleepWithContext(tu.ctx, 2*time.Second) //nolint:mnd // not need
	tu.Info("start tidbupgrader")

	checkInterval := trivialPoolCheckInterval
	if tu.isCriticalPool() {
		checkInterval = criticalPoolCheckInterval
	}
	checkTicker := time.NewTicker(checkInterval)
	defer checkTicker.Stop()

	for {
		select {
		case <-tu.ctx.Done():
			err := tu.ctx.Err()
			tu.Error(err, "stop tidbupgrader")
			return err
		case <-checkTicker.C:
			start := time.Now()
			var err error
			tu.V(1).Info("start tidbupgrader check cycle...")

			if !tu.GetPool() {
				tu.Info("GetPool false, skip")
				continue
			}

			tidbPool := tu.tidbPool
			if tu.record.revision != tidbPool.Revision {
				tu.initRecord()
				tu.Info("new upgradeRecord", "revision", tidbPool.Revision)
			}
			tu.V(2).Info("tidb pool and upgrade record", "revision", tidbPool.Revision,
				"minSize", tidbPool.MinSize, "maxSize", tidbPool.MaxSize, "record", tu.record)

			tu.handleOldAbnormal()
			err = tu.handleFreeCache()
			if err != nil {
				metrics.UpgraderErrorDurationGauge.WithLabelValues(tu.tidbPoolNamespace).Set(float64(time.Since(tu.lastSuccess).Seconds()))
				tu.V(1).Info("finish tidbupgrader cycle", "time", time.Since(start), "error", err)
				continue
			}
			if !tu.isCriticalPool() {
				err = tu.processRegularPool()
				if err != nil {
					metrics.UpgraderErrorDurationGauge.WithLabelValues(tu.tidbPoolNamespace).Set(float64(time.Since(tu.lastSuccess).Seconds()))
					tu.V(1).Info("finish tidbupgrader cycle", "time", time.Since(start), "error", err)
					continue
				}
			}

			metrics.UpgraderErrorDurationGauge.WithLabelValues(tu.tidbPoolNamespace).Set(0)
			tu.lastSuccess = time.Now()
			tu.V(1).Info("finish tidbupgrader cycle", "time", time.Since(start), "error", err)
		}
	}
}

func (tu *TidbUpgrader) isCriticalPool() bool {
	serverlessPoolConfigCm := &corev1.ConfigMap{}
	if err := tu.client.Get(tu.ctx,
		client.ObjectKey{
			Name:      types.ServerlessPoolConfig,
			Namespace: tu.tidbPoolNamespace,
		}, serverlessPoolConfigCm); err != nil {
		tu.Error(err, "get cm serverlessPoolConfig")
		return false
	}
	return serverlessPoolConfigCm.Data[types.TidbCriticalKey] == "true"
}

func (tu *TidbUpgrader) GetPool() bool {
	tu.tidbPool = tu.pool.GetPool(tu.ctx)
	return tu.tidbPool != nil
}
