package zoneredundantevicter

import (
	"context"

	"github.com/tidbcloud/aws-shared-provider/pkg/metrics"
	"github.com/tidbcloud/aws-shared-provider/pkg/utils/kubeapi"
	"github.com/tidbcloud/aws-shared-provider/pkg/utils/once"
	corev1 "k8s.io/api/core/v1"
	policyv1 "k8s.io/api/policy/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	apitypes "k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/log"
)

//nolint:nakedret // not need
func (r *Reconciler) evictComponent(
	ctx context.Context,
	component string,
	evictNodes []*corev1.Node,
	evictPodLabels map[string]string,
) (wait bool, retry bool, err error) {
	logger := log.FromContext(ctx, "component", component)

	defer func() {
		if err != nil {
			logger.Error(err, "failed to evictComponent", "wait", wait, "retry", retry)
			if wait || retry {
				err = nil
			}
		}
	}()

	setEvicterTaintOnce := once.Oncev2{}
	controlledBy := make(map[apitypes.UID]bool)
	for _, node := range evictNodes {
		podList := &corev1.PodList{}
		if err = r.Client.List(ctx, podList,
			client.MatchingFields{"spec.nodeName": node.Name},
			client.MatchingLabels(evictPodLabels),
		); err != nil {
			return
		}
		evictPods := make([]*corev1.Pod, 0, len(podList.Items))
		for i := range podList.Items {
			pod := &podList.Items[i]
			if ignoredPod(pod) {
				continue
			}
			if ownerRef := metav1.GetControllerOf(pod); ownerRef != nil && ownerRef.Kind == "DaemonSet" {
				continue
			}
			// init controlledBy for terminating pods
			if pod.DeletionTimestamp != nil {
				if ownerRef := metav1.GetControllerOf(pod); isControlledByNonJob(ownerRef) {
					controlledBy[ownerRef.UID] = true
				}
			}
			evictPods = append(evictPods, pod)
		}

		if len(evictPods) > 0 {
			if err = setEvicterTaintOnce.Do(func() error {
				for _, node := range evictNodes {
					if updated, err := r.trySetEvicterTaint(ctx, node); err != nil {
						return err
					} else if updated {
						logger.Info("updated node taint", "node", node.Name)
					}
				}
				return nil
			}); err != nil {
				return
			}
		}

		var deletedPods, evictedPods []string
		isNodeUnreachable := kubeapi.IsNodeUnreachable(node)
		for _, pod := range evictPods {
			if err = r.evictPodPVC(ctx, pod); err != nil {
				return
			}

			if isNodeUnreachable {
				if err = client.IgnoreNotFound(r.Client.Delete(ctx, pod, &client.DeleteOptions{
					GracePeriodSeconds: ptr.To[int64](0),
				})); err != nil {
					return
				}
				deletedPods = append(deletedPods, podName(pod))
				logger.Info("deleted pod", "pod", podName(pod), "node", node.Name)
				metrics.ZREvictPodCounter.WithLabelValues(pod.Namespace, node.Name, component, "delete").Inc()
			} else {
				if pod.DeletionTimestamp != nil {
					continue
				}
				ownerRef := metav1.GetControllerOf(pod)
				if isControlledByNonJob(ownerRef) && controlledBy[ownerRef.UID] {
					logger.V(1).Info("skip evict pod", "pod", podName(pod), "node", node.Name, "ownerRef", ownerRef)
					wait = true
					continue
				}

				if err = client.IgnoreNotFound(r.Client.SubResource("eviction").Create(
					ctx, pod, &policyv1.Eviction{}),
				); err != nil {
					if apierrors.IsTooManyRequests(err) {
						retry = true
						logger.Info(err.Error(), "pod", podName(pod), "node", node.Name, "ownerRef", ownerRef)
						metrics.ZREvictPodRetryCounter.WithLabelValues(pod.Namespace, node.Name, component).Inc()
						err = nil
						continue
					} else {
						logger.Error(err, "failed to evict", "pod", podName(pod), "node", node.Name, "ownerRef", ownerRef)
						return
					}
				}
				evictedPods = append(evictedPods, podName(pod))
				logger.Info("evicted pod", "pod", podName(pod), "node", node.Name, "ownerRef", ownerRef)
				metrics.ZREvictPodCounter.WithLabelValues(pod.Namespace, node.Name, component, "evict").Inc()

				if isControlledByNonJob(ownerRef) {
					controlledBy[ownerRef.UID] = true
					metrics.ZREvictPodWaitCounter.WithLabelValues(pod.Namespace, node.Name, component).Inc()
				}
			}
		}
		if len(deletedPods) > 0 {
			r.Recorder.Event(node, corev1.EventTypeNormal, "DeleteZoneRedundantPods", "delete all pods")
		}
		if len(evictedPods) > 0 {
			r.Recorder.Eventf(node, corev1.EventTypeNormal, "EvictZoneRedundantPods", "evict pods: %v", evictedPods)
		}
	}
	return
}
