package autoops

import (
	"context"

	"github.com/crossplane-contrib/provider-aws/apis/eks/manualv1alpha1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	xpv1meta "github.com/crossplane/crossplane-runtime/pkg/meta"
	"github.com/go-logr/logr"
	corev1 "k8s.io/api/core/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/infra-provider/pkg/k8s"
)

const (
	IssueCodeIamThrottling = "IamThrottling"

	// label with the nodegroup name applied to nodes
	// ref: https://docs.aws.amazon.com/eks/latest/userguide/managed-node-update-behavior.html#managed-node-update-scale-up
	eksNodeGroupLabelKey = "eks.amazonaws.com/nodegroup"
	// ref: https://github.com/crossplane/crossplane-runtime/blob/6966b5a68a7c47fdbe2e40535753af9f314dea06/pkg/reconciler/managed/reconciler.go#L53
	externalCreatePending = "cannot determine creation result - remove the " + xpv1meta.AnnotationKeyExternalCreatePending + " annotation if it is safe to proceed"
)

func DeleteNodeGroupsIfIAMThrottled(ctx context.Context, logger logr.Logger, cli client.Client, nodeGroup *manualv1alpha1.NodeGroup) error {
	iamThrotted := false
	for _, issue := range nodeGroup.Status.AtProvider.Health.Issues {
		if issue.Code == IssueCodeIamThrottling {
			iamThrotted = true
			break
		}
	}
	if iamThrotted {
		logger.Info("create node group iam throtted, delete it", "nodegroup", nodeGroup.Name)
		err := cli.Delete(ctx, nodeGroup)
		if err != nil {
			return err
		}
	}
	return nil
}

func RemoveNodeGroupPendingAnnotationIfReady(ctx context.Context, logger logr.Logger, localCli, remoteCli client.Client, nodeGroup *manualv1alpha1.NodeGroup) error {
	for _, condition := range nodeGroup.Status.Conditions {
		// check the nodegroup is stuck in the "external-create-pending" state
		if condition.Type == xpv1.TypeSynced && condition.Reason == xpv1.ReasonReconcileError &&
			condition.Status == corev1.ConditionFalse && condition.Message == externalCreatePending {
			// check all nodes in the group are ready
			ready, err := allNodesReadyForGroup(ctx, remoteCli, nodeGroup)
			if err != nil {
				return err
			} else if ready {
				logger.Info("node group is ready, remove annotation "+xpv1meta.AnnotationKeyExternalCreatePending, "nodegroup", nodeGroup.Name)
				delete(nodeGroup.Annotations, xpv1meta.AnnotationKeyExternalCreatePending)
				err = localCli.Update(ctx, nodeGroup)
				if err != nil {
					return err
				}
			}
			break
		}
	}

	return nil
}

func allNodesReadyForGroup(ctx context.Context, cli client.Client, nodeGroup *manualv1alpha1.NodeGroup) (bool, error) {
	if nodeGroup.Spec.ForProvider.ScalingConfig == nil || nodeGroup.Spec.ForProvider.ScalingConfig.MinSize == nil {
		return false, nil // this should not happened
	}

	nodeList := &corev1.NodeList{}
	ops := k8s.ListOpts[*corev1.Node]{
		ObjectList: nodeList,
		Unpack:     func() []*corev1.Node { return k8s.MapPtr(nodeList.Items) },
	}
	nodes, err := ops.ListFilterBy(ctx, cli, func(n *corev1.Node) bool {
		for _, condition := range n.Status.Conditions {
			if condition.Type == corev1.NodeReady && condition.Status == corev1.ConditionTrue {
				return true
			}
		}
		return false
	}, client.MatchingLabels{eksNodeGroupLabelKey: nodeGroup.Name})
	if err != nil {
		return false, err
	}

	return len(nodes) >= int(*nodeGroup.Spec.ForProvider.ScalingConfig.MinSize), nil
}
