//go:build e2e
// +build e2e

package e2e

import (
	"context"
	"fmt"
	"os"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/wait"
	karpv1 "sigs.k8s.io/karpenter/pkg/apis/v1"

	"github.com/pfeifferj/karpenter-provider-ibm-cloud/pkg/apis/v1alpha1"
)

const (
	simpleTestTimeout = 8 * time.Minute
	pollInterval      = 15 * time.Second
)

func TestE2ESimpleSmokeTest(t *testing.T) {
	// Skip if not running E2E tests
	if os.Getenv("RUN_E2E_TESTS") != "true" {
		t.Skip("Skipping E2E tests - set RUN_E2E_TESTS=true to run")
	}

	suite := setupSimpleTestSuite(t)
	ctx := context.Background()
	testName := fmt.Sprintf("simple-smoke-%d", time.Now().Unix())

	t.Logf("🚀 Starting simple smoke test: %s", testName)

	// Step 1: Create NodeClass
	t.Logf("📝 Step 1: Creating NodeClass...")
	nodeClass := createSimpleNodeClass(t, suite, testName)
	t.Logf("✅ NodeClass created: %s", nodeClass.Name)

	// Step 2: Wait for NodeClass to be ready
	t.Logf("⏳ Step 2: Waiting for NodeClass to be ready...")
	waitForNodeClassReady(t, suite, nodeClass.Name)
	t.Logf("✅ NodeClass is ready")

	// Step 3: Create NodePool
	t.Logf("📝 Step 3: Creating NodePool...")
	nodePool := createSimpleNodePool(t, suite, testName, nodeClass.Name)
	t.Logf("✅ NodePool created: %s", nodePool.Name)

	// Step 4: Create test workload
	t.Logf("📝 Step 4: Creating test workload...")
	deployment := createSimpleWorkload(t, suite, testName)
	t.Logf("✅ Workload created: %s", deployment.Name)

	// Step 5: Wait for pod to be scheduled
	t.Logf("⏳ Step 5: Waiting for pod to be scheduled...")
	waitForPodScheduled(t, suite, deployment.Name)
	t.Logf("✅ Pod scheduled successfully")

	// Step 6: Verify node was created by Karpenter
	t.Logf("🔍 Step 6: Verifying Karpenter node exists...")
	verifyKarpenterNode(t, suite, testName)
	t.Logf("✅ Karpenter node verified")

	// Step 7: Simple cleanup
	t.Logf("🧹 Step 7: Cleaning up...")
	simpleCleanup(t, suite, ctx, testName, deployment, nodePool, nodeClass)
	t.Logf("✅ Cleanup completed")

	t.Logf("🎉 Simple smoke test completed successfully: %s", testName)
}

func createSimpleNodeClass(t *testing.T, suite *E2ETestSuite, testName string) *v1alpha1.IBMNodeClass {
	nodeClass := &v1alpha1.IBMNodeClass{
		ObjectMeta: metav1.ObjectMeta{
			Name: fmt.Sprintf("%s-nc", testName),
			Labels: map[string]string{
				"test": "simple-e2e",
			},
		},
		Spec: v1alpha1.IBMNodeClassSpec{
			InstanceProfile:   "bx2-2x8",
			VPC:               os.Getenv("TEST_VPC_ID"),
			Subnet:            os.Getenv("TEST_SUBNET_ID"),
			Zone:              os.Getenv("TEST_ZONE"),
			Region:            os.Getenv("IBMCLOUD_REGION"),
			SecurityGroups:    []string{os.Getenv("TEST_SECURITY_GROUP_ID")},
			ResourceGroup:     os.Getenv("IBM_RESOURCE_GROUP_ID"),
			Image:             os.Getenv("TEST_IMAGE_ID"),
			APIServerEndpoint: os.Getenv("KUBERNETES_API_SERVER_ENDPOINT"),
		},
	}

	err := suite.kubeClient.Create(context.Background(), nodeClass)
	require.NoError(t, err)
	return nodeClass
}

func createSimpleNodePool(t *testing.T, suite *E2ETestSuite, testName, nodeClassName string) *karpv1.NodePool {
	nodePool := &karpv1.NodePool{
		ObjectMeta: metav1.ObjectMeta{
			Name: fmt.Sprintf("%s-np", testName),
			Labels: map[string]string{
				"test": "simple-e2e",
			},
		},
		Spec: karpv1.NodePoolSpec{
			Template: karpv1.NodeClaimTemplate{
				Spec: karpv1.NodeClaimTemplateSpec{
					NodeClassRef: &karpv1.NodeClassReference{
						Group: "karpenter.ibm.sh",
						Kind:  "IBMNodeClass",
						Name:  nodeClassName,
					},
					Requirements: []karpv1.NodeSelectorRequirementWithMinValues{
						{
							NodeSelectorRequirement: corev1.NodeSelectorRequirement{
								Key:      karpv1.CapacityTypeLabelKey,
								Operator: corev1.NodeSelectorOpIn,
								Values:   []string{"on-demand"},
							},
						},
						{
							NodeSelectorRequirement: corev1.NodeSelectorRequirement{
								Key:      corev1.LabelInstanceTypeStable,
								Operator: corev1.NodeSelectorOpIn,
								Values:   []string{"bx2-2x8"},
							},
						},
					},
				},
			},
			Limits: karpv1.Limits{
				corev1.ResourceCPU: resource.MustParse("100"),
			},
		},
	}

	err := suite.kubeClient.Create(context.Background(), nodePool)
	require.NoError(t, err)
	return nodePool
}

func createSimpleWorkload(t *testing.T, suite *E2ETestSuite, testName string) *appsv1.Deployment {
	deployment := &appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:      fmt.Sprintf("%s-app", testName),
			Namespace: "default",
			Labels: map[string]string{
				"test": "simple-e2e",
			},
		},
		Spec: appsv1.DeploymentSpec{
			Replicas: int32Ptr(1),
			Selector: &metav1.LabelSelector{
				MatchLabels: map[string]string{
					"app": fmt.Sprintf("%s-app", testName),
				},
			},
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{
						"app":  fmt.Sprintf("%s-app", testName),
						"test": "simple-e2e",
					},
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name:  "test-container",
							Image: "quay.io/karpenter/pause:latest",
							Resources: corev1.ResourceRequirements{
								Requests: corev1.ResourceList{
									corev1.ResourceCPU:    resource.MustParse("100m"),
									corev1.ResourceMemory: resource.MustParse("128Mi"),
								},
							},
						},
					},
					RestartPolicy: corev1.RestartPolicyAlways,
				},
			},
		},
	}

	err := suite.kubeClient.Create(context.Background(), deployment)
	require.NoError(t, err)
	return deployment
}

func waitForNodeClassReady(t *testing.T, suite *E2ETestSuite, nodeClassName string) {
	ctx, cancel := context.WithTimeout(context.Background(), simpleTestTimeout)
	defer cancel()

	err := wait.PollUntilContextTimeout(ctx, pollInterval, simpleTestTimeout, true, func(ctx context.Context) (bool, error) {
		var nodeClass v1alpha1.IBMNodeClass
		err := suite.kubeClient.Get(ctx, types.NamespacedName{Name: nodeClassName}, &nodeClass)
		if err != nil {
			t.Logf("Failed to get NodeClass: %v", err)
			return false, nil
		}

		// Check if NodeClass is ready
		for _, condition := range nodeClass.Status.Conditions {
			if condition.Type == "Ready" && condition.Status == "True" {
				return true, nil
			}
		}
		return false, nil
	})
	require.NoError(t, err, "NodeClass should become ready within timeout")
}

func waitForPodScheduled(t *testing.T, suite *E2ETestSuite, deploymentName string) {
	ctx, cancel := context.WithTimeout(context.Background(), simpleTestTimeout)
	defer cancel()

	err := wait.PollUntilContextTimeout(ctx, pollInterval, simpleTestTimeout, true, func(ctx context.Context) (bool, error) {
		var deployment appsv1.Deployment
		err := suite.kubeClient.Get(ctx, types.NamespacedName{Name: deploymentName, Namespace: "default"}, &deployment)
		if err != nil {
			return false, nil
		}

		// Check if deployment has ready replicas
		return deployment.Status.ReadyReplicas > 0, nil
	})
	require.NoError(t, err, "Pod should be scheduled within timeout")
}

func verifyKarpenterNode(t *testing.T, suite *E2ETestSuite, testName string) {
	ctx := context.Background()
	var nodeList corev1.NodeList
	err := suite.kubeClient.List(ctx, &nodeList)
	require.NoError(t, err)

	karpenterNodeFound := false
	for _, node := range nodeList.Items {
		if _, exists := node.Labels[karpv1.NodePoolLabelKey]; exists {
			karpenterNodeFound = true
			t.Logf("Found Karpenter node: %s", node.Name)
			break
		}
	}
	require.True(t, karpenterNodeFound, "At least one Karpenter-managed node should exist")
}

func simpleCleanup(t *testing.T, suite *E2ETestSuite, ctx context.Context, testName string, deployment *appsv1.Deployment, nodePool *karpv1.NodePool, nodeClass *v1alpha1.IBMNodeClass) {
	// Delete deployment
	err := suite.kubeClient.Delete(ctx, deployment)
	if err != nil && !errors.IsNotFound(err) {
		t.Logf("Failed to delete deployment: %v", err)
	}

	// Delete nodepool
	err = suite.kubeClient.Delete(ctx, nodePool)
	if err != nil && !errors.IsNotFound(err) {
		t.Logf("Failed to delete nodepool: %v", err)
	}

	// Delete nodeclass
	err = suite.kubeClient.Delete(ctx, nodeClass)
	if err != nil && !errors.IsNotFound(err) {
		t.Logf("Failed to delete nodeclass: %v", err)
	}

	// Wait a moment for deletion to start
	time.Sleep(30 * time.Second)
}

func int32Ptr(i int32) *int32 {
	return &i
}

// setupSimpleTestSuite creates a minimal test suite without complex cleanup/circuit breaker logic
func setupSimpleTestSuite(t *testing.T) *E2ETestSuite {
	// Verify required environment variables
	requiredEnvVars := []string{
		"IBMCLOUD_API_KEY",
		"IBMCLOUD_REGION",
		"TEST_VPC_ID",
		"TEST_SUBNET_ID",
		"TEST_IMAGE_ID",
		"TEST_ZONE",
		"TEST_SECURITY_GROUP_ID",
		"IBM_RESOURCE_GROUP_ID",
	}

	for _, envVar := range requiredEnvVars {
		if os.Getenv(envVar) == "" {
			t.Fatalf("Required environment variable %s is not set", envVar)
		}
	}

	// Set simple mode to skip complex cleanup
	os.Setenv("E2E_SIMPLE_MODE", "true")
	defer os.Unsetenv("E2E_SIMPLE_MODE")

	// Use the existing setup but in simple mode
	suite := SetupE2ETestSuite(t)

	t.Logf("✅ Simple test suite setup completed")
	return suite
}
