package cluster

import (
	"context"
	"fmt"
	"time"

	"github.com/crossplane-contrib/provider-aws/apis/autoscaling/v1beta1"
	ec2v1alpha1 "github.com/crossplane-contrib/provider-aws/apis/ec2/v1alpha1"
	"github.com/mitchellh/mapstructure"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/log"

	"github.com/tidbcloud/eks-provider/controllers/workflow/action"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/util"
)

var _ action.Action = &deleteTiDBClusterNodes{}

// deleteTiDBClusterNodes deletes all nodes of a tidb cluster.
type deleteTiDBClusterNodes struct {
	cli client.Client
}

type deleteTiDBClusterNodesInputs struct {
	ClusterName string `mapstructure:"cluster" json:"cluster" validate:"required"`
	Namespace   string `mapstructure:"namespace" json:"namespace" validate:"required"`
}

func NewDeleteTiDBClusterNodes(cli client.Client) action.Action {
	return &deleteTiDBClusterNodes{cli: cli}
}

func (a *deleteTiDBClusterNodes) Name() cloudv1.WorkflowActionName {
	return consts.WfActionDeleteTiDBClusterNodesAction
}

func (a *deleteTiDBClusterNodes) PreCheck(ctx context.Context, wf *cloudv1.Workflow, step cloudv1.WorkflowStep) error {
	inputs := &deleteTiDBClusterNodesInputs{}
	if err := mapstructure.Decode(step.Inputs.Object, inputs); err != nil {
		return fmt.Errorf("decode inputs: %w", err)
	}
	if err := validate.Struct(inputs); err != nil {
		return fmt.Errorf("validate inputs: %w", err)
	}
	return nil
}

// waitForASGsDeleted waits for all autoscaling groups with the given cluster label to be deleted
func (a *deleteTiDBClusterNodes) waitForASGsDeleted(ctx context.Context, clusterName string, timeout time.Duration) error {
	logger := log.FromContext(ctx).WithValues("cluster", clusterName)
	pollInterval := 10 * time.Second

	return wait.PollUntilContextTimeout(ctx, pollInterval, timeout, true, func(ctx context.Context) (bool, error) {
		var asgList v1beta1.AutoScalingGroupList
		if err := a.cli.List(ctx, &asgList, client.MatchingLabels{"cluster": clusterName}); err != nil {
			logger.Error(err, "failed to list autoscaling groups")
			return false, err
		}

		if len(asgList.Items) == 0 {
			logger.Info("all autoscaling groups have been deleted")
			return true, nil
		}

		remaining := len(asgList.Items)
		logger.Info("waiting for autoscaling groups to be deleted", "remaining", remaining)
		return false, nil
	})
}

func (a *deleteTiDBClusterNodes) Handle(ctx context.Context, wf *cloudv1.Workflow, step cloudv1.WorkflowStep) error {
	inputs := &deleteTiDBClusterNodesInputs{}
	_ = mapstructure.Decode(step.Inputs.Object, inputs)
	logger := log.FromContext(ctx).WithValues("action", a.Name(), "cluster", inputs.ClusterName, "namespace", inputs.Namespace)

	cluster, err := getCluster(ctx, a.cli, client.ObjectKey{Namespace: inputs.Namespace, Name: inputs.ClusterName})
	if err != nil {
		return err
	}

	switch cluster.Spec.Kubernetes.Provider {
	case consts.ProviderTypeEKS:
		if util.GetAnnotation(cluster, consts.UseSelfManagedNodegroupAnnotation) != consts.AnnoValTrue {
			return fmt.Errorf("not support since the cluster %s/%s uses managed nodegroups", inputs.Namespace, inputs.ClusterName)
		}

		var asgList v1beta1.AutoScalingGroupList
		if err := a.cli.List(ctx, &asgList, client.MatchingLabels{"cluster": inputs.ClusterName}); err != nil {
			return fmt.Errorf("list autoscaling groups failed: %w", err)
		}

		if len(asgList.Items) == 0 {
			logger.Info("no autoscaling groups found for the cluster")
			return nil
		}

		logger.Info("deleting autoscaling groups", "count", len(asgList.Items))
		for i := range asgList.Items {
			asg := &asgList.Items[i]
			logger.Info("deleting autoscaling group", "asg", asg.Name)
			if err := a.cli.Delete(ctx, asg); client.IgnoreNotFound(err) != nil {
				logger.Error(err, "delete autoscaling group failed", "asg", asg.Name)
				return err
			}

			// delete the corresponding launch template
			// Can NOT use `client.DeleteAllOf` here, because some launch templates are owned by CDC or DM.
			if asg.Spec.ForProvider.LaunchTemplate != nil && asg.Spec.ForProvider.LaunchTemplate.LaunchTemplateName != nil {
				lt := &ec2v1alpha1.LaunchTemplate{
					ObjectMeta: metav1.ObjectMeta{
						Name: *asg.Spec.ForProvider.LaunchTemplate.LaunchTemplateName,
					},
				}
				logger.Info("deleting launch template", "launchtemplate", lt.Name)
				if err := a.cli.Delete(ctx, lt); client.IgnoreNotFound(err) != nil {
					logger.Error(err, "delete launch template failed", "launchtemplate", lt.Name)
					return err
				}
			}
		}

		// Wait for all autoscaling groups to be deleted
		logger.Info("waiting for all autoscaling groups to be deleted")
		timeout := 20 * time.Minute
		if err := a.waitForASGsDeleted(ctx, inputs.ClusterName, timeout); err != nil {
			logger.Error(err, "failed to wait for autoscaling groups deletion")
			return fmt.Errorf("wait for autoscaling groups deletion: %w", err)
		}

		return nil

	default:
		return fmt.Errorf("not support for %s", cluster.Spec.Kubernetes.Provider)
	}
}
