package tidbpool

import (
	"context"
	"strconv"

	"github.com/go-logr/logr"
	"github.com/tidbcloud/aws-shared-provider/cmd/manager/app/config"
	"github.com/tidbcloud/aws-shared-provider/pkg/scheme"
	"github.com/tidbcloud/aws-shared-provider/pkg/types"
	"github.com/tidbcloud/aws-shared-provider/pkg/utils"
	batchv1 "k8s.io/api/batch/v1"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

const (
	fakeTidbJobName = "fake-tidb-server-job"
)

type Pool interface {
	GetPool(ctx context.Context) *TidbPool
	GetAbnormalPodList(ctx context.Context) ([]corev1.Pod, error)
	Namespace() string
}

func New(cfg *config.Config, tidbPoolNamespace string, client client.Client) Pool {
	return &tidbPool{
		Logger: ctrl.Log.WithName("tidbpool").WithName(tidbPoolNamespace),

		poolNamespace: tidbPoolNamespace,

		client: client,

		codecs: serializer.NewCodecFactory(scheme.Scheme),

		cfg: cfg,
	}
}

type tidbPool struct {
	logr.Logger

	poolNamespace string

	client client.Client

	codecs serializer.CodecFactory

	cfg *config.Config
}

type TidbPool struct {
	TidbPod    *corev1.Pod
	NodeGroups []string
	Revision   string
	MinSize    int
	MaxSize    int
}

func (tp *tidbPool) Namespace() string {
	return tp.poolNamespace
}

func (tp *tidbPool) GetPool(ctx context.Context) *TidbPool {
	serverlessPoolConfigCm := &corev1.ConfigMap{}
	if err := tp.client.Get(ctx,
		client.ObjectKey{
			Name:      types.ServerlessPoolConfig,
			Namespace: tp.poolNamespace,
		}, serverlessPoolConfigCm); err != nil {
		tp.Error(err, "get cm serverlessPoolConfig")
		return nil
	}
	if serverlessPoolConfigCm.Data[types.TidbImageKey] == "" {
		tp.Info("tidbImageKey is empty")
		return nil
	}
	minSize, err := strconv.Atoi(serverlessPoolConfigCm.Data[types.MinPoolReplicasKey])
	if err != nil {
		tp.Error(err, "Atoi", "MinSize", serverlessPoolConfigCm.Data[types.MinPoolReplicasKey])
		return nil
	}
	maxSize, err := strconv.Atoi(serverlessPoolConfigCm.Data[types.MaxPoolReplicasKey])
	if err != nil {
		tp.Error(err, "Atoi", "MaxSize", serverlessPoolConfigCm.Data[types.MaxPoolReplicasKey])
		return nil
	}
	if minSize < 0 {
		tp.Error(nil, "MinSize can't be less than 0", "MinSize", minSize)
		return nil
	}
	if minSize > maxSize {
		tp.Error(nil, "MinSize can't be greater than MaxSize", "MinSize", minSize, "MaxSize", maxSize)
		return nil
	}

	tidbPod, nodeGroups, err := tp.decodeTidbServerPod(ctx, serverlessPoolConfigCm)
	if err != nil {
		tp.Error(err, "decodeTidbServerPod")
		return nil
	}

	revision, err := tp.genRevision(ctx, tidbPod)
	if err != nil {
		tp.Error(err, "genRevision")
		return nil
	}

	tidbPod.GenerateName = types.TidbServerNamePrefix + revision + "-"
	tidbPod.Labels = utils.UpdateMap(tidbPod.Labels, types.TidbServerRevision, revision)
	if err := tp.setControllerReference(ctx, tidbPod); err != nil {
		tp.Error(err, "setControllerReference")
		return nil
	}
	controllerutil.AddFinalizer(tidbPod, types.TidbPoolFinalizer)

	return &TidbPool{
		TidbPod:    tidbPod,
		NodeGroups: nodeGroups,
		Revision:   revision,
		MinSize:    minSize,
		MaxSize:    maxSize,
	}
}

// setControllerReference for cluster autoscaling scale down node
func (tp *tidbPool) setControllerReference(ctx context.Context, tidbPod *corev1.Pod) error {
	fakeTidbJob := &batchv1.Job{}
	if err := tp.client.Get(ctx,
		client.ObjectKey{
			Name:      fakeTidbJobName,
			Namespace: tp.poolNamespace,
		}, fakeTidbJob); err != nil {
		if apierrors.IsNotFound(err) {
			tp.Error(err, "skip set setControllerReference, cluster autoscaler can't scale down!")
			return nil
		}
		return err
	}

	return controllerutil.SetControllerReference(fakeTidbJob, tidbPod, scheme.Scheme)
}

func (tp *tidbPool) GetAbnormalPodList(ctx context.Context) ([]corev1.Pod, error) {
	var podList corev1.PodList
	if err := tp.client.List(ctx, &podList, client.MatchingFields{
		"tidbpool.abnormal." + tp.poolNamespace: "abnormal"}); err != nil {
		return nil, err
	}
	return podList.Items, nil
}
