package task

import (
	"context"
	"time"

	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	"github.com/tidbcloud/eks-provider/controllers/utils"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/nodepoolgc"
)

const (
	maxNodeCountPerZone   = 50
	defaultRootDiskSizeGB = 50
	defaultInstanceType   = "m5.large"
)

type nodePool struct {
	*baseTask
}

func NewNodePool(ctx *Context) deploy.Task {
	return &nodePool{
		baseTask: &baseTask{Context: ctx},
	}
}

func (t *nodePool) Name() string {
	return "NodePool"
}

func (t *nodePool) Create(ctx context.Context) deploy.Result {
	var instanceType string
	if t.compactBackup.Spec.InstanceType == nil || *t.compactBackup.Spec.InstanceType == "" {
		instanceType = defaultInstanceType
		t.logger.Info("spec.instanceType not provided, using default", "instanceType", instanceType)
	} else {
		instanceType = *t.compactBackup.Spec.InstanceType
	}
	k8sVersion := t.cloud.GetKubernetesVersion()
	k8sName := t.cloud.GetKubernetesName()
	zones := t.cloud.GetNodeZones()
	// cloud resource tags, used for cost and billing analysis
	tags := map[string]string{
		consts.TagKeyTenant:      t.getTenantID(),
		consts.TagKeyProject:     t.getProjectID(),
		consts.TagKeyUsedBy:      consts.UsedByCustomerTiDB,
		consts.TagKeyServiceType: consts.ServiceTypeDedicated,
		consts.TagKeyEnvironment: t.env,
		consts.TagKeyComponent:   string(cloudv1.ComponentCompactBackup),
	}
	// k8s resource labels
	labels := t.cloud.GetNodepoolLabels()

	np := &NodePool{
		Name:           t.getNodePoolName(instanceType, k8sName, k8sVersion),
		InstanceType:   instanceType,
		RootDiskSizeGB: 50,
		Region:         t.compactBackup.GetRegion(),
		Zones:          zones,
		KubeClusterID:  t.cloud.GetKubernetesID(),
		NodeSubnets:    t.cloud.GetNodeSubnets(),
		PodSubnets:     t.cloud.GetPodSubnets(),
		Labels:         labels,
		Tags:           tags,
		Taints:         utils.Taint(cloudv1.ComponentCompactBackup),
		Identity:       t.cloud.GetNodeIdentity(),
	}
	pc := t.cloud.GetProviderConfigName()
	// build the nodepool CR, AWS will create both nodegroup and launch template
	// GCP and Azure will create nodepool CR
	nodepoolObjs, err := t.cloud.BuildNodePool(ctx, pc, np)
	if err != nil {
		return deploy.Error(err)
	}
	now := time.Now()
	for _, obj := range nodepoolObjs {
		t.logger.Info("Ensure compact backup nodepool", "Name", obj.GetName())
		// avoid using k8s.Apply due to we use unstructured for unsupported field in upbound sdk
		_, err = controllerutil.CreateOrUpdate(ctx, t.local, obj, func() error {
			if t.compactBackup.DeletionTimestamp.IsZero() && !t.compactBackup.IsFinished() {
				nodepoolgc.RefreshUsage(obj, now)
			}
			return nil
		})
		if err != nil {
			return deploy.Error(err)
		}
	}

	return deploy.Done()
}

// nodepool is shared by all compact backup job in the same dataplane k8s cluster
// so we should not delete them when compact backup is deleted
// TODO(aws): on AWS, nodepools are now per-cluster (different node roles per cluster).
// These nodepools should be cleaned up when the TiDB cluster is deleted.
// The same issue exists in backup/task and restore/task.
func (t *nodePool) Delete(ctx context.Context) deploy.Result {
	return deploy.Done()
}
