package eks

import (
	"context"
	"fmt"

	"github.com/crossplane-contrib/provider-aws/apis/eks/manualv1alpha1"
	"helm.sh/helm/v3/pkg/chart"
	"helm.sh/helm/v3/pkg/chart/loader"
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"

	"github.com/tidbcloud/eks-provider/controllers/network/task"
	"github.com/tidbcloud/infra-provider/pkg/config"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/helm"
	"github.com/tidbcloud/infra-provider/pkg/namer"
)

type ClusterAutoscaler struct {
	*task.BaseTask
	config *clusterAutoscalerConfig
}

type clusterAutoscalerConfig struct {
	helmChartPath  string
	clusterName    string
	nodegroupName  string
	imageConfig    config.ImageConfig
	region         string
	min            int
	max            int
	asgName        string
	eksClusterName string
}

func (c *clusterAutoscalerConfig) GetNamespace() string {
	return metav1.NamespaceSystem
}

func (c *clusterAutoscalerConfig) GetName() string {
	return "cluster-autoscaler"
}

func (c *clusterAutoscalerConfig) ToHelmChart() (result *chart.Chart, err error) {
	result, err = loader.Load(c.helmChartPath)
	if err != nil {
		return nil, fmt.Errorf("failed to load helm chart %q: %w", c.helmChartPath, err)
	}
	return
}

func (c *clusterAutoscalerConfig) ToHelmValues() map[string]interface{} {
	return map[string]interface{}{
		"clusterName":   c.clusterName,
		"nodeGroupName": c.nodegroupName,
		"image": map[string]interface{}{
			"repository": c.imageConfig.Repo,
			"tag":        c.imageConfig.Tag,
		},
		"region":         c.region,
		"asgName":        c.asgName,
		"eksClusterName": c.eksClusterName,
		"min":            c.min,
		"max":            c.max,
	}
}

func (c *clusterAutoscalerConfig) GetCRDs() ([]*apiextensionsv1.CustomResourceDefinition, error) {
	return nil, nil
}

func NewClusterAutoscaler(ctx *task.Context) *ClusterAutoscaler {
	t := &task.BaseTask{Context: ctx}

	return &ClusterAutoscaler{
		BaseTask: t,
		config: &clusterAutoscalerConfig{
			helmChartPath: ctx.Config.Network.ClusterAutoscaler.ChartPath,
			clusterName:   namer.LegacyBuildClusterNameByNetwork(t.Network),
			nodegroupName: namer.UniqueIdentifier(t.Network.ObjectMeta, namer.WithPrefix(consts.TiDBCloudPrefix)),
			imageConfig:   ctx.Config.Network.ClusterAutoscaler.Image,
			region:        ctx.Network.Spec.Region,
		},
	}
}

func (t *ClusterAutoscaler) Name() string {
	return "ClusterAutoscaler"
}

func (t *ClusterAutoscaler) Create(ctx context.Context) deploy.Result {
	_, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}

	// to support the cluster autoscaler flags
	// https://github.com/tidbcloud/eks-bootstrap/blob/5e422f2cbccea5976708defb845cea3c38c1c94f/helm/aws-cluster-auto-scaler/templates/cluster-auto-discover.yaml#L180-L193
	//
	// - ./cluster-autoscaler
	// - --v=4
	// ...
	// - --scale-down-utilization-threshold=0.000001
	// {{-  if .Values.asgName }}
	// - --nodes={{ .Values.min }}:{{ .Values.max }}:{{ .Values.asgName }}{{- end}}
	// {{-  if .Values.eksClusterName }}
	// - --skip-nodes-with-system-pods=false
	// - --balance-similar-node-groups
	// - --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,kubernetes.io/cluster/{{ .Values.eksClusterName }}{{- end}}
	//
	var g manualv1alpha1.NodeGroup
	key := types.NamespacedName{Name: namer.UniqueIdentifier(t.Network.ObjectMeta, namer.WithPrefix(consts.TiDBCloudPrefix))}
	err = t.Local.Get(ctx, key, &g)
	if err != nil {
		return deploy.Error(err)
	}
	if len(g.Status.AtProvider.Resources.AutoScalingGroups) == 0 {
		return deploy.Error(fmt.Errorf("no autoscaling group found"))
	}
	// if it's a shared vpc network, keep the asgName param empty, let the cluster-autoscaler control all the nodegroups
	asgName := g.Status.AtProvider.Resources.AutoScalingGroups[0].Name
	t.config.asgName = asgName
	t.config.min = int(*g.Spec.ForProvider.ScalingConfig.MinSize)
	t.config.max = int(*g.Spec.ForProvider.ScalingConfig.MaxSize)

	return deploy.ErrorOrDone(helm.Apply(ctx, t.Remote, t.config))
}
