/*
Copyright 2025 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

package e2e

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/adobe/k8s-shredder/pkg/config"
	"github.com/adobe/k8s-shredder/pkg/handler"
	"github.com/adobe/k8s-shredder/pkg/utils"
	"github.com/prometheus/client_golang/api"
	v1 "github.com/prometheus/client_golang/api/prometheus/v1"
	"github.com/prometheus/common/model"
	log "github.com/sirupsen/logrus"
	"golang.org/x/exp/slices"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
)

var (
	// Intentionally skipped the gauge metrics as they are going to be wiped out before every eviction loop
	shredderMetrics = []string{
		"shredder_loops_total",
		"shredder_loops_duration_seconds",
		"shredder_processed_nodes_total",
		"shredder_processed_pods_total",
		"shredder_errors_total",
	}

	// Intentionally skipped the gauge metrics as they are going to be wiped out before every eviction loop
	shredderGaugeMetrics = []string{
		"shredder_pod_errors_total",
		"shredder_node_force_to_evict_time",
		"shredder_pod_force_to_evict_time",
	}

	// Global variables for port-forward management
	prometheusPortForwardCmd *exec.Cmd
	prometheusPort           string
)

// setupPrometheusPortForward starts the Prometheus port-forward and waits for it to be ready
func setupPrometheusPortForward(t *testing.T) error {
	// Determine the correct Prometheus port based on the test environment
	prometheusPort = "30007" // default port for local-test
	kubeconfig := os.Getenv("KUBECONFIG")
	if strings.Contains(kubeconfig, "karpenter") {
		prometheusPort = "30008" // port for local-test-karpenter
	} else if strings.Contains(kubeconfig, "node-labels") {
		prometheusPort = "30009" // port for local-test-node-labels
	}

	// Kill any existing port-forward for this port
	killCmd := exec.Command("pkill", "-f", fmt.Sprintf("kubectl port-forward.*%s", prometheusPort))
	if err := killCmd.Run(); err != nil {
		// Ignore errors as there might not be any process to kill
		t.Logf("Note: No existing port-forward process found to kill: %v", err)
	}

	// Start port-forward for Prometheus
	cmd := exec.Command("kubectl", "port-forward", "-n", "kube-system", "svc/prometheus",
		fmt.Sprintf("%s:9090", prometheusPort), "--kubeconfig", kubeconfig)

	// Redirect output to avoid cluttering test output
	cmd.Stdout = nil
	cmd.Stderr = nil

	if err := cmd.Start(); err != nil {
		return fmt.Errorf("failed to start port-forward: %v", err)
	}

	prometheusPortForwardCmd = cmd
	t.Logf("Started Prometheus port-forward on port %s", prometheusPort)

	// Wait for port-forward to be ready
	retryCount := 0
	maxRetries := 30
	for retryCount < maxRetries {
		// Check if the port is accessible
		resp, err := http.Get(fmt.Sprintf("http://localhost:%s/-/ready", prometheusPort))
		if err == nil && resp.StatusCode == 200 {
			if closeErr := resp.Body.Close(); closeErr != nil {
				t.Logf("Warning: Failed to close response body: %v", closeErr)
			}
			t.Logf("Prometheus port-forward is ready on port %s", prometheusPort)
			return nil
		}
		if resp != nil {
			if closeErr := resp.Body.Close(); closeErr != nil {
				t.Logf("Warning: Failed to close response body: %v", closeErr)
			}
		}

		time.Sleep(2 * time.Second)
		retryCount++
		t.Logf("Waiting for Prometheus port-forward to be ready... (attempt %d/%d)", retryCount, maxRetries)
	}

	// If we get here, port-forward failed to become ready
	cleanupPrometheusPortForward(t)
	return fmt.Errorf("Prometheus port-forward failed to become ready after %d attempts", maxRetries)
}

// cleanupPrometheusPortForward stops the Prometheus port-forward
func cleanupPrometheusPortForward(t *testing.T) {
	if prometheusPortForwardCmd != nil && prometheusPortForwardCmd.Process != nil {
		t.Logf("Stopping Prometheus port-forward on port %s", prometheusPort)
		if err := prometheusPortForwardCmd.Process.Kill(); err != nil {
			t.Logf("Warning: Failed to kill port-forward process: %v", err)
		}
		if err := prometheusPortForwardCmd.Wait(); err != nil {
			t.Logf("Warning: Failed to wait for port-forward process: %v", err)
		}
		prometheusPortForwardCmd = nil
	}
}

// TestMain sets up and tears down the Prometheus port-forward for all tests
func TestMain(m *testing.M) {
	// Set up port-forward before running tests
	if err := setupPrometheusPortForward(&testing.T{}); err != nil {
		log.Errorf("Failed to setup Prometheus port-forward: %v", err)
		os.Exit(1)
	}

	// Run tests
	code := m.Run()

	// Clean up port-forward after tests
	cleanupPrometheusPortForward(&testing.T{})

	os.Exit(code)
}

func grabMetrics(shredderMetrics []string, t *testing.T) map[string]float64 {
	results := make(map[string]float64)
	warnings := make([]string, 0)

	// First check if Prometheus is accessible with retries
	var err error
	retryCount := 0
	maxRetries := 30 // Reduced retries since port-forward is managed

	for retryCount < maxRetries {
		_, _, err = prometheusQuery("up")
		if err == nil {
			break
		}
		t.Logf("Warning: Prometheus not accessible (attempt %d/%d): %v", retryCount+1, maxRetries, err)
		time.Sleep(time.Second)
		retryCount++
	}

	if err != nil {
		t.Logf("Warning: Prometheus is not accessible after %d retries: %v", maxRetries, err)
		t.Logf("Skipping metrics collection")
		return results
	}

	// If we get here, Prometheus is accessible
	for _, metric := range shredderMetrics {
		value, promWarnings, err := prometheusQuery(metric)
		if err != nil {
			t.Logf("Warning: Failed to query metric %s: %v", metric, err)
			continue
		}
		if len(promWarnings) > 0 {
			warnings = append(warnings, promWarnings...)
		}
		if value != nil {
			// Properly extract float64 from model.Vector
			if vector, ok := value.(model.Vector); ok && len(vector) > 0 {
				results[metric] = float64(vector[0].Value)
			}
		}
	}

	if len(warnings) > 0 {
		t.Logf("Warnings while collecting metrics: %v", warnings)
	}

	t.Logf("Collected metrics: %v", results)
	return results
}

func prometheusQuery(query string) (model.Value, v1.Warnings, error) {
	// Use the global prometheusPort variable
	if prometheusPort == "" {
		return nil, nil, fmt.Errorf("Prometheus port not set - port-forward may not be running")
	}

	// Create a new client for each query to avoid connection reuse issues
	client, err := api.NewClient(api.Config{
		Address:      fmt.Sprintf("http://localhost:%s", prometheusPort),
		RoundTripper: api.DefaultRoundTripper,
	})
	if err != nil {
		return nil, nil, fmt.Errorf("error creating Prometheus client: %v", err)
	}

	v1api := v1.NewAPI(client)
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	// Add retry logic for the query
	var result model.Value
	var warnings v1.Warnings
	maxRetries := 3
	for i := 0; i < maxRetries; i++ {
		result, warnings, err = v1api.Query(ctx, query, time.Now(), v1.WithTimeout(5*time.Second))
		if err == nil {
			break
		}
		if i < maxRetries-1 {
			time.Sleep(time.Second)
		}
	}

	if err != nil {
		return nil, warnings, fmt.Errorf("error querying Prometheus after %d retries: %v", maxRetries, err)
	}

	return result, warnings, nil
}

func compareTime(expirationTime time.Time, t *testing.T, ch chan time.Time) {
	currentTime := time.Now().UTC()

	for !currentTime.After(expirationTime.UTC()) {
		t.Logf("Node TTL haven't expired yet: current time(UTC): %s, expire time(UTC): %s", currentTime, expirationTime.UTC())
		time.Sleep(10 * time.Second)
		currentTime = time.Now().UTC()

	}
	ch <- currentTime
}

// getProjectRoot returns the absolute path to the project root directory
func getProjectRoot() (string, error) {
	if envRoot := os.Getenv("PROJECT_ROOT"); envRoot != "" {
		return envRoot, nil
	}
	cwd, err := os.Getwd()
	if err != nil {
		return "", fmt.Errorf("failed to get current working directory: %v", err)
	}
	return cwd, nil
}

// TestEvictionSafetyCheck tests the EvictionSafetyCheck functionality
func TestNodeIsCleanedUp(t *testing.T) {
	var err error

	// Print the KUBECONFIG being used
	t.Logf("Using KUBECONFIG: %s", os.Getenv("KUBECONFIG"))

	appContext, err := utils.NewAppContext(config.Config{
		ParkedNodeTTL:                      30 * time.Second,
		EvictionLoopInterval:               10 * time.Second,
		RollingRestartThreshold:            0.1,
		UpgradeStatusLabel:                 "shredder.ethos.adobe.net/upgrade-status",
		ExpiresOnLabel:                     "shredder.ethos.adobe.net/parked-node-expires-on",
		NamespacePrefixSkipInitialEviction: "",
		RestartedAtAnnotation:              "shredder.ethos.adobe.net/restartedAt",
		AllowEvictionLabel:                 "shredder.ethos.adobe.net/allow-eviction",
		ToBeDeletedTaint:                   "ToBeDeletedByClusterAutoscaler",
		ParkedByLabel:                      "shredder.ethos.adobe.net/parked-by",
		ParkedByValue:                      "k8s-shredder",
		EvictionSafetyCheck:                true, // Re-enable since we now properly label pods
	}, false)

	if err != nil {
		log.Fatalf("Failed to setup application context: %s", err)
	}

	// Determine the correct node name based on the test environment
	parkedWorkerNode := "k8s-shredder-test-cluster-worker" // default
	if strings.Contains(os.Getenv("KUBECONFIG"), "karpenter") {
		parkedWorkerNode = "k8s-shredder-test-cluster-karpenter-worker" // In Karpenter test, we use the worker node
	} else if strings.Contains(os.Getenv("KUBECONFIG"), "node-labels") {
		parkedWorkerNode = "k8s-shredder-test-cluster-node-labels-worker"
	}

	coreV1Client := appContext.K8sClient.CoreV1()
	// Get the first available worker node if the specific node is not found
	node, err := coreV1Client.Nodes().Get(appContext.Context, parkedWorkerNode, metav1.GetOptions{})
	if err != nil {
		// Try to get any worker node
		nodes, err := coreV1Client.Nodes().List(appContext.Context, metav1.ListOptions{
			LabelSelector: "node-role.kubernetes.io/worker=",
		})
		if err != nil || len(nodes.Items) == 0 {
			t.Fatalf("Failed to get any worker node: %s", err)
		}
		node = &nodes.Items[0]
		t.Logf("Using worker node: %s", node.Name)
	}

	h := handler.NewHandler(appContext)

	// Wait for node TTL to expire
	expirationTime, err := utils.GetParkedNodeExpiryTime(*node, appContext.Config.ExpiresOnLabel)
	if err != nil {
		t.Fatalf("Failed to get expiration time for the parked node %s: %s", node.Name, err)
	}
	ch := make(chan time.Time)
	go func() {
		compareTime(expirationTime, t, ch)
	}()
	currentTime := <-ch

	t.Logf("Node TTL expired: current time(UTC): %s, expire time(UTC): %s", currentTime, expirationTime.UTC())

	grabMetrics(append(shredderMetrics, shredderGaugeMetrics...), t)

	// Sleep a bit to let the apiserver catch up
	time.Sleep(10 * time.Second)

	pods, err := h.GetPodsForNode(*node)
	if err != nil {
		t.Fatalf("Failed to get running pods from the parked node %s: %s", node.Name, err)
	}

	userNamespaces := []string{"ns-team-k8s-shredder-test", "ns-k8s-shredder-test"}
	for _, pod := range pods {
		if pod.DeletionTimestamp == nil && slices.Contains(userNamespaces, pod.Namespace) {
			t.Logf("Pod %s was not evicted by shredder", pod.Name)
			t.Fail()
		}
	}
}

// Validates shredder metrics
func TestShredderMetrics(t *testing.T) {
	// Print the KUBECONFIG being used
	t.Logf("Using KUBECONFIG: %s", os.Getenv("KUBECONFIG"))

	// Define the metrics to check
	shredderMetrics := []string{
		"shredder_processed_pods_total",
		"shredder_errors_total",
		"shredder_node_force_to_evict_time",
		"shredder_node_label_nodes_parked_total",
		"shredder_node_label_nodes_parking_failed_total",
		"shredder_node_label_processing_duration_seconds",
		"shredder_node_label_matching_nodes_total",
	}

	// Check if we're running in a node labels test environment
	kubeconfig := os.Getenv("KUBECONFIG")
	if strings.Contains(kubeconfig, "node-labels") {
		// Create app context with node label detection enabled
		appContext, err := utils.NewAppContext(config.Config{
			ParkedNodeTTL:                      30 * time.Second,
			EvictionLoopInterval:               10 * time.Second,
			RollingRestartThreshold:            0.1,
			UpgradeStatusLabel:                 "shredder.ethos.adobe.net/upgrade-status",
			ExpiresOnLabel:                     "shredder.ethos.adobe.net/parked-node-expires-on",
			NamespacePrefixSkipInitialEviction: "",
			RestartedAtAnnotation:              "shredder.ethos.adobe.net/restartedAt",
			AllowEvictionLabel:                 "shredder.ethos.adobe.net/allow-eviction",
			ToBeDeletedTaint:                   "ToBeDeletedByClusterAutoscaler",
			ParkedByLabel:                      "shredder.ethos.adobe.net/parked-by",
			ParkedByValue:                      "k8s-shredder",
			EnableNodeLabelDetection:           true,
			NodeLabelsToDetect:                 []string{"test-label", "maintenance=scheduled"},
		}, false)

		if err != nil {
			log.Fatalf("Failed to setup application context: %s", err)
		}

		// Process nodes with labels
		err = utils.ProcessNodesWithLabels(appContext.Context, appContext, log.WithField("test", "TestShredderMetrics"))
		if err != nil {
			t.Fatalf("Failed to process nodes with labels: %s", err)
		}
	}

	// Check the metrics
	results := grabMetrics(shredderMetrics, t)

	// Verify that we have at least some metrics
	if len(results) == 0 {
		t.Fatal("No metrics found")
	}

	// Log the metrics we found
	for metric, value := range results {
		t.Logf("Found metric %s: %v", metric, value)
	}
}

func TestArgoRolloutRestartAt(t *testing.T) {
	// Print the KUBECONFIG being used
	t.Logf("Using KUBECONFIG: %s", os.Getenv("KUBECONFIG"))

	var err error

	appContext, err := utils.NewAppContext(config.Config{
		ParkedNodeTTL:                      30 * time.Second,
		EvictionLoopInterval:               10 * time.Second,
		RollingRestartThreshold:            0.1,
		UpgradeStatusLabel:                 "shredder.ethos.adobe.net/upgrade-status",
		ExpiresOnLabel:                     "shredder.ethos.adobe.net/parked-node-expires-on",
		NamespacePrefixSkipInitialEviction: "",
		RestartedAtAnnotation:              "shredder.ethos.adobe.net/restartedAt",
		AllowEvictionLabel:                 "shredder.ethos.adobe.net/allow-eviction",
		ToBeDeletedTaint:                   "ToBeDeletedByClusterAutoscaler",
		ArgoRolloutsAPIVersion:             "v1alpha1",
		ParkedByLabel:                      "shredder.ethos.adobe.net/parked-by",
		ParkedByValue:                      "k8s-shredder",
	}, false)

	if err != nil {
		log.Fatalf("Failed to setup application context: %s", err)
	}

	gvr := schema.GroupVersionResource{
		Group:    "argoproj.io",
		Version:  appContext.Config.ArgoRolloutsAPIVersion,
		Resource: "rollouts",
	}

	// Wait for up to 2 minutes for the restartAt field to be set
	timeout := time.After(2 * time.Minute)
	tick := time.Tick(5 * time.Second)
	for {
		select {
		case <-timeout:
			t.Fatalf("Timed out waiting for Argo Rollout restartAt field to be set")
		case <-tick:
			rollout, err := appContext.DynamicK8SClient.Resource(gvr).Namespace("ns-team-k8s-shredder-test").Get(appContext.Context, "test-app-argo-rollout", metav1.GetOptions{})
			if err != nil {
				log.Fatalf("Failed to get the Argo Rollout object: %s", err)
			}
			restartAt, found, err := unstructured.NestedString(rollout.Object, "spec", "restartAt")
			if err != nil {
				log.Fatalf("Failed to get the Argo Rollout spec.restartAt field: %s", err)
			}
			if found && restartAt != "" {
				t.Logf("Found restartAt field set to: %s", restartAt)
				return
			}
			t.Log("Waiting for restartAt field to be set...")
		}
	}
}

// TestKarpenterMetrics validates Karpenter metrics when drift detection is enabled
func TestKarpenterMetrics(t *testing.T) {
	// Print the KUBECONFIG being used
	t.Logf("Using KUBECONFIG: %s", os.Getenv("KUBECONFIG"))

	// Check if we're running in a Karpenter test environment
	kubeconfig := os.Getenv("KUBECONFIG")
	if !strings.Contains(kubeconfig, "karpenter") {
		t.Skip("Skipping Karpenter metrics test: not running in a Karpenter test environment")
	}

	// Define the Karpenter metrics to check
	karpenterMetrics := []string{
		"shredder_karpenter_drifted_nodes_total",
		"shredder_karpenter_nodes_parked_total",
		"shredder_karpenter_nodes_parking_failed_total",
		"shredder_karpenter_processing_duration_seconds",
		"shredder_nodes_parked_total",
		"shredder_nodes_parking_failed_total",
		"shredder_processing_duration_seconds",
	}

	// Only read the metrics, do not mutate cluster state
	results := grabMetrics(karpenterMetrics, t)

	if len(results) == 0 {
		t.Fatal("No Karpenter metrics found")
	}

	t.Logf("Karpenter metrics found:")
	for metric, value := range results {
		t.Logf("Found metric %s: %v", metric, value)
	}

	if _, ok := results["shredder_karpenter_drifted_nodes_total"]; !ok {
		t.Error("Missing shredder_karpenter_drifted_nodes_total metric")
	}
	if _, ok := results["shredder_karpenter_nodes_parked_total"]; !ok {
		t.Error("Missing shredder_karpenter_nodes_parked_total metric")
	}
	if _, ok := results["shredder_karpenter_nodes_parking_failed_total"]; !ok {
		t.Error("Missing shredder_karpenter_nodes_parking_failed_total metric")
	}
	if _, ok := results["shredder_karpenter_processing_duration_seconds"]; !ok {
		t.Error("Missing shredder_karpenter_processing_duration_seconds metric")
	}

	if driftedTotal, ok := results["shredder_karpenter_drifted_nodes_total"]; ok {
		t.Logf("Total drifted nodes detected (historical): %v", driftedTotal)
	} else {
		t.Error("Missing shredder_karpenter_drifted_nodes_total metric")
	}
	if parkedTotal, ok := results["shredder_karpenter_nodes_parked_total"]; ok {
		t.Logf("Total nodes parked (historical): %v", parkedTotal)
	} else {
		t.Error("Missing shredder_karpenter_nodes_parked_total metric")
	}

	t.Log("Karpenter metrics test completed successfully")
}

// TestNodeLabelMetrics specifically tests the node label detection metrics
func TestNodeLabelMetrics(t *testing.T) {
	// Print the KUBECONFIG being used
	t.Logf("Using KUBECONFIG: %s", os.Getenv("KUBECONFIG"))

	// Check if we're running in a node labels test environment
	kubeconfig := os.Getenv("KUBECONFIG")
	if !strings.Contains(kubeconfig, "node-labels") {
		t.Skip("Skipping node label metrics test: not running in a node labels test environment")
	}

	// Define the node label metrics to check
	nodeLabelMetrics := []string{
		"shredder_node_label_nodes_parked_total",
		"shredder_node_label_nodes_parking_failed_total",
		"shredder_node_label_processing_duration_seconds",
		"shredder_node_label_matching_nodes_total",
		"shredder_nodes_parked_total",
		"shredder_nodes_parking_failed_total",
		"shredder_processing_duration_seconds",
	}

	// Only read the metrics, do not mutate cluster state
	results := grabMetrics(nodeLabelMetrics, t)

	if len(results) == 0 {
		t.Fatal("No node label metrics found")
	}

	t.Logf("Node label metrics found:")
	for metric, value := range results {
		t.Logf("Found metric %s: %v", metric, value)
	}

	if _, ok := results["shredder_node_label_nodes_parked_total"]; !ok {
		t.Error("Missing shredder_node_label_nodes_parked_total metric")
	}
	if _, ok := results["shredder_node_label_matching_nodes_total"]; !ok {
		t.Error("Missing shredder_node_label_matching_nodes_total metric")
	}
	if _, ok := results["shredder_node_label_nodes_parking_failed_total"]; !ok {
		t.Error("Missing shredder_node_label_nodes_parking_failed_total metric")
	}
	if _, ok := results["shredder_node_label_processing_duration_seconds"]; !ok {
		t.Error("Missing shredder_node_label_processing_duration_seconds metric")
	}

	if parkedTotal, ok := results["shredder_node_label_nodes_parked_total"]; ok {
		t.Logf("Total nodes parked (historical): %v", parkedTotal)
	} else {
		t.Error("Missing shredder_node_label_nodes_parked_total metric")
	}
	if matchingNodes, ok := results["shredder_node_label_matching_nodes_total"]; ok {
		t.Logf("Total matching nodes (current): %v", matchingNodes)
	} else {
		t.Error("Missing shredder_node_label_matching_nodes_total metric")
	}

	t.Log("Node label metrics test completed successfully")
}

// TestEvictionSafetyCheck tests the EvictionSafetyCheck functionality
func TestEvictionSafetyCheck(t *testing.T) {
	// Print the KUBECONFIG being used
	t.Logf("Using KUBECONFIG: %s", os.Getenv("KUBECONFIG"))

	// Skip this test if running in Karpenter or node-labels test environments
	// as they have different node structures
	kubeconfig := os.Getenv("KUBECONFIG")
	if strings.Contains(kubeconfig, "karpenter") || strings.Contains(kubeconfig, "node-labels") {
		t.Skip("Skipping EvictionSafetyCheck test: not running in standard test environment")
	}

	// Check if EvictionSafetyCheck is enabled in the k8s-shredder configuration
	t.Log("Checking EvictionSafetyCheck configuration...")
	checkConfigCmd := exec.Command("kubectl", "get", "configmap", "k8s-shredder-config", "-n", "kube-system", "-o", "json")
	checkConfigCmd.Env = append(os.Environ(), fmt.Sprintf("KUBECONFIG=%s", kubeconfig))
	configOutput, err := checkConfigCmd.Output()
	if err != nil {
		// Get the stderr output for debugging
		if exitErr, ok := err.(*exec.ExitError); ok {
			t.Logf("kubectl stderr: %s", string(exitErr.Stderr))
		}
		t.Skipf("Skipping EvictionSafetyCheck test: failed to get k8s-shredder config: %v", err)
	}

	var configMap struct {
		Data struct {
			ConfigYaml string `json:"config.yaml"`
		} `json:"data"`
	}

	if err := json.Unmarshal(configOutput, &configMap); err != nil {
		t.Skipf("Skipping EvictionSafetyCheck test: failed to parse configmap: %v", err)
	}

	// Parse the YAML config to check EvictionSafetyCheck setting
	if !strings.Contains(configMap.Data.ConfigYaml, "EvictionSafetyCheck: true") {
		t.Skip("Skipping EvictionSafetyCheck test: EvictionSafetyCheck is disabled in k8s-shredder configuration")
	}

	t.Log("EvictionSafetyCheck is enabled, proceeding with test")

	// Step 1: Scale k8s-shredder replicas to zero to disable actions
	t.Log("Step 1: Scaling k8s-shredder replicas to zero")
	scaleCmd := exec.Command("kubectl", "scale", "deployment", "k8s-shredder", "--replicas=0", "-n", "kube-system")
	scaleCmd.Env = append(os.Environ(), fmt.Sprintf("KUBECONFIG=%s", kubeconfig))
	if err := scaleCmd.Run(); err != nil {
		t.Fatalf("Failed to scale k8s-shredder to zero: %v", err)
	}

	// Wait for k8s-shredder to scale down
	t.Log("Waiting for k8s-shredder to scale down...")
	time.Sleep(10 * time.Second)

	// Step 2: Create a PodDisruptionBudget to protect the unlabeled pod from soft eviction
	t.Log("Step 2: Creating PodDisruptionBudget to prevent soft eviction of the unlabeled pod")

	// Create a PDB manifest that blocks eviction of the specific pod
	pdbManifest := `apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: test-pdb-unlabeled-pod
  namespace: ns-k8s-shredder-test
spec:
  minAvailable: 10
  selector:
    matchLabels:
      test-pod: "true"
`

	// Create the PDB using kubectl
	createPdbCmd := exec.Command("kubectl", "apply", "-f", "-")
	createPdbCmd.Env = append(os.Environ(), fmt.Sprintf("KUBECONFIG=%s", kubeconfig))
	createPdbCmd.Stdin = strings.NewReader(pdbManifest)
	if err := createPdbCmd.Run(); err != nil {
		t.Fatalf("Failed to create PodDisruptionBudget: %v", err)
	}

	t.Log("Created PodDisruptionBudget to protect the unlabeled pod from soft eviction")

	// Step 3: Create a new pod without proper parking labels on worker2 (before parking)
	// Step 3: Park the worker2 node first
	t.Log("Step 3: Parking worker2 node")

	worker2Node := "k8s-shredder-test-cluster-worker2"

	// Get the project root to locate the park-node binary
	projectRoot, err := getProjectRoot()
	if err != nil {
		t.Fatalf("Failed to get project root: %v", err)
	}
	parkNodePath := filepath.Join(projectRoot, "park-node")

	parkCmd := exec.Command(parkNodePath, "-node", worker2Node, "-park-kubeconfig", kubeconfig)
	if err := parkCmd.Run(); err != nil {
		t.Fatalf("Failed to park worker2 node: %v", err)
	}

	// Wait a moment for the parking to complete
	time.Sleep(5 * time.Second)

	// Step 4: Create a new pod without proper parking labels on the parked node
	t.Log("Step 4: Creating a new pod without proper parking labels on worker2")

	// Create a pod manifest for a pod without proper parking labels
	podManifest := fmt.Sprintf(`apiVersion: v1
kind: Pod
metadata:
  name: test-pod-no-labels
  namespace: ns-k8s-shredder-test
  labels:
    eviction-safety-test: "true"
    test-pod: "true"
    shredder.ethos.adobe.net/allow-eviction: "false"
spec:
  nodeName: %s
  containers:
  - name: nginx
    image: nginx:alpine
    ports:
    - containerPort: 80
`, worker2Node)

	// Create the pod using kubectl
	createPodCmd := exec.Command("kubectl", "apply", "-f", "-")
	createPodCmd.Env = append(os.Environ(), fmt.Sprintf("KUBECONFIG=%s", kubeconfig))
	createPodCmd.Stdin = strings.NewReader(podManifest)
	if err := createPodCmd.Run(); err != nil {
		t.Fatalf("Failed to create pod without labels: %v", err)
	}

	// Wait a moment for the pod to be scheduled
	time.Sleep(5 * time.Second)

	t.Log("Created pod without proper parking labels on worker2")

	// Step 5: Scale k8s-shredder replicas to 1 to start the test
	t.Log("Step 5: Scaling k8s-shredder replicas to 1")
	scaleUpCmd := exec.Command("kubectl", "scale", "deployment", "k8s-shredder", "--replicas=1", "-n", "kube-system")
	scaleUpCmd.Env = append(os.Environ(), fmt.Sprintf("KUBECONFIG=%s", kubeconfig))
	if err := scaleUpCmd.Run(); err != nil {
		t.Fatalf("Failed to scale k8s-shredder to 1: %v", err)
	}

	// Wait for k8s-shredder to be ready
	t.Log("Waiting for k8s-shredder to be ready...")
	time.Sleep(15 * time.Second)

	// Step 6: Monitor worker2 parking status; it should eventually be unparked
	t.Log("Step 6: Monitoring worker2 parking status - it should be unparked due to safety check failure")

	// Wait for up to 2 minutes for the node to be unparked
	timeout := time.After(2 * time.Minute)
	tick := time.Tick(5 * time.Second)

	for {
		select {
		case <-timeout:
			t.Fatalf("Timed out waiting for worker2 to be unparked")
		case <-tick:
			// Check the node's parking status
			getNodeCmd := exec.Command("kubectl", "get", "node", worker2Node, "-o", "json")
			getNodeCmd.Env = append(os.Environ(), fmt.Sprintf("KUBECONFIG=%s", kubeconfig))
			nodeOutput, err := getNodeCmd.Output()
			if err != nil {
				t.Logf("Failed to get node status: %v", err)
				continue
			}

			var node struct {
				Metadata struct {
					Labels map[string]string `json:"labels"`
				} `json:"metadata"`
			}

			if err := json.Unmarshal(nodeOutput, &node); err != nil {
				t.Logf("Failed to parse node JSON: %v", err)
				continue
			}

			upgradeStatus, exists := node.Metadata.Labels["shredder.ethos.adobe.net/upgrade-status"]
			if !exists {
				t.Log("Node has no upgrade-status label")
				continue
			}

			t.Logf("Current node status: %s", upgradeStatus)

			switch upgradeStatus {
			case "unparked":
				t.Log("SUCCESS: Worker2 node was unparked due to EvictionSafetyCheck failure")
				return
			case "parked":
				t.Log("Node is still parked, waiting...")
			default:
				t.Logf("Node has unexpected status: %s", upgradeStatus)
			}
		}
	}
}

// TestEvictionSafetyCheckPasses tests the EvictionSafetyCheck functionality when all pods are properly labeled
func TestEvictionSafetyCheckPasses(t *testing.T) {
	// Print the KUBECONFIG being used
	t.Logf("Using KUBECONFIG: %s", os.Getenv("KUBECONFIG"))

	// Skip this test if running in Karpenter or node-labels test environments
	// as they have different node structures
	kubeconfig := os.Getenv("KUBECONFIG")
	if strings.Contains(kubeconfig, "karpenter") || strings.Contains(kubeconfig, "node-labels") {
		t.Skip("Skipping EvictionSafetyCheckPasses test: not running in standard test environment")
	}

	// Check if EvictionSafetyCheck is enabled in the k8s-shredder configuration
	t.Log("Checking EvictionSafetyCheck configuration...")
	checkConfigCmd := exec.Command("kubectl", "get", "configmap", "k8s-shredder-config", "-n", "kube-system", "-o", "json")
	checkConfigCmd.Env = append(os.Environ(), fmt.Sprintf("KUBECONFIG=%s", kubeconfig))
	configOutput, err := checkConfigCmd.Output()
	if err != nil {
		// Get the stderr output for debugging
		if exitErr, ok := err.(*exec.ExitError); ok {
			t.Logf("kubectl stderr: %s", string(exitErr.Stderr))
		}
		t.Skipf("Skipping EvictionSafetyCheckPasses test: failed to get k8s-shredder config: %v", err)
	}

	var configMap struct {
		Data struct {
			ConfigYaml string `json:"config.yaml"`
		} `json:"data"`
	}

	if err := json.Unmarshal(configOutput, &configMap); err != nil {
		t.Skipf("Skipping EvictionSafetyCheckPasses test: failed to parse configmap: %v", err)
	}

	// Parse the YAML config to check EvictionSafetyCheck setting
	if !strings.Contains(configMap.Data.ConfigYaml, "EvictionSafetyCheck: true") {
		t.Skip("Skipping EvictionSafetyCheckPasses test: EvictionSafetyCheck is disabled in k8s-shredder configuration")
	}

	t.Log("EvictionSafetyCheck is enabled, proceeding with test")

	// Step 1: Scale k8s-shredder replicas to zero to disable actions
	t.Log("Step 1: Scaling k8s-shredder replicas to zero")
	scaleCmd := exec.Command("kubectl", "scale", "deployment", "k8s-shredder", "--replicas=0", "-n", "kube-system")
	scaleCmd.Env = append(os.Environ(), fmt.Sprintf("KUBECONFIG=%s", kubeconfig))
	if err := scaleCmd.Run(); err != nil {
		t.Fatalf("Failed to scale k8s-shredder to zero: %v", err)
	}

	// Wait for k8s-shredder to scale down
	t.Log("Waiting for k8s-shredder to scale down...")
	time.Sleep(10 * time.Second)

	// Step 2: Park the worker2 node and all pods on it (this will properly label all pods)
	t.Log("Step 2: Parking worker2 node with proper pod labeling")
	worker2Node := "k8s-shredder-test-cluster-worker2"

	// Get the project root to locate the park-node binary
	projectRoot, err := getProjectRoot()
	if err != nil {
		t.Fatalf("Failed to get project root: %v", err)
	}
	parkNodePath := filepath.Join(projectRoot, "park-node")

	parkCmd := exec.Command(parkNodePath, "-node", worker2Node, "-park-kubeconfig", kubeconfig)
	if err := parkCmd.Run(); err != nil {
		t.Fatalf("Failed to park worker2 node: %v", err)
	}

	// Wait a moment for the parking to complete
	time.Sleep(5 * time.Second)

	// Step 3: Scale k8s-shredder replicas to 1 to start the test
	t.Log("Step 3: Scaling k8s-shredder replicas to 1")
	scaleUpCmd := exec.Command("kubectl", "scale", "deployment", "k8s-shredder", "--replicas=1", "-n", "kube-system")
	scaleUpCmd.Env = append(os.Environ(), fmt.Sprintf("KUBECONFIG=%s", kubeconfig))
	if err := scaleUpCmd.Run(); err != nil {
		t.Fatalf("Failed to scale k8s-shredder to 1: %v", err)
	}

	// Wait for k8s-shredder to be ready
	t.Log("Waiting for k8s-shredder to be ready...")
	time.Sleep(15 * time.Second)

	// Step 4: Monitor worker2 parking status; it should remain parked until TTL expires, then get force evicted
	t.Log("Step 4: Monitoring worker2 parking status - it should remain parked until TTL expires")

	// Wait for up to 2 minutes for the node to be processed
	timeout := time.After(2 * time.Minute)
	tick := time.Tick(5 * time.Second)

	for {
		select {
		case <-timeout:
			t.Fatalf("Timed out waiting for worker2 to be processed")
		case <-tick:
			// Check the node's parking status
			getNodeCmd := exec.Command("kubectl", "get", "node", worker2Node, "-o", "json")
			getNodeCmd.Env = append(os.Environ(), fmt.Sprintf("KUBECONFIG=%s", kubeconfig))
			nodeOutput, err := getNodeCmd.Output()
			if err != nil {
				t.Logf("Failed to get node status: %v", err)
				continue
			}

			var node struct {
				Metadata struct {
					Labels map[string]string `json:"labels"`
				} `json:"metadata"`
			}

			if err := json.Unmarshal(nodeOutput, &node); err != nil {
				t.Logf("Failed to parse node JSON: %v", err)
				continue
			}

			upgradeStatus, exists := node.Metadata.Labels["shredder.ethos.adobe.net/upgrade-status"]
			if !exists {
				t.Log("Node has no upgrade-status label")
				continue
			}

			t.Logf("Current node status: %s", upgradeStatus)

			switch upgradeStatus {
			case "unparked":
				t.Log("SUCCESS: Worker2 node was unparked (this might happen if TTL expired and safety check passed)")
				return
			case "parked":
				t.Log("Node is still parked, waiting...")
			default:
				t.Logf("Node has unexpected status: %s", upgradeStatus)
			}

			// Also check if pods are being evicted (which would indicate force eviction is happening)
			getPodsCmd := exec.Command("kubectl", "get", "pods", "-A", "--field-selector", fmt.Sprintf("spec.nodeName=%s", worker2Node), "-o", "json")
			getPodsCmd.Env = append(os.Environ(), fmt.Sprintf("KUBECONFIG=%s", kubeconfig))
			podsOutput, err := getPodsCmd.Output()
			if err != nil {
				t.Logf("Failed to get pods: %v", err)
				continue
			}

			var podsList struct {
				Items []struct {
					Metadata struct {
						Name      string `json:"name"`
						Namespace string `json:"namespace"`
					} `json:"metadata"`
					Status struct {
						Phase string `json:"phase"`
					} `json:"status"`
				} `json:"items"`
			}

			if err := json.Unmarshal(podsOutput, &podsList); err != nil {
				t.Logf("Failed to parse pods JSON: %v", err)
				continue
			}

			// Count non-kube-system pods (user pods that should be evicted)
			userPodCount := 0
			for _, pod := range podsList.Items {
				if pod.Metadata.Namespace != "kube-system" {
					userPodCount++
				}
			}

			if userPodCount == 0 {
				t.Log("SUCCESS: All user pods have been evicted from worker2 (force eviction worked)")
				return
			} else {
				t.Logf("Still %d user pods on worker2, waiting...", userPodCount)
			}
		}
	}
}
