package jumppod

import (
	"context"
	"errors"
	"fmt"
	"slices"

	"github.com/go-logr/logr"
	corev1 "k8s.io/api/core/v1"
	rbacv1 "k8s.io/api/rbac/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/selection"
	"sigs.k8s.io/controller-runtime/pkg/client"
	ctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
	"tailscale.com/client/tailscale/v2"

	jpent "github.com/tidbcloud/devops/entity/jump"
	"github.com/tidbcloud/devops/pkg/infra/clients/coresvccli"
	"github.com/tidbcloud/devops/pkg/infra/clients/dedicatedcli"
	"github.com/tidbcloud/devops/pkg/infra/clients/mgmthttpcli"
	"github.com/tidbcloud/devops/pkg/infra/config"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	"github.com/tidbcloud/devops/pkg/infra/secretsource"
	k8sutil "github.com/tidbcloud/devops/pkg/infra/utils/k8s"
	"github.com/tidbcloud/devops/pkg/service/jump/jumppod/jumper"
	"github.com/tidbcloud/devops/pkg/service/jump/jumppod/types"
)

const (
	accessLeastRoleName        = "jump-server-least-role"
	accessLeastRoleBindingName = "jump-server-least-role-binding"
	jumpPodPeerServiceName     = "jump-pod-peer"
	jumpPodServiceAccountName  = "jump-pod" // NOTE: ns and name must match the AWS Role Policy which is created in CD.
	jumpPodContainerName       = "jump-pod"
	jumpPodAgentContainerName  = "jump-pod-agent"
	jumpPodSharedPVNamePrefix  = "jump-pod-shared-pv"
	jumpPodSharedPVCNamePrefix = "jump-pod-shared-pvc"
)

type ManagerParam struct {
	Conf         config.JumpServiceConfig
	Logger       logr.Logger
	CoreSvc      *coresvccli.CoreSvcCli
	MgmtHTTP     mgmthttpcli.MgmtHTTPClient
	InfraClient  dedicatedcli.InfraAPIClient
	SecretSource secretsource.SecretSource
	JumpCluster  *k8sutil.UnionClient // The cluster where the jump pod is running.
	Tailscale    *tailscale.Client
}

type manager struct {
	conf            config.JumpServiceConfig
	namespace       string
	logger          logr.Logger
	cluster         *k8sutil.UnionClient
	jumper          *jumper.Router
	providerAdapter ProviderAdapter

	jumpPodSharedPV  *corev1.PersistentVolume
	jumpPodSharedPVC *corev1.PersistentVolumeClaim
}

func NewManager(p ManagerParam) (*manager, error) {
	var err error
	m := &manager{
		conf:      p.Conf,
		namespace: p.Conf.JumpPod.JumpPodNamespace,
		logger:    p.Logger.WithName("JumpPodManager"),
		cluster:   p.JumpCluster,
	}

	providerAdapter, err := NewProviderAdapter(p.Conf.JumpPod)
	if err != nil {
		return nil, fmt.Errorf("failed to create provider adapter: %w", err)
	}
	m.providerAdapter = providerAdapter

	registy, err := jumper.NewDestinationRegistryFromFile(
		p.Conf.DestinationRegistryFilepath,
		p.SecretSource,
	)
	if err != nil {
		return nil, fmt.Errorf("failed to create destination registry: %w", err)
	}

	k8sJumperFactory, err := jumper.NewK8sJumperFactory(p.Conf.JumpRoleDir)
	if err != nil {
		return nil, fmt.Errorf("failed to create k8s jumper factory: %w", err)
	}

	m.jumper, err = jumper.DefaultRouter(jumper.JumperDependency{
		Logger:              m.logger,
		Conf:                m.conf,
		CoreSvc:             p.CoreSvc,
		MgmtHTTP:            p.MgmtHTTP,
		Infra:               p.InfraClient,
		DestinationRegistry: registy,
		Tailscale:           p.Tailscale,
		K8sJumperFactory:    k8sJumperFactory,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create jumper registry: %w", err)
	}

	return m, nil
}

func (m *manager) Initialize(ctx context.Context) error {
	commonLabels := types.NewLabels(nil)

	// create ns if not exist
	ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: m.namespace}}
	result, err := ctrlutil.CreateOrUpdate(ctx, m.cluster.Ctrl, ns, func() error {
		if ns.Labels == nil {
			ns.Labels = make(map[string]string)
		}
		commonLabels.InjectTo(ns.Labels)
		return nil
	})
	if err != nil {
		return err
	}
	if result != ctrlutil.OperationResultNone {
		m.logger.Info("create/updated the jump pod namespace", "operation", result)
	}

	// create access least role if not exist
	accessLeastRole := &rbacv1.ClusterRole{ObjectMeta: metav1.ObjectMeta{Name: accessLeastRoleName}}
	result, err = ctrlutil.CreateOrUpdate(ctx, m.cluster.Ctrl, accessLeastRole, func() error {
		if accessLeastRole.Labels == nil {
			accessLeastRole.Labels = make(map[string]string)
		}
		commonLabels.InjectTo(accessLeastRole.Labels)
		accessLeastRole.Rules = []rbacv1.PolicyRule{
			{
				APIGroups: []string{""},
				Resources: []string{"pods"},
				Verbs:     []string{"list"},
			},
		}
		return nil
	})
	if err != nil {
		return err
	}
	if result != ctrlutil.OperationResultNone {
		m.logger.Info("created/updated the access least role", "operation", result)
	}

	// create the access least role binding if not exist
	accessLeastRoleBinding := &rbacv1.ClusterRoleBinding{
		ObjectMeta: metav1.ObjectMeta{Name: accessLeastRoleBindingName},
	}
	result, err = ctrlutil.CreateOrUpdate(ctx, m.cluster.Ctrl, accessLeastRoleBinding, func() error {
		if accessLeastRoleBinding.Labels == nil {
			accessLeastRoleBinding.Labels = make(map[string]string)
		}
		commonLabels.InjectTo(accessLeastRoleBinding.Labels)
		accessLeastRoleBinding.RoleRef = rbacv1.RoleRef{
			APIGroup: "rbac.authorization.k8s.io",
			Kind:     "ClusterRole",
			Name:     accessLeastRoleName,
		}
		return nil
	})
	if err != nil {
		return err
	}
	if result != ctrlutil.OperationResultNone {
		m.logger.Info("created/updated the access least role binding", "operation", result)
	}

	// create the headless service for the jump pod
	jumpPodHeadlessSvc := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:      jumpPodPeerServiceName,
			Namespace: m.namespace,
		},
	}
	result, err = ctrlutil.CreateOrUpdate(ctx, m.cluster.Ctrl, jumpPodHeadlessSvc, func() error {
		if jumpPodHeadlessSvc.Labels == nil {
			jumpPodHeadlessSvc.Labels = make(map[string]string)
		}
		commonLabels.InjectTo(jumpPodHeadlessSvc.Labels)
		jumpPodHeadlessSvc.Spec.ClusterIP = "None"
		jumpPodHeadlessSvc.Spec.Selector = commonLabels.Copy() // must match the label of the jump pod
		return nil
	})
	if err != nil {
		return err
	}
	if result != ctrlutil.OperationResultNone {
		m.logger.Info("created/updated the jump-pod-peer service", "operation", result)
	}

	// create the service account for jump pod
	jumpPodSA := &corev1.ServiceAccount{
		ObjectMeta: metav1.ObjectMeta{
			Name:      jumpPodServiceAccountName,
			Namespace: m.namespace,
		},
	}
	result, err = ctrlutil.CreateOrUpdate(ctx, m.cluster.Ctrl, jumpPodSA, func() error {
		if jumpPodSA.Labels == nil {
			jumpPodSA.Labels = make(map[string]string)
		}
		commonLabels.InjectTo(jumpPodSA.Labels)

		roleAnnoKey, roleAnnoVal := m.providerAdapter.ServiceAccountAnnoForRole()
		jumpPodSA.Annotations = map[string]string{
			roleAnnoKey: roleAnnoVal,
		}
		return nil
	})
	if err != nil {
		return err
	}
	if result != ctrlutil.OperationResultNone {
		m.logger.Info("created/updated the jump-pod-sa service account", "operation", result)
	}

	// create the shared pv and pvc for the jump pod
	jumpPodSharedPV := &corev1.PersistentVolume{
		ObjectMeta: metav1.ObjectMeta{
			Name: fmt.Sprintf("%s-%s", jumpPodSharedPVNamePrefix, m.namespace),
		},
	}
	result, err = ctrlutil.CreateOrUpdate(ctx, m.cluster.Ctrl, jumpPodSharedPV, func() error {
		if jumpPodSharedPV.Labels == nil {
			jumpPodSharedPV.Labels = make(map[string]string)
		}
		commonLabels.InjectTo(jumpPodSharedPV.Labels)

		return m.providerAdapter.MutateSharedFSPV(ctx, jumpPodSharedPV)
	})
	if err != nil {
		return err
	}
	if result != ctrlutil.OperationResultNone {
		m.logger.Info("created/updated the jump pod shared pv", "operation", result)
	}
	m.jumpPodSharedPV = jumpPodSharedPV

	jumpPodSharedPVC := &corev1.PersistentVolumeClaim{
		ObjectMeta: metav1.ObjectMeta{
			Name:      fmt.Sprintf("%s-%s", jumpPodSharedPVCNamePrefix, m.namespace),
			Namespace: m.namespace,
		},
	}
	result, err = ctrlutil.CreateOrUpdate(ctx, m.cluster.Ctrl, jumpPodSharedPVC, func() error {
		if jumpPodSharedPVC.Labels == nil {
			jumpPodSharedPVC.Labels = make(map[string]string)
		}
		commonLabels.InjectTo(jumpPodSharedPVC.Labels)

		return m.providerAdapter.MutateSharedFSPVC(ctx, jumpPodSharedPVC)
	})
	if err != nil {
		return err
	}
	if result != ctrlutil.OperationResultNone {
		m.logger.Info("created/updated the jump pod shared pvc", "operation", result)
	}
	m.jumpPodSharedPVC = jumpPodSharedPVC

	// update existing jumppod if needed
	jps, err := m.ListJumpPods(ctx)
	if err != nil {
		return err
	}
	for _, jp := range jps {
		pod := jp.Pod()
		result, err := ctrlutil.CreateOrUpdate(ctx, m.cluster.Ctrl, pod, func() error {
			for idx, container := range pod.Spec.Containers {
				if container.Name == jumpPodContainerName {
					pod.Spec.Containers[idx].Image = m.conf.JumpPod.JumpPodImage
				}
				if container.Name == jumpPodAgentContainerName {
					pod.Spec.Containers[idx].Image = m.conf.JumpPod.JumpPodAgentImage
				}
			}
			return nil
		})
		if err != nil {
			m.logger.Error(err, "failed to update the jump pod", "pod", pod.Name)
			continue
		}
		if result != ctrlutil.OperationResultNone {
			m.logger.Info("updated the jump pod", "pod", pod.Name, "operation", result)
		}
	}

	return nil
}

func (m *manager) GetJumpPod(ctx context.Context, name string) (JumpPod, error) {
	jctx := m.jumpctx(name)

	pod := &corev1.Pod{}
	err := m.cluster.Ctrl.Get(ctx, jctx.Namer.JumpPod(), pod)
	if err != nil {
		return nil, err
	}

	jp := &jumpPod{
		jctx:    jctx,
		pod:     pod,
		cluster: m.cluster,
		jumper:  m.jumper,
	}
	return jp, nil
}

func (m *manager) ListJumpPods(ctx context.Context) ([]JumpPod, error) {
	pods := &corev1.PodList{}
	nameLabelReq, err := labels.NewRequirement(types.NameLabelKey, selection.Exists, nil)
	if err != nil {
		return nil, fmt.Errorf("build label requirement for list jump pods: %w", err)
	}

	err = m.cluster.Ctrl.List(
		ctx,
		pods,
		client.InNamespace(m.namespace),
		client.MatchingLabelsSelector{Selector: labels.NewSelector().Add(*nameLabelReq)},
	)
	if err != nil {
		return nil, err
	}

	jpList := make([]JumpPod, 0, len(pods.Items))
	for i := range pods.Items {
		pod := pods.Items[i]

		name, _ := types.NewLabels(pod.Labels).GetName()
		if name == "" {
			m.logger.Error(fmt.Errorf("the name of a jump pod is empty"), "ignore a jump pod when list", "pod", pod.Name)
			continue
		}

		jctx := m.jumpctx(name)
		jpList = append(jpList, &jumpPod{
			jctx:    jctx,
			pod:     &pod,
			cluster: m.cluster,
			jumper:  m.jumper,
		})
	}

	return jpList, nil
}

func (m *manager) SetUpJumpPod(ctx context.Context, name string) (JumpPod, error) {
	jctx := m.jumpctx(name)
	err := m.setUpJumpPod(ctx, jctx)
	if err != nil {
		return nil, err
	}

	jp := &jumpPod{
		jctx:    jctx,
		cluster: m.cluster,
		jumper:  m.jumper,
	}
	return jp, nil
}

func (m *manager) TearDownJumpPod(ctx context.Context, name string) error {
	err := m.tearDownJumpPod(ctx, m.jumpctx(name))
	if err != nil {
		return err
	}

	return nil
}

func (m *manager) SupportedDestinations(typ jpent.DestinationType) []string {
	return m.jumper.SupportedDestinations(typ)
}

func (m *manager) jumpctx(name string) *types.JumpContext {
	id := types.NewID(name)
	return &types.JumpContext{
		Name:   name,
		ID:     id,
		Namer:  types.NewResourceNamer(id, m.namespace),
		Logger: m.logger.WithValues("id", id, "name", name),
	}
}

func (m *manager) setUpJumpPod(ctx context.Context, jctx *types.JumpContext) error {
	id := jctx.ID
	jumpPodK8sCredKey := jctx.Namer.JumpPodK8sCredSecret()
	jumpPodKey := jctx.Namer.JumpPod()
	accessSAKey := jctx.Namer.AccessServiceAccount()
	accessRoleKey := jctx.Namer.AccessRole()
	accessRoleBindingKey := jctx.Namer.AccessRoleBinding()
	commonLabels := types.NewLabels(nil).SetID(id).SetName(jctx.Name)

	// create the access service account
	// It is used to generate the access token to access the jump pod in the jump server.
	accessSA := &corev1.ServiceAccount{
		ObjectMeta: metav1.ObjectMeta{
			Name:      accessSAKey.Name,
			Namespace: accessSAKey.Namespace,
		},
	}
	created, err := k8sutil.GetOrCreate(ctx, m.cluster.Ctrl, accessSA, func() error {
		accessSA.Labels = commonLabels.Copy()
		return nil
	})
	if err != nil {
		return err
	}
	if created {
		jctx.Logger.Info("created the access service account", "resource", accessSAKey.String())
	}

	// create the access role and role binding
	// It grants the access service account the permission.
	accessRole := &rbacv1.Role{
		ObjectMeta: metav1.ObjectMeta{
			Name:      accessRoleKey.Name,
			Namespace: accessRoleKey.Namespace,
		},
	}
	created, err = k8sutil.GetOrCreate(ctx, m.cluster.Ctrl, accessRole, func() error {
		accessRole.Labels = commonLabels.Copy()
		accessRole.Rules = []rbacv1.PolicyRule{
			{
				APIGroups:     []string{""},
				Resources:     []string{"pods"},
				ResourceNames: []string{jumpPodKey.Name},
				Verbs:         []string{"get"},
			},
			{
				APIGroups:     []string{""},
				Resources:     []string{"pods/exec"},
				ResourceNames: []string{jumpPodKey.Name},
				Verbs:         []string{"create"},
			},
		}
		return nil
	})
	if err != nil {
		return err
	}
	if created {
		jctx.Logger.Info("created the access role", "resource", accessRoleKey.String())
	}
	accessRoleBinding := &rbacv1.RoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name:      accessRoleBindingKey.Name,
			Namespace: accessRoleBindingKey.Namespace,
		},
	}
	created, err = k8sutil.GetOrCreate(ctx, m.cluster.Ctrl, accessRoleBinding, func() error {
		accessRoleBinding.Labels = commonLabels.Copy()
		accessRoleBinding.Subjects = []rbacv1.Subject{
			{
				Kind:      "ServiceAccount",
				Name:      accessSA.Name,
				Namespace: accessSA.Namespace,
			},
		}
		accessRoleBinding.RoleRef = rbacv1.RoleRef{
			APIGroup: "rbac.authorization.k8s.io",
			Kind:     "Role",
			Name:     accessRole.Name,
		}
		return nil
	})
	if err != nil {
		return err
	}
	if created {
		jctx.Logger.Info("created the access role binding", "resource", accessRoleBindingKey.String())
	}

	// create the k8s cred secret to store the k8s config
	k8sCred := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      jumpPodK8sCredKey.Name,
			Namespace: jumpPodK8sCredKey.Namespace,
		},
	}
	created, err = k8sutil.GetOrCreate(ctx, m.cluster.Ctrl, k8sCred, func() error {
		k8sCred.Labels = commonLabels.Copy()
		return nil
	})
	if err != nil {
		return err
	}
	if created {
		jctx.Logger.Info("created the k8s cred secret", "resource", jumpPodK8sCredKey.String())
	}

	// create the jump pod
	jumpPod := &corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:      jumpPodKey.Name,
			Namespace: jumpPodKey.Namespace,
		},
	}
	created, err = k8sutil.GetOrCreate(ctx, m.cluster.Ctrl, jumpPod, func() error {
		// TODO: sperate a new function to generate the jump pod spec
		jpCPULmt, err := resource.ParseQuantity(m.conf.JumpPod.JumpPodCPULimit)
		if err != nil {
			return fmt.Errorf("failed to parse jump pod cpu limit: %w", err)
		}
		jpMemLmt, err := resource.ParseQuantity(m.conf.JumpPod.JumpPodMemLimit)
		if err != nil {
			return fmt.Errorf("failed to parse jump pod memory limit: %w", err)
		}
		jpaCPULmt, err := resource.ParseQuantity(m.conf.JumpPod.JumpPodAgentCPULimit)
		if err != nil {
			return fmt.Errorf("failed to parse jump pod cpu limit: %w", err)
		}
		jpaMemLmt, err := resource.ParseQuantity(m.conf.JumpPod.JumpPodAgentMemLimit)
		if err != nil {
			return fmt.Errorf("failed to parse jump pod memory limit: %w", err)
		}

		jumpPod.Labels = commonLabels.Copy()
		jumpPod.Spec = corev1.PodSpec{
			Containers: []corev1.Container{
				{
					Name:    jumpPodContainerName,
					Image:   m.conf.JumpPod.JumpPodImage,
					Command: []string{"/usr/bin/tail", "-f", "/dev/null"},
					Resources: corev1.ResourceRequirements{
						Requests: corev1.ResourceList{
							corev1.ResourceCPU:    resource.MustParse("50m"),
							corev1.ResourceMemory: resource.MustParse("50Mi"),
						},
						Limits: corev1.ResourceList{
							corev1.ResourceCPU:    jpCPULmt,
							corev1.ResourceMemory: jpMemLmt,
						},
					},
					VolumeMounts: []corev1.VolumeMount{
						{
							Name:      "shared-storage",
							MountPath: "/storage",
						},
						{
							Name:      "cred-store",
							MountPath: "/root/.jump", // NOTE: must match the output dir in the jump pod agent
						},
					},
				},
				{
					Name:    jumpPodAgentContainerName,
					Image:   m.conf.JumpPod.JumpPodAgentImage,
					Command: []string{"/jump-pod-agent"},
					Ports: []corev1.ContainerPort{
						{
							Name:          "jump-pod-agent",
							Protocol:      corev1.ProtocolTCP,
							ContainerPort: consts.JumpPodAgentPort,
						},
					},
					Resources: corev1.ResourceRequirements{
						Requests: corev1.ResourceList{
							corev1.ResourceCPU:    resource.MustParse("50m"),
							corev1.ResourceMemory: resource.MustParse("50Mi"),
						},
						Limits: corev1.ResourceList{
							corev1.ResourceCPU:    jpaCPULmt,
							corev1.ResourceMemory: jpaMemLmt,
						},
					},
					VolumeMounts: []corev1.VolumeMount{
						{
							Name:      "cred-store",
							MountPath: "/root/.jump", // NOTE: must match the output dir in the jump pod agent
						},
						{
							Name:      "k8s-cred",
							MountPath: "/root/.jump-pod-agent/kube/creds", // NOTE: must match the input dir in the jump pod agent
							ReadOnly:  true,
						},
					},
					SecurityContext: &corev1.SecurityContext{
						Capabilities: &corev1.Capabilities{
							Add: []corev1.Capability{
								"NET_ADMIN", // for tailscale
							},
						},
					},
				},
			},
			Affinity: &corev1.Affinity{
				NodeAffinity: &corev1.NodeAffinity{
					RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
						NodeSelectorTerms: []corev1.NodeSelectorTerm{
							{
								MatchExpressions: []corev1.NodeSelectorRequirement{
									{
										Key:      "node_type", // must match the label in the node, see https://github.com/tidbcloud/devops-cd/blob/main/infra/src/eks.ts
										Operator: corev1.NodeSelectorOpIn,
										Values:   []string{"jumppod"},
									},
								},
							},
						},
					},
				},
			},
			Hostname:           jumpPod.Name,
			Subdomain:          jumpPodPeerServiceName,
			ServiceAccountName: jumpPodServiceAccountName,
			Volumes: []corev1.Volume{
				{
					Name: "k8s-cred",
					VolumeSource: corev1.VolumeSource{
						Secret: &corev1.SecretVolumeSource{
							SecretName: k8sCred.Name,
						},
					},
				},
				{
					Name: "shared-storage",
					VolumeSource: corev1.VolumeSource{
						PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
							ClaimName: m.jumpPodSharedPVC.Name,
						},
					},
				},
				{
					Name: "cred-store", // for jump pod agent to share cred with the jump pod
					VolumeSource: corev1.VolumeSource{
						EmptyDir: &corev1.EmptyDirVolumeSource{},
					},
				},
			},
			Tolerations: []corev1.Toleration{
				{
					Key:      "dedicated", // must match the taint key in the node, see https://github.com/tidbcloud/devops-cd/blob/main/infra/src/eks.ts
					Operator: corev1.TolerationOpEqual,
					Value:    "jumppod",
				},
			},
		}
		return nil
	})
	if err != nil {
		return err
	}
	if created {
		jctx.Logger.Info("created the jump pod", "resource", jumpPodKey.String())
	}

	err = m.setUpAccessLeastRole(ctx, jctx)
	if err != nil {
		return fmt.Errorf("failed to set up access least role: %w", err)
	}

	return nil
}

func (m *manager) tearDownJumpPod(ctx context.Context, jctx *types.JumpContext) error {
	jumpPod := &corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:      jctx.Namer.JumpPod().Name,
			Namespace: jctx.Namer.JumpPod().Namespace,
		},
	}
	jumpPodK8sCred := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      jctx.Namer.JumpPodK8sCredSecret().Name,
			Namespace: jctx.Namer.JumpPodK8sCredSecret().Namespace,
		},
	}
	accessSA := &corev1.ServiceAccount{
		ObjectMeta: metav1.ObjectMeta{
			Name:      jctx.Namer.AccessServiceAccount().Name,
			Namespace: jctx.Namer.AccessServiceAccount().Namespace,
		},
	}
	accessRole := &rbacv1.Role{
		ObjectMeta: metav1.ObjectMeta{
			Name:      jctx.Namer.AccessRole().Name,
			Namespace: jctx.Namer.AccessRole().Namespace,
		},
	}
	accessRoleBinding := &rbacv1.RoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name:      jctx.Namer.AccessRoleBinding().Name,
			Namespace: jctx.Namer.AccessRoleBinding().Namespace,
		},
	}

	objsToDelete := []client.Object{
		jumpPod, jumpPodK8sCred,
		accessSA, accessRole, accessRoleBinding,
	}
	errs := []error{}
	for _, obj := range objsToDelete {
		err := m.cluster.Ctrl.Delete(ctx, obj)
		if err == nil {
			jctx.Logger.Info("deleted a resource", "resource", client.ObjectKeyFromObject(obj).String())
		} else if client.IgnoreNotFound(err) != nil {
			errs = append(errs, err)
		}
	}

	err := m.tearDownAccessLeastRole(ctx, jctx)
	if err != nil {
		errs = append(errs, err)
	}

	return errors.Join(errs...)
}

func (m *manager) setUpAccessLeastRole(ctx context.Context, jctx *types.JumpContext) error {
	// get or create the access least role binding
	accessLeastRoleBinding := &rbacv1.ClusterRoleBinding{}
	err := m.cluster.Ctrl.Get(ctx, client.ObjectKey{Name: accessLeastRoleBindingName}, accessLeastRoleBinding)
	if err != nil {
		return err
	}

	// update the access least role binding and add the access service account
	accessSA := jctx.Namer.AccessServiceAccount()
	exist := slices.ContainsFunc(accessLeastRoleBinding.Subjects, func(s rbacv1.Subject) bool {
		return s.Name == accessSA.Name &&
			s.Namespace == accessSA.Namespace
	})
	if !exist {
		sub := rbacv1.Subject{
			Kind:      "ServiceAccount",
			Name:      accessSA.Name,
			Namespace: accessSA.Namespace,
		}
		accessLeastRoleBinding.Subjects = append(accessLeastRoleBinding.Subjects, sub)
		err = m.cluster.Ctrl.Update(ctx, accessLeastRoleBinding)
		if err != nil {
			return err
		}
	}

	return nil
}

func (m *manager) tearDownAccessLeastRole(ctx context.Context, jctx *types.JumpContext) error {
	accessLeastRoleBindingName := "jump-server-least-role-binding"

	// update the access least role binding and remove the access service account
	accessLeastRoleBinding := &rbacv1.ClusterRoleBinding{}
	err := m.cluster.Ctrl.Get(ctx, client.ObjectKey{Name: accessLeastRoleBindingName}, accessLeastRoleBinding)
	if err != nil {
		return err
	}

	accessSA := jctx.Namer.AccessServiceAccount()
	exist := slices.ContainsFunc(accessLeastRoleBinding.Subjects, func(s rbacv1.Subject) bool {
		return s.Name == accessSA.Name &&
			s.Namespace == accessSA.Namespace
	})
	if exist {
		accessLeastRoleBinding.Subjects = slices.DeleteFunc(accessLeastRoleBinding.Subjects,
			func(s rbacv1.Subject) bool {
				return s.Name == accessSA.Name &&
					s.Namespace == accessSA.Namespace
			},
		)
		err = m.cluster.Ctrl.Update(ctx, accessLeastRoleBinding)
		if err != nil {
			return err
		}
	}

	return nil
}
