// Copyright Contributors to the KubeOpenCode project

package e2e

import (
	"fmt"
	"time"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"

	kubeopenv1alpha1 "github.com/kubeopencode/kubeopencode/api/v1alpha1"
)

const (
	// contextHashAnnotationKey mirrors the controller constant for e2e test assertions.
	contextHashAnnotationKey = "kubeopencode.io/context-hash"
)

var _ = Describe("Agent E2E Tests", Label(LabelAgent), func() {

	Context("Agent with custom podSpec.labels", func() {
		It("should apply labels to generated Pods", func() {
			agentName := uniqueName("ws-labels")
			taskName := uniqueName("task-labels")
			content := "# Labels Test"

			By("Creating Agent with podSpec.labels")
			agent := &kubeopenv1alpha1.Agent{
				ObjectMeta: metav1.ObjectMeta{
					Name:      agentName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.AgentSpec{
					AgentImage:         agentImage,
					ExecutorImage:      echoImage,
					ServiceAccountName: testServiceAccount,
					WorkspaceDir:       "/workspace",
					Command:            []string{"sh", "-c", "echo '=== Task Content ===' && find ${WORKSPACE_DIR} -type f -print0 2>/dev/null | sort -z | xargs -0 -I {} sh -c 'echo \"--- File: {} ---\" && cat \"{}\" && echo' && echo '=== Task Completed ==='"},
					PodSpec: &kubeopenv1alpha1.AgentPodSpec{
						Labels: map[string]string{
							"custom-label":   "custom-value",
							"network-policy": "restricted",
							"team":           "platform",
						},
					},
				},
			}
			Expect(k8sClient.Create(ctx, agent)).Should(Succeed())

			By("Creating Task using Agent")
			task := &kubeopenv1alpha1.Task{
				ObjectMeta: metav1.ObjectMeta{
					Name:      taskName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.TaskSpec{
					AgentRef:    &kubeopenv1alpha1.AgentReference{Name: agentName},
					Description: &content,
				},
			}
			Expect(k8sClient.Create(ctx, task)).Should(Succeed())

			By("Waiting for Task to start running")
			taskKey := types.NamespacedName{Name: taskName, Namespace: testNS}
			Eventually(func() kubeopenv1alpha1.TaskPhase {
				t := &kubeopenv1alpha1.Task{}
				if err := k8sClient.Get(ctx, taskKey, t); err != nil {
					return ""
				}
				return t.Status.Phase
			}, timeout, interval).Should(Equal(kubeopenv1alpha1.TaskPhaseRunning))

			By("Verifying Pod has custom labels")
			Eventually(func() map[string]string {
				pods := &corev1.PodList{}
				if err := k8sClient.List(ctx, pods,
					client.InNamespace(testNS),
					client.MatchingLabels{"kubeopencode.io/task": taskName}); err != nil || len(pods.Items) == 0 {
					return nil
				}
				return pods.Items[0].Labels
			}, timeout, interval).Should(And(
				HaveKeyWithValue("custom-label", "custom-value"),
				HaveKeyWithValue("network-policy", "restricted"),
				HaveKeyWithValue("team", "platform"),
			))

			By("Cleaning up")
			Expect(k8sClient.Delete(ctx, task)).Should(Succeed())
			Expect(k8sClient.Delete(ctx, agent)).Should(Succeed())
		})
	})

	Context("Agent with podSpec.scheduling constraints", func() {
		It("should apply nodeSelector to generated Pods", func() {
			agentName := uniqueName("ws-scheduling")
			taskName := uniqueName("task-scheduling")
			content := "# Scheduling Test"

			By("Creating Agent with podSpec.scheduling")
			agent := &kubeopenv1alpha1.Agent{
				ObjectMeta: metav1.ObjectMeta{
					Name:      agentName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.AgentSpec{
					AgentImage:         agentImage,
					ExecutorImage:      echoImage,
					ServiceAccountName: testServiceAccount,
					WorkspaceDir:       "/workspace",
					Command:            []string{"sh", "-c", "echo '=== Task Content ===' && find ${WORKSPACE_DIR} -type f -print0 2>/dev/null | sort -z | xargs -0 -I {} sh -c 'echo \"--- File: {} ---\" && cat \"{}\" && echo' && echo '=== Task Completed ==='"},
					PodSpec: &kubeopenv1alpha1.AgentPodSpec{
						Scheduling: &kubeopenv1alpha1.PodScheduling{
							NodeSelector: map[string]string{
								"kubernetes.io/os": "linux",
							},
						},
					},
				},
			}
			Expect(k8sClient.Create(ctx, agent)).Should(Succeed())

			By("Creating Task")
			task := &kubeopenv1alpha1.Task{
				ObjectMeta: metav1.ObjectMeta{
					Name:      taskName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.TaskSpec{
					AgentRef:    &kubeopenv1alpha1.AgentReference{Name: agentName},
					Description: &content,
				},
			}
			Expect(k8sClient.Create(ctx, task)).Should(Succeed())

			By("Waiting for Task to complete")
			taskKey := types.NamespacedName{Name: taskName, Namespace: testNS}
			Eventually(func() kubeopenv1alpha1.TaskPhase {
				t := &kubeopenv1alpha1.Task{}
				if err := k8sClient.Get(ctx, taskKey, t); err != nil {
					return ""
				}
				return t.Status.Phase
			}, timeout, interval).Should(Equal(kubeopenv1alpha1.TaskPhaseCompleted))

			By("Verifying Pod was scheduled successfully with nodeSelector")
			// If the Pod completed successfully, the scheduling was applied correctly
			logs := getPodLogs(ctx, testNS, fmt.Sprintf("%s-pod", taskName))
			Expect(logs).Should(ContainSubstring("Scheduling Test"))

			By("Cleaning up")
			Expect(k8sClient.Delete(ctx, task)).Should(Succeed())
			Expect(k8sClient.Delete(ctx, agent)).Should(Succeed())
		})
	})

	Context("Agent with credentials", func() {
		It("should inject credentials as environment variables", func() {
			agentName := uniqueName("ws-creds")
			taskName := uniqueName("task-creds")
			secretName := uniqueName("test-secret")
			content := "# Credentials Test"

			By("Creating Secret")
			secret := &corev1.Secret{
				ObjectMeta: metav1.ObjectMeta{
					Name:      secretName,
					Namespace: testNS,
				},
				Data: map[string][]byte{
					"api-key": []byte("test-api-key-value-12345"),
				},
			}
			Expect(k8sClient.Create(ctx, secret)).Should(Succeed())

			envName := "TEST_API_KEY"
			By("Creating Agent with credentials")
			agent := &kubeopenv1alpha1.Agent{
				ObjectMeta: metav1.ObjectMeta{
					Name:      agentName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.AgentSpec{
					AgentImage:         agentImage,
					ExecutorImage:      echoImage,
					ServiceAccountName: testServiceAccount,
					WorkspaceDir:       "/workspace",
					Command:            []string{"sh", "-c", "echo '=== Task Content ===' && find ${WORKSPACE_DIR} -type f -print0 2>/dev/null | sort -z | xargs -0 -I {} sh -c 'echo \"--- File: {} ---\" && cat \"{}\" && echo' && echo '=== Task Completed ==='"},
					Credentials: []kubeopenv1alpha1.Credential{
						{
							Name: "test-api-key",
							SecretRef: kubeopenv1alpha1.SecretReference{
								Name: secretName,
								Key:  ptr.To("api-key"),
							},
							Env: &envName,
						},
					},
				},
			}
			Expect(k8sClient.Create(ctx, agent)).Should(Succeed())

			By("Creating Task")
			task := &kubeopenv1alpha1.Task{
				ObjectMeta: metav1.ObjectMeta{
					Name:      taskName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.TaskSpec{
					AgentRef:    &kubeopenv1alpha1.AgentReference{Name: agentName},
					Description: &content,
				},
			}
			Expect(k8sClient.Create(ctx, task)).Should(Succeed())

			By("Waiting for Task to complete")
			taskKey := types.NamespacedName{Name: taskName, Namespace: testNS}
			Eventually(func() kubeopenv1alpha1.TaskPhase {
				t := &kubeopenv1alpha1.Task{}
				if err := k8sClient.Get(ctx, taskKey, t); err != nil {
					return ""
				}
				return t.Status.Phase
			}, timeout, interval).Should(Equal(kubeopenv1alpha1.TaskPhaseCompleted))

			By("Cleaning up")
			Expect(k8sClient.Delete(ctx, task)).Should(Succeed())
			Expect(k8sClient.Delete(ctx, agent)).Should(Succeed())
			Expect(k8sClient.Delete(ctx, secret)).Should(Succeed())
		})
	})

	Context("AgentRef validation", func() {
		It("should fail when agentRef is not specified", func() {
			taskName := uniqueName("task-no-agent")
			content := "# No Agent Test"

			By("Creating Task without AgentRef - should be rejected by API server")
			task := &kubeopenv1alpha1.Task{
				ObjectMeta: metav1.ObjectMeta{
					Name:      taskName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.TaskSpec{
					// AgentRef is NOT specified - CRD validation requires it
					Description: &content,
				},
			}
			err := k8sClient.Create(ctx, task)
			Expect(err).Should(HaveOccurred(), "Expected API server to reject Task without agentRef")
			Expect(err.Error()).Should(ContainSubstring("agentRef"))
		})
	})

	Context("Agent with maxConcurrentTasks limit", func() {
		It("should queue tasks when concurrency limit is reached", func() {
			agentName := uniqueName("ws-concurrency")
			maxConcurrent := int32(1)

			By("Creating Agent with maxConcurrentTasks=1")
			agent := &kubeopenv1alpha1.Agent{
				ObjectMeta: metav1.ObjectMeta{
					Name:      agentName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.AgentSpec{
					AgentImage:         agentImage,
					ExecutorImage:      echoImage,
					ServiceAccountName: testServiceAccount,
					WorkspaceDir:       "/workspace",
					// Use a command that takes some time to complete
					Command:            []string{"sh", "-c", "echo 'Starting task' && sleep 10 && echo 'Task completed'"},
					MaxConcurrentTasks: &maxConcurrent,
				},
			}
			Expect(k8sClient.Create(ctx, agent)).Should(Succeed())

			task1Name := uniqueName("task-conc-1")
			task2Name := uniqueName("task-conc-2")
			content1 := "# Task 1"
			content2 := "# Task 2"

			By("Creating first Task")
			task1 := &kubeopenv1alpha1.Task{
				ObjectMeta: metav1.ObjectMeta{
					Name:      task1Name,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.TaskSpec{
					AgentRef:    &kubeopenv1alpha1.AgentReference{Name: agentName},
					Description: &content1,
				},
			}
			Expect(k8sClient.Create(ctx, task1)).Should(Succeed())

			By("Waiting for first Task to be Running")
			task1Key := types.NamespacedName{Name: task1Name, Namespace: testNS}
			Eventually(func() kubeopenv1alpha1.TaskPhase {
				t := &kubeopenv1alpha1.Task{}
				if err := k8sClient.Get(ctx, task1Key, t); err != nil {
					return ""
				}
				return t.Status.Phase
			}, timeout, interval).Should(Equal(kubeopenv1alpha1.TaskPhaseRunning))

			By("Creating second Task while first is still running")
			task2 := &kubeopenv1alpha1.Task{
				ObjectMeta: metav1.ObjectMeta{
					Name:      task2Name,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.TaskSpec{
					AgentRef:    &kubeopenv1alpha1.AgentReference{Name: agentName},
					Description: &content2,
				},
			}
			Expect(k8sClient.Create(ctx, task2)).Should(Succeed())

			By("Verifying second Task enters Queued phase")
			task2Key := types.NamespacedName{Name: task2Name, Namespace: testNS}
			Eventually(func() kubeopenv1alpha1.TaskPhase {
				t := &kubeopenv1alpha1.Task{}
				if err := k8sClient.Get(ctx, task2Key, t); err != nil {
					return ""
				}
				return t.Status.Phase
			}, timeout, interval).Should(Equal(kubeopenv1alpha1.TaskPhaseQueued))

			By("Verifying Task has agent label")
			task2Obj := &kubeopenv1alpha1.Task{}
			Expect(k8sClient.Get(ctx, task2Key, task2Obj)).Should(Succeed())
			Expect(task2Obj.Labels["kubeopencode.io/agent"]).Should(Equal(agentName))

			By("Waiting for first Task to complete")
			Eventually(func() kubeopenv1alpha1.TaskPhase {
				t := &kubeopenv1alpha1.Task{}
				if err := k8sClient.Get(ctx, task1Key, t); err != nil {
					return ""
				}
				return t.Status.Phase
			}, timeout, interval).Should(Equal(kubeopenv1alpha1.TaskPhaseCompleted))

			By("Verifying second Task transitions to Running after first completes")
			Eventually(func() kubeopenv1alpha1.TaskPhase {
				t := &kubeopenv1alpha1.Task{}
				if err := k8sClient.Get(ctx, task2Key, t); err != nil {
					return ""
				}
				return t.Status.Phase
			}, timeout, interval).Should(Equal(kubeopenv1alpha1.TaskPhaseRunning))

			By("Waiting for second Task to complete")
			Eventually(func() kubeopenv1alpha1.TaskPhase {
				t := &kubeopenv1alpha1.Task{}
				if err := k8sClient.Get(ctx, task2Key, t); err != nil {
					return ""
				}
				return t.Status.Phase
			}, timeout, interval).Should(Equal(kubeopenv1alpha1.TaskPhaseCompleted))

			By("Cleaning up")
			Expect(k8sClient.Delete(ctx, task1)).Should(Succeed())
			Expect(k8sClient.Delete(ctx, task2)).Should(Succeed())
			Expect(k8sClient.Delete(ctx, agent)).Should(Succeed())
		})
	})

	Context("Agent with credential file mount", func() {
		It("should mount credentials as files", func() {
			agentName := uniqueName("ws-cred-file")
			taskName := uniqueName("task-cred-file")
			secretName := uniqueName("ssh-secret")
			content := "# Credential File Mount Test"

			By("Creating Secret with SSH key content")
			secret := &corev1.Secret{
				ObjectMeta: metav1.ObjectMeta{
					Name:      secretName,
					Namespace: testNS,
				},
				Data: map[string][]byte{
					"id_rsa": []byte("-----BEGIN RSA PRIVATE KEY-----\ntest-key-content\n-----END RSA PRIVATE KEY-----"),
				},
			}
			Expect(k8sClient.Create(ctx, secret)).Should(Succeed())

			sshKeyPath := "/home/agent/.ssh/id_rsa"
			keyName := "id_rsa"
			// Use 0444 (world readable) since the container runs as non-root user
			fileMode := int32(0444)
			By("Creating Agent with credential mounted as file")
			agent := &kubeopenv1alpha1.Agent{
				ObjectMeta: metav1.ObjectMeta{
					Name:      agentName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.AgentSpec{
					AgentImage:         agentImage,
					ExecutorImage:      echoImage,
					ServiceAccountName: testServiceAccount,
					WorkspaceDir:       "/workspace",
					Command:            []string{"sh", "-c", fmt.Sprintf("echo '=== Checking SSH Key ===' && ls -la %s && cat %s | head -1 && echo '=== Done ==='", sshKeyPath, sshKeyPath)},
					Credentials: []kubeopenv1alpha1.Credential{
						{
							Name: "ssh-key",
							SecretRef: kubeopenv1alpha1.SecretReference{
								Name: secretName,
								Key:  &keyName,
							},
							MountPath: &sshKeyPath,
							FileMode:  &fileMode,
						},
					},
				},
			}
			Expect(k8sClient.Create(ctx, agent)).Should(Succeed())

			By("Creating Task")
			task := &kubeopenv1alpha1.Task{
				ObjectMeta: metav1.ObjectMeta{
					Name:      taskName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.TaskSpec{
					AgentRef:    &kubeopenv1alpha1.AgentReference{Name: agentName},
					Description: &content,
				},
			}
			Expect(k8sClient.Create(ctx, task)).Should(Succeed())

			By("Waiting for Task to complete")
			taskKey := types.NamespacedName{Name: taskName, Namespace: testNS}
			Eventually(func() kubeopenv1alpha1.TaskPhase {
				t := &kubeopenv1alpha1.Task{}
				if err := k8sClient.Get(ctx, taskKey, t); err != nil {
					return ""
				}
				return t.Status.Phase
			}, timeout, interval).Should(Equal(kubeopenv1alpha1.TaskPhaseCompleted))

			By("Verifying SSH key was mounted correctly")
			logs := getPodLogs(ctx, testNS, fmt.Sprintf("%s-pod", taskName))
			Expect(logs).Should(ContainSubstring("Checking SSH Key"))
			Expect(logs).Should(ContainSubstring("BEGIN RSA PRIVATE KEY"))

			By("Cleaning up")
			Expect(k8sClient.Delete(ctx, task)).Should(Succeed())
			Expect(k8sClient.Delete(ctx, agent)).Should(Succeed())
			Expect(k8sClient.Delete(ctx, secret)).Should(Succeed())
		})
	})

	Context("Agent with quota rate limiting", func() {
		It("should queue tasks when rate limit is exceeded", func() {
			agentName := uniqueName("ws-quota")

			By("Creating Agent with quota limiting")
			agent := &kubeopenv1alpha1.Agent{
				ObjectMeta: metav1.ObjectMeta{
					Name:      agentName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.AgentSpec{
					AgentImage:         agentImage,
					ExecutorImage:      echoImage,
					ServiceAccountName: testServiceAccount,
					WorkspaceDir:       "/workspace",
					Command:            []string{"sh", "-c", "echo 'Quick task' && sleep 1"},
					Quota: &kubeopenv1alpha1.QuotaConfig{
						MaxTaskStarts: 2,
						WindowSeconds: 120, // 2 minutes window
					},
				},
			}
			Expect(k8sClient.Create(ctx, agent)).Should(Succeed())

			taskNames := []string{
				uniqueName("task-quota-1"),
				uniqueName("task-quota-2"),
				uniqueName("task-quota-3"),
			}
			content := "# Quota Test Task"

			By("Creating 3 Tasks rapidly")
			for _, taskName := range taskNames {
				task := &kubeopenv1alpha1.Task{
					ObjectMeta: metav1.ObjectMeta{
						Name:      taskName,
						Namespace: testNS,
					},
					Spec: kubeopenv1alpha1.TaskSpec{
						AgentRef:    &kubeopenv1alpha1.AgentReference{Name: agentName},
						Description: &content,
					},
				}
				Expect(k8sClient.Create(ctx, task)).Should(Succeed())
			}

			By("Verifying first two Tasks start running and third is queued")
			// First two should start (or complete quickly)
			task1Key := types.NamespacedName{Name: taskNames[0], Namespace: testNS}
			task3Key := types.NamespacedName{Name: taskNames[2], Namespace: testNS}

			// Wait for first task to at least start
			Eventually(func() bool {
				t := &kubeopenv1alpha1.Task{}
				if err := k8sClient.Get(ctx, task1Key, t); err != nil {
					return false
				}
				return t.Status.Phase == kubeopenv1alpha1.TaskPhaseRunning ||
					t.Status.Phase == kubeopenv1alpha1.TaskPhaseCompleted
			}, timeout, interval).Should(BeTrue())

			// Third task should be queued with QuotaExceeded reason
			Eventually(func() string {
				t := &kubeopenv1alpha1.Task{}
				if err := k8sClient.Get(ctx, task3Key, t); err != nil {
					return ""
				}
				if t.Status.Phase == kubeopenv1alpha1.TaskPhaseQueued {
					cond := getTaskCondition(t, "Queued")
					if cond != nil {
						return cond.Reason
					}
				}
				return string(t.Status.Phase)
			}, timeout, interval).Should(Or(Equal("QuotaExceeded"), Equal(string(kubeopenv1alpha1.TaskPhaseRunning)), Equal(string(kubeopenv1alpha1.TaskPhaseCompleted))))

			By("Waiting for all Tasks to complete")
			for _, taskName := range taskNames {
				taskKey := types.NamespacedName{Name: taskName, Namespace: testNS}
				Eventually(func() kubeopenv1alpha1.TaskPhase {
					t := &kubeopenv1alpha1.Task{}
					if err := k8sClient.Get(ctx, taskKey, t); err != nil {
						return ""
					}
					return t.Status.Phase
				}, timeout, interval).Should(Equal(kubeopenv1alpha1.TaskPhaseCompleted))
			}

			By("Cleaning up")
			for _, taskName := range taskNames {
				task := &kubeopenv1alpha1.Task{}
				taskKey := types.NamespacedName{Name: taskName, Namespace: testNS}
				if err := k8sClient.Get(ctx, taskKey, task); err == nil {
					Expect(k8sClient.Delete(ctx, task)).Should(Succeed())
				}
			}
			Expect(k8sClient.Delete(ctx, agent)).Should(Succeed())

			// Wait for agent's task history to clear
			Eventually(func() bool {
				a := &kubeopenv1alpha1.Agent{}
				return k8sClient.Get(ctx, types.NamespacedName{Name: agentName, Namespace: testNS}, a) != nil
			}, timeout, interval).Should(BeTrue())
		})
	})

	Context("Agent with config", func() {
		It("should write OpenCode config to file and set env var", func() {
			agentName := uniqueName("ws-config")
			taskName := uniqueName("task-config")
			content := "# Config Test"

			configJSON := `{"$schema":"https://opencode.ai/config.json","model":"test-model"}`

			By("Creating Agent with config")
			agent := &kubeopenv1alpha1.Agent{
				ObjectMeta: metav1.ObjectMeta{
					Name:      agentName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.AgentSpec{
					AgentImage:         agentImage,
					ExecutorImage:      echoImage,
					ServiceAccountName: testServiceAccount,
					WorkspaceDir:       "/workspace",
					Command:            []string{"sh", "-c", "echo '=== OPENCODE_CONFIG ===' && echo $OPENCODE_CONFIG && echo '=== Config Content ===' && cat /tools/opencode.json && echo '=== Done ==='"},
					Config:             &runtime.RawExtension{Raw: []byte(configJSON)},
				},
			}
			Expect(k8sClient.Create(ctx, agent)).Should(Succeed())

			By("Creating Task")
			task := &kubeopenv1alpha1.Task{
				ObjectMeta: metav1.ObjectMeta{
					Name:      taskName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.TaskSpec{
					AgentRef:    &kubeopenv1alpha1.AgentReference{Name: agentName},
					Description: &content,
				},
			}
			Expect(k8sClient.Create(ctx, task)).Should(Succeed())

			By("Waiting for Task to complete")
			waitForTaskPhase(testNS, taskName, kubeopenv1alpha1.TaskPhaseCompleted)

			By("Verifying config file path is set in OPENCODE_CONFIG env var")
			logs := getPodLogs(ctx, testNS, fmt.Sprintf("%s-pod", taskName))
			Expect(logs).Should(ContainSubstring("OPENCODE_CONFIG"))
			Expect(logs).Should(ContainSubstring("/tools/opencode.json"))

			By("Verifying config content is written")
			Expect(logs).Should(ContainSubstring("test-model"))

			By("Cleaning up")
			Expect(k8sClient.Delete(ctx, task)).Should(Succeed())
			Expect(k8sClient.Delete(ctx, agent)).Should(Succeed())
		})
	})

	Context("Agent with tolerations", func() {
		It("should apply tolerations to generated Pods", func() {
			agentName := uniqueName("ws-tolerations")
			taskName := uniqueName("task-tolerations")
			content := "# Tolerations Test"

			By("Creating Agent with tolerations")
			agent := &kubeopenv1alpha1.Agent{
				ObjectMeta: metav1.ObjectMeta{
					Name:      agentName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.AgentSpec{
					AgentImage:         agentImage,
					ExecutorImage:      echoImage,
					ServiceAccountName: testServiceAccount,
					WorkspaceDir:       "/workspace",
					Command:            []string{"sh", "-c", "echo 'Tolerations test passed'"},
					PodSpec: &kubeopenv1alpha1.AgentPodSpec{
						Scheduling: &kubeopenv1alpha1.PodScheduling{
							Tolerations: []corev1.Toleration{
								{
									Key:      "dedicated",
									Operator: corev1.TolerationOpEqual,
									Value:    "ai-workload",
									Effect:   corev1.TaintEffectNoSchedule,
								},
							},
						},
					},
				},
			}
			Expect(k8sClient.Create(ctx, agent)).Should(Succeed())

			By("Creating Task")
			task := &kubeopenv1alpha1.Task{
				ObjectMeta: metav1.ObjectMeta{
					Name:      taskName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.TaskSpec{
					AgentRef:    &kubeopenv1alpha1.AgentReference{Name: agentName},
					Description: &content,
				},
			}
			Expect(k8sClient.Create(ctx, task)).Should(Succeed())

			By("Waiting for Pod to be created")
			var pod *corev1.Pod
			Eventually(func() bool {
				var err error
				pod, err = getPodForTask(ctx, testNS, taskName)
				return err == nil && pod != nil
			}, timeout, interval).Should(BeTrue())

			By("Verifying Pod has tolerations")
			Expect(pod.Spec.Tolerations).Should(ContainElement(
				corev1.Toleration{
					Key:      "dedicated",
					Operator: corev1.TolerationOpEqual,
					Value:    "ai-workload",
					Effect:   corev1.TaintEffectNoSchedule,
				},
			))

			By("Waiting for Task to complete")
			waitForTaskPhase(testNS, taskName, kubeopenv1alpha1.TaskPhaseCompleted)

			By("Cleaning up")
			Expect(k8sClient.Delete(ctx, task)).Should(Succeed())
			Expect(k8sClient.Delete(ctx, agent)).Should(Succeed())
		})
	})

	Context("Agent with affinity", func() {
		It("should apply affinity to generated Pods", func() {
			agentName := uniqueName("ws-affinity")
			taskName := uniqueName("task-affinity")
			content := "# Affinity Test"

			By("Creating Agent with node affinity")
			agent := &kubeopenv1alpha1.Agent{
				ObjectMeta: metav1.ObjectMeta{
					Name:      agentName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.AgentSpec{
					AgentImage:         agentImage,
					ExecutorImage:      echoImage,
					ServiceAccountName: testServiceAccount,
					WorkspaceDir:       "/workspace",
					Command:            []string{"sh", "-c", "echo 'Affinity test passed'"},
					PodSpec: &kubeopenv1alpha1.AgentPodSpec{
						Scheduling: &kubeopenv1alpha1.PodScheduling{
							Affinity: &corev1.Affinity{
								NodeAffinity: &corev1.NodeAffinity{
									PreferredDuringSchedulingIgnoredDuringExecution: []corev1.PreferredSchedulingTerm{
										{
											Weight: 100,
											Preference: corev1.NodeSelectorTerm{
												MatchExpressions: []corev1.NodeSelectorRequirement{
													{
														Key:      "topology.kubernetes.io/zone",
														Operator: corev1.NodeSelectorOpIn,
														Values:   []string{"us-east-1a", "us-east-1b"},
													},
												},
											},
										},
									},
								},
							},
						},
					},
				},
			}
			Expect(k8sClient.Create(ctx, agent)).Should(Succeed())

			By("Creating Task")
			task := &kubeopenv1alpha1.Task{
				ObjectMeta: metav1.ObjectMeta{
					Name:      taskName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.TaskSpec{
					AgentRef:    &kubeopenv1alpha1.AgentReference{Name: agentName},
					Description: &content,
				},
			}
			Expect(k8sClient.Create(ctx, task)).Should(Succeed())

			By("Waiting for Pod to be created")
			var pod *corev1.Pod
			Eventually(func() bool {
				var err error
				pod, err = getPodForTask(ctx, testNS, taskName)
				return err == nil && pod != nil
			}, timeout, interval).Should(BeTrue())

			By("Verifying Pod has affinity")
			Expect(pod.Spec.Affinity).ShouldNot(BeNil())
			Expect(pod.Spec.Affinity.NodeAffinity).ShouldNot(BeNil())

			By("Waiting for Task to complete")
			waitForTaskPhase(testNS, taskName, kubeopenv1alpha1.TaskPhaseCompleted)

			By("Cleaning up")
			Expect(k8sClient.Delete(ctx, task)).Should(Succeed())
			Expect(k8sClient.Delete(ctx, agent)).Should(Succeed())
		})
	})

	Context("Agent with resources", func() {
		It("should apply resource requests and limits to generated Pods", func() {
			agentName := uniqueName("ws-resources")
			taskName := uniqueName("task-resources")
			content := "# Resources Test"

			By("Creating Agent with resource requirements")
			agent := &kubeopenv1alpha1.Agent{
				ObjectMeta: metav1.ObjectMeta{
					Name:      agentName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.AgentSpec{
					AgentImage:         agentImage,
					ExecutorImage:      echoImage,
					ServiceAccountName: testServiceAccount,
					WorkspaceDir:       "/workspace",
					Command:            []string{"sh", "-c", "echo 'Resources test passed'"},
					PodSpec: &kubeopenv1alpha1.AgentPodSpec{
						Resources: &corev1.ResourceRequirements{
							Requests: corev1.ResourceList{
								corev1.ResourceMemory: resource.MustParse("128Mi"),
								corev1.ResourceCPU:    resource.MustParse("100m"),
							},
							Limits: corev1.ResourceList{
								corev1.ResourceMemory: resource.MustParse("512Mi"),
								corev1.ResourceCPU:    resource.MustParse("500m"),
							},
						},
					},
				},
			}
			Expect(k8sClient.Create(ctx, agent)).Should(Succeed())

			By("Creating Task")
			task := &kubeopenv1alpha1.Task{
				ObjectMeta: metav1.ObjectMeta{
					Name:      taskName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.TaskSpec{
					AgentRef:    &kubeopenv1alpha1.AgentReference{Name: agentName},
					Description: &content,
				},
			}
			Expect(k8sClient.Create(ctx, task)).Should(Succeed())

			By("Waiting for Pod to be created")
			var pod *corev1.Pod
			Eventually(func() bool {
				var err error
				pod, err = getPodForTask(ctx, testNS, taskName)
				return err == nil && pod != nil
			}, timeout, interval).Should(BeTrue())

			By("Verifying Pod container has resource requirements")
			// Find the agent container (main container, not init containers)
			var agentContainer *corev1.Container
			for i := range pod.Spec.Containers {
				if pod.Spec.Containers[i].Name == "agent" {
					agentContainer = &pod.Spec.Containers[i]
					break
				}
			}
			Expect(agentContainer).ShouldNot(BeNil())
			Expect(agentContainer.Resources.Requests.Memory().String()).Should(Equal("128Mi"))
			Expect(agentContainer.Resources.Limits.Memory().String()).Should(Equal("512Mi"))

			By("Waiting for Task to complete")
			waitForTaskPhase(testNS, taskName, kubeopenv1alpha1.TaskPhaseCompleted)

			By("Cleaning up")
			Expect(k8sClient.Delete(ctx, task)).Should(Succeed())
			Expect(k8sClient.Delete(ctx, agent)).Should(Succeed())
		})
	})

	Context("Agent with runtimeClassName", func() {
		// Skip this test in Kind clusters as RuntimeClass validation happens at Pod creation time
		// and Kind doesn't have custom RuntimeClasses installed
		PIt("should apply runtimeClassName to generated Pods", func() {
			agentName := uniqueName("ws-runtime")
			taskName := uniqueName("task-runtime")
			content := "# RuntimeClass Test"
			// Use a non-existent runtime class - we only verify the spec is set correctly
			// The Pod will not be scheduled but that's expected
			runtimeClassName := "test-runtime-class"

			By("Creating Agent with runtimeClassName")
			agent := &kubeopenv1alpha1.Agent{
				ObjectMeta: metav1.ObjectMeta{
					Name:      agentName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.AgentSpec{
					AgentImage:         agentImage,
					ExecutorImage:      echoImage,
					ServiceAccountName: testServiceAccount,
					WorkspaceDir:       "/workspace",
					Command:            []string{"sh", "-c", "echo 'RuntimeClass test'"},
					PodSpec: &kubeopenv1alpha1.AgentPodSpec{
						RuntimeClassName: &runtimeClassName,
					},
				},
			}
			Expect(k8sClient.Create(ctx, agent)).Should(Succeed())

			By("Creating Task")
			task := &kubeopenv1alpha1.Task{
				ObjectMeta: metav1.ObjectMeta{
					Name:      taskName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.TaskSpec{
					AgentRef:    &kubeopenv1alpha1.AgentReference{Name: agentName},
					Description: &content,
				},
			}
			Expect(k8sClient.Create(ctx, task)).Should(Succeed())

			By("Waiting for Pod to be created (may not be scheduled due to missing runtime)")
			var pod *corev1.Pod
			// Use a shorter timeout since we expect the Pod to be created quickly
			// but may not be scheduled
			Eventually(func() bool {
				var err error
				pod, err = getPodForTask(ctx, testNS, taskName)
				return err == nil && pod != nil
			}, time.Minute*2, interval).Should(BeTrue())

			By("Verifying Pod has runtimeClassName set")
			Expect(pod.Spec.RuntimeClassName).ShouldNot(BeNil())
			Expect(*pod.Spec.RuntimeClassName).Should(Equal("test-runtime-class"))

			// Note: Task may fail or stay pending if the runtime class doesn't exist
			// but we only care about verifying the spec is set correctly

			By("Cleaning up")
			Expect(k8sClient.Delete(ctx, task)).Should(Succeed())
			Expect(k8sClient.Delete(ctx, agent)).Should(Succeed())
		})
	})

	Context("Agent with entire Secret as env vars", func() {
		It("should inject all Secret keys as environment variables", func() {
			agentName := uniqueName("ws-secret-env")
			taskName := uniqueName("task-secret-env")
			secretName := uniqueName("multi-key-secret")
			content := "# Secret Env Test"

			By("Creating Secret with multiple keys")
			secret := &corev1.Secret{
				ObjectMeta: metav1.ObjectMeta{
					Name:      secretName,
					Namespace: testNS,
				},
				Data: map[string][]byte{
					"API_KEY":      []byte("test-api-key"),
					"API_SECRET":   []byte("test-api-secret"),
					"API_ENDPOINT": []byte("https://api.example.com"),
				},
			}
			Expect(k8sClient.Create(ctx, secret)).Should(Succeed())

			By("Creating Agent with credential referencing entire Secret (no key specified)")
			agent := &kubeopenv1alpha1.Agent{
				ObjectMeta: metav1.ObjectMeta{
					Name:      agentName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.AgentSpec{
					AgentImage:         agentImage,
					ExecutorImage:      echoImage,
					ServiceAccountName: testServiceAccount,
					WorkspaceDir:       "/workspace",
					Command:            []string{"sh", "-c", "echo '=== Env Vars ===' && env | grep API_ | sort && echo '=== Done ==='"},
					Credentials: []kubeopenv1alpha1.Credential{
						{
							Name: "api-credentials",
							SecretRef: kubeopenv1alpha1.SecretReference{
								Name: secretName,
								// No Key specified - entire secret becomes env vars
							},
						},
					},
				},
			}
			Expect(k8sClient.Create(ctx, agent)).Should(Succeed())

			By("Creating Task")
			task := &kubeopenv1alpha1.Task{
				ObjectMeta: metav1.ObjectMeta{
					Name:      taskName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.TaskSpec{
					AgentRef:    &kubeopenv1alpha1.AgentReference{Name: agentName},
					Description: &content,
				},
			}
			Expect(k8sClient.Create(ctx, task)).Should(Succeed())

			By("Waiting for Task to complete")
			taskKey := types.NamespacedName{Name: taskName, Namespace: testNS}
			Eventually(func() kubeopenv1alpha1.TaskPhase {
				t := &kubeopenv1alpha1.Task{}
				if err := k8sClient.Get(ctx, taskKey, t); err != nil {
					return ""
				}
				return t.Status.Phase
			}, timeout, interval).Should(Equal(kubeopenv1alpha1.TaskPhaseCompleted))

			By("Verifying all Secret keys are available as env vars")
			logs := getPodLogs(ctx, testNS, fmt.Sprintf("%s-pod", taskName))
			Expect(logs).Should(ContainSubstring("API_KEY=test-api-key"))
			Expect(logs).Should(ContainSubstring("API_SECRET=test-api-secret"))
			Expect(logs).Should(ContainSubstring("API_ENDPOINT=https://api.example.com"))

			By("Cleaning up")
			Expect(k8sClient.Delete(ctx, task)).Should(Succeed())
			Expect(k8sClient.Delete(ctx, agent)).Should(Succeed())
			Expect(k8sClient.Delete(ctx, secret)).Should(Succeed())
		})
	})

	Context("Agent context content change triggers Deployment rollout", func() {
		It("should update Deployment pod template hash when text context content changes", func() {
			agentName := uniqueName("ctx-hash")

			By("Creating Agent with a text context")
			agent := &kubeopenv1alpha1.Agent{
				ObjectMeta: metav1.ObjectMeta{
					Name:      agentName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.AgentSpec{
					AgentImage:         agentImage,
					ExecutorImage:      echoImage,
					ServiceAccountName: testServiceAccount,
					WorkspaceDir:       "/workspace",
					Command:            []string{"sh", "-c", "echo 'running' && sleep 3600"},
					Contexts: []kubeopenv1alpha1.ContextItem{
						{
							Type: kubeopenv1alpha1.ContextTypeText,
							Text: "initial system prompt for testing",
						},
					},
				},
			}
			Expect(k8sClient.Create(ctx, agent)).Should(Succeed())

			By("Waiting for Deployment to be created with context hash annotation")
			deploymentName := fmt.Sprintf("%s-server", agentName)
			deploymentKey := types.NamespacedName{Name: deploymentName, Namespace: testNS}
			var initialHash string
			Eventually(func() string {
				deployment := &appsv1.Deployment{}
				if err := k8sClient.Get(ctx, deploymentKey, deployment); err != nil {
					return ""
				}
				if deployment.Spec.Template.Annotations == nil {
					return ""
				}
				initialHash = deployment.Spec.Template.Annotations[contextHashAnnotationKey]
				return initialHash
			}, timeout, interval).ShouldNot(BeEmpty(), "Deployment should have context-hash annotation")

			By("Updating the Agent text context content")
			var updatedAgent kubeopenv1alpha1.Agent
			Expect(k8sClient.Get(ctx, types.NamespacedName{Name: agentName, Namespace: testNS}, &updatedAgent)).Should(Succeed())
			updatedAgent.Spec.Contexts[0].Text = "completely different system prompt with new instructions"
			Expect(k8sClient.Update(ctx, &updatedAgent)).Should(Succeed())

			By("Expecting the Deployment context hash annotation to change (triggering rolling update)")
			Eventually(func() string {
				deployment := &appsv1.Deployment{}
				if err := k8sClient.Get(ctx, deploymentKey, deployment); err != nil {
					return initialHash
				}
				if deployment.Spec.Template.Annotations == nil {
					return initialHash
				}
				return deployment.Spec.Template.Annotations[contextHashAnnotationKey]
			}, timeout, interval).ShouldNot(Equal(initialHash), "Context hash should change when text content changes")

			By("Cleaning up")
			Expect(k8sClient.Delete(ctx, &updatedAgent)).Should(Succeed())
		})

		It("should update Deployment pod template hash when config content changes alongside contexts", func() {
			agentName := uniqueName("cfg-hash")

			By("Creating Agent with config and a text context")
			agent := &kubeopenv1alpha1.Agent{
				ObjectMeta: metav1.ObjectMeta{
					Name:      agentName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.AgentSpec{
					AgentImage:         agentImage,
					ExecutorImage:      echoImage,
					ServiceAccountName: testServiceAccount,
					WorkspaceDir:       "/workspace",
					Command:            []string{"sh", "-c", "echo 'running' && sleep 3600"},
					Config:             &runtime.RawExtension{Raw: []byte(`{"model":"test-model-a"}`)},
					Contexts: []kubeopenv1alpha1.ContextItem{
						{
							Type: kubeopenv1alpha1.ContextTypeText,
							Text: "some context",
						},
					},
				},
			}
			Expect(k8sClient.Create(ctx, agent)).Should(Succeed())

			By("Waiting for Deployment with context hash annotation")
			deploymentName := fmt.Sprintf("%s-server", agentName)
			deploymentKey := types.NamespacedName{Name: deploymentName, Namespace: testNS}
			var initialHash string
			Eventually(func() string {
				deployment := &appsv1.Deployment{}
				if err := k8sClient.Get(ctx, deploymentKey, deployment); err != nil {
					return ""
				}
				if deployment.Spec.Template.Annotations == nil {
					return ""
				}
				initialHash = deployment.Spec.Template.Annotations[contextHashAnnotationKey]
				return initialHash
			}, timeout, interval).ShouldNot(BeEmpty())

			By("Updating only the config content (not adding/removing contexts)")
			var updatedAgent kubeopenv1alpha1.Agent
			Expect(k8sClient.Get(ctx, types.NamespacedName{Name: agentName, Namespace: testNS}, &updatedAgent)).Should(Succeed())
			updatedAgent.Spec.Config = &runtime.RawExtension{Raw: []byte(`{"model":"test-model-b"}`)}
			Expect(k8sClient.Update(ctx, &updatedAgent)).Should(Succeed())

			By("Expecting the context hash to change (config content change detected)")
			Eventually(func() string {
				deployment := &appsv1.Deployment{}
				if err := k8sClient.Get(ctx, deploymentKey, deployment); err != nil {
					return initialHash
				}
				if deployment.Spec.Template.Annotations == nil {
					return initialHash
				}
				return deployment.Spec.Template.Annotations[contextHashAnnotationKey]
			}, timeout, interval).ShouldNot(Equal(initialHash), "Context hash should change when config content changes")

			By("Cleaning up")
			Expect(k8sClient.Delete(ctx, &updatedAgent)).Should(Succeed())
		})
	})

	Context("Agent with entire Secret as directory", func() {
		It("should mount Secret as directory with each key as a file", func() {
			agentName := uniqueName("ws-secret-dir")
			taskName := uniqueName("task-secret-dir")
			secretName := uniqueName("cert-secret")
			content := "# Secret Dir Test"

			By("Creating Secret with config files")
			secret := &corev1.Secret{
				ObjectMeta: metav1.ObjectMeta{
					Name:      secretName,
					Namespace: testNS,
				},
				Data: map[string][]byte{
					"config.yaml":     []byte("# Config file\nkey: value\nenv: test"),
					"settings.json":   []byte(`{"debug": true, "level": "info"}`),
					"credentials.txt": []byte("username=testuser\npassword=testpass"),
				},
			}
			Expect(k8sClient.Create(ctx, secret)).Should(Succeed())

			By("Creating Agent with credential as directory mount")
			mountPath := "/etc/certs"
			// Use 0644 to allow non-root user to read the files
			fileMode := int32(420) // 0644 in decimal
			agent := &kubeopenv1alpha1.Agent{
				ObjectMeta: metav1.ObjectMeta{
					Name:      agentName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.AgentSpec{
					AgentImage:         agentImage,
					ExecutorImage:      echoImage,
					ServiceAccountName: testServiceAccount,
					WorkspaceDir:       "/workspace",
					Command:            []string{"sh", "-c", fmt.Sprintf("echo '=== Config Files ===' && ls -la %s && echo '=== Content ===' && head -1 %s/config.yaml && echo '=== Done ==='", mountPath, mountPath)},
					Credentials: []kubeopenv1alpha1.Credential{
						{
							Name: "certs",
							SecretRef: kubeopenv1alpha1.SecretReference{
								Name: secretName,
								// No Key specified - entire secret as directory
							},
							MountPath: &mountPath,
							FileMode:  &fileMode,
						},
					},
				},
			}
			Expect(k8sClient.Create(ctx, agent)).Should(Succeed())

			By("Creating Task")
			task := &kubeopenv1alpha1.Task{
				ObjectMeta: metav1.ObjectMeta{
					Name:      taskName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.TaskSpec{
					AgentRef:    &kubeopenv1alpha1.AgentReference{Name: agentName},
					Description: &content,
				},
			}
			Expect(k8sClient.Create(ctx, task)).Should(Succeed())

			By("Waiting for Task to complete")
			taskKey := types.NamespacedName{Name: taskName, Namespace: testNS}
			Eventually(func() kubeopenv1alpha1.TaskPhase {
				t := &kubeopenv1alpha1.Task{}
				if err := k8sClient.Get(ctx, taskKey, t); err != nil {
					return ""
				}
				return t.Status.Phase
			}, timeout, interval).Should(Equal(kubeopenv1alpha1.TaskPhaseCompleted))

			By("Verifying all Secret files are mounted")
			logs := getPodLogs(ctx, testNS, fmt.Sprintf("%s-pod", taskName))
			Expect(logs).Should(ContainSubstring("config.yaml"))
			Expect(logs).Should(ContainSubstring("settings.json"))
			Expect(logs).Should(ContainSubstring("credentials.txt"))
			Expect(logs).Should(ContainSubstring("Config file"))

			By("Cleaning up")
			Expect(k8sClient.Delete(ctx, task)).Should(Succeed())
			Expect(k8sClient.Delete(ctx, agent)).Should(Succeed())
			Expect(k8sClient.Delete(ctx, secret)).Should(Succeed())
		})
	})

	Context("Agent with extraPorts", func() {
		It("should expose extra ports on Deployment and Service", func() {
			agentName := uniqueName("ws-extra-ports")

			By("Creating Agent with ExtraPorts")
			agent := &kubeopenv1alpha1.Agent{
				ObjectMeta: metav1.ObjectMeta{
					Name:      agentName,
					Namespace: testNS,
				},
				Spec: kubeopenv1alpha1.AgentSpec{
					AgentImage:         agentImage,
					ExecutorImage:      echoImage,
					ServiceAccountName: testServiceAccount,
					WorkspaceDir:       "/workspace",
					Port:               4096,
					ExtraPorts: []kubeopenv1alpha1.ExtraPort{
						{Name: "webapp", Port: 3000},
						{Name: "vscode", Port: 8080, Protocol: corev1.ProtocolTCP},
					},
				},
			}
			Expect(k8sClient.Create(ctx, agent)).Should(Succeed())

			By("Waiting for Deployment to be created with extra container ports")
			deploymentKey := types.NamespacedName{
				Name:      agentName + "-server",
				Namespace: testNS,
			}
			Eventually(func() int {
				var deployment appsv1.Deployment
				if err := k8sClient.Get(ctx, deploymentKey, &deployment); err != nil {
					return 0
				}
				if len(deployment.Spec.Template.Spec.Containers) == 0 {
					return 0
				}
				return len(deployment.Spec.Template.Spec.Containers[0].Ports)
			}, timeout, interval).Should(Equal(3)) // http + webapp + vscode

			By("Verifying Deployment container port details")
			var deployment appsv1.Deployment
			Expect(k8sClient.Get(ctx, deploymentKey, &deployment)).Should(Succeed())
			ports := deployment.Spec.Template.Spec.Containers[0].Ports
			Expect(ports[0].Name).To(Equal("http"))
			Expect(ports[0].ContainerPort).To(Equal(int32(4096)))
			Expect(ports[1].Name).To(Equal("webapp"))
			Expect(ports[1].ContainerPort).To(Equal(int32(3000)))
			Expect(ports[2].Name).To(Equal("vscode"))
			Expect(ports[2].ContainerPort).To(Equal(int32(8080)))

			By("Verifying Service has extra ports")
			serviceKey := types.NamespacedName{
				Name:      agentName,
				Namespace: testNS,
			}
			var service corev1.Service
			Eventually(func() int {
				if err := k8sClient.Get(ctx, serviceKey, &service); err != nil {
					return 0
				}
				return len(service.Spec.Ports)
			}, timeout, interval).Should(Equal(3))

			Expect(service.Spec.Ports[0].Name).To(Equal("http"))
			Expect(service.Spec.Ports[0].Port).To(Equal(int32(4096)))
			Expect(service.Spec.Ports[1].Name).To(Equal("webapp"))
			Expect(service.Spec.Ports[1].Port).To(Equal(int32(3000)))
			Expect(service.Spec.Ports[2].Name).To(Equal("vscode"))
			Expect(service.Spec.Ports[2].Port).To(Equal(int32(8080)))

			By("Cleaning up")
			Expect(k8sClient.Delete(ctx, agent)).Should(Succeed())
		})
	})
})
