package eks

import (
	"context"
	"fmt"

	"github.com/crossplane-contrib/provider-aws/apis/eks/manualv1alpha1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	"github.com/tidbcloud/eks-provider/controllers/cluster/task"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/namer"
)

const (
	placeholderNGNamePrefix = "placeholder-ng"
)

// PlaceholderNodegroup is a temporary workaround
// A workaround to avoid tidb cluster node role being removed from aws-auth configmap
// When br, lightning managed nodegroup deleted, if their role is not used by other managed nodegroups
// the role will be removed from aws-auth automatically by EKS
// Ref: https://docs.aws.amazon.com/eks/latest/userguide/delete-managed-node-group.html
type PlaceholderNodegroup struct {
	*task.BaseTask
}

func NewPlaceholderNodegroup(ctx *task.Context) *PlaceholderNodegroup {
	return &PlaceholderNodegroup{BaseTask: &task.BaseTask{Context: ctx}}
}

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

func (t *PlaceholderNodegroup) Create(ctx context.Context) deploy.Result {
	if t.IsLegacy() {
		// Legacy cluster does not need placeholder nodegroup
		return deploy.Done()
	}

	if t.Config.DryRun.Enabled {
		return deploy.Done()
	}

	zones := t.Zones()
	ng := &manualv1alpha1.NodeGroup{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.getNodegroupName(),
		},
		Spec: manualv1alpha1.NodeGroupSpec{
			ForProvider: manualv1alpha1.NodeGroupParameters{
				ClusterName: t.EnvProvider.GetClusterName(),
				Region:      t.EnvProvider.GetRegion(),
				NodeRole:    t.GetAWSNodeRoleARN(),
				// set taints and labels to avoid nodegroup be scheduled
				Taints: []manualv1alpha1.Taint{
					{
						Effect: "NO_SCHEDULE",
						Key:    ptr.To("placeholder-nodegroup"),
						Value:  ptr.To("true"),
					},
				},
				Labels: map[string]string{
					"placeholder-nodegroup": "true",
				},
				ScalingConfig: &manualv1alpha1.NodeGroupScalingConfig{
					DesiredSize: ptr.To[int32](0),
					MinSize:     ptr.To[int32](0),
					MaxSize:     ptr.To[int32](1),
				},
				Subnets: t.EnvProvider.AWS().GetNodeSubnets(zones...),
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: t.GetAWSProviderConfigName()},
			},
		},
	}

	// Sync version before apply to ensure version and releaseVersion are correct
	// Note: When K8s cluster is upgraded, placeholder nodegroup version may become inconsistent
	// with cluster version, so we need to sync it here before applying
	_, err := controllerutil.CreateOrUpdate(ctx, t.Local, ng, func() error {
		// Ensure version fields are set correctly every time
		if t.CommonK8sClusterStatus != nil && t.CommonK8sClusterStatus.NodeVersion != "" {
			ng.Spec.ForProvider.Version = ptr.To(t.CommonK8sClusterStatus.NodeVersion)
			ng.Spec.ForProvider.ReleaseVersion = nil
		}
		return nil
	})
	if err != nil {
		return deploy.Error(fmt.Errorf("failed to create or update placeholder nodegroup: %w", err))
	}
	return deploy.Done()
}

func (t *PlaceholderNodegroup) Delete(ctx context.Context) deploy.Result {
	if t.IsLegacy() {
		// Legacy cluster does not need placeholder nodegroup
		return deploy.Done()
	}
	ng := &manualv1alpha1.NodeGroup{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.getNodegroupName(),
		},
	}
	if err := client.IgnoreNotFound(t.Local.Delete(ctx, ng)); err != nil {
		return deploy.Panic(err)
	}
	return deploy.Done()
}

func (t *PlaceholderNodegroup) getNodegroupName() string {
	return namer.NewName(t.Cluster.ClusterScopeName(), placeholderNGNamePrefix, "", -1)
}
