package eks

import (
	"strings"
	"testing"

	"github.com/crossplane-contrib/provider-aws/apis/eks/manualv1alpha1"
	"github.com/stretchr/testify/assert"
	"k8s.io/utils/ptr"
)

func Test_getBottlerocketAdminNodegroupName_MaxLen63(t *testing.T) {
	t.Run("short base name", func(t *testing.T) {
		got := getBottlerocketAdminNodegroupName("admin-ng")
		assert.Equal(t, "admin-ng"+bottlerocketSuffix, got)
		assert.LessOrEqual(t, len(got), 63)
	})

	t.Run("already suffixed stays suffixed", func(t *testing.T) {
		in := "admin-ng" + bottlerocketSuffix
		got := getBottlerocketAdminNodegroupName(in)
		assert.Equal(t, in, got)
		assert.LessOrEqual(t, len(got), 63)
	})

	t.Run("long base is truncated to fit within 63", func(t *testing.T) {
		// Base longer than max allowed (63 - len(bottlerocketSuffix) = 59)
		base := strings.Repeat("a", 60)
		got := getBottlerocketAdminNodegroupName(base)
		assert.True(t, strings.HasSuffix(got, bottlerocketSuffix), got)
		assert.Equal(t, 63, len(got))
	})

	t.Run("long base without hash tail is truncated to fit", func(t *testing.T) {
		base := strings.Repeat("b", 63)
		got := getBottlerocketAdminNodegroupName(base)
		assert.True(t, strings.HasSuffix(got, bottlerocketSuffix), got)
		assert.LessOrEqual(t, len(got), 63)
	})

	t.Run("already suffixed but too long is trimmed to fit", func(t *testing.T) {
		base := strings.Repeat("c", 63) + bottlerocketSuffix
		got := getBottlerocketAdminNodegroupName(base)
		assert.True(t, strings.HasSuffix(got, bottlerocketSuffix), got)
		assert.LessOrEqual(t, len(got), 63)
	})
}

func Test_setNodeGroupUpdateConfig(t *testing.T) {
	tests := []struct {
		name           string
		nodeGroup      *manualv1alpha1.NodeGroup
		options        map[string]string
		wantNeedUpdate bool
		wantNodeGroup  *manualv1alpha1.NodeGroup
	}{
		{
			name: "no maxUnavailablePercentage option",
			nodeGroup: &manualv1alpha1.NodeGroup{
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(10)),
						},
					},
				},
			},
			options:        map[string]string{},
			wantNeedUpdate: false,
			wantNodeGroup: &manualv1alpha1.NodeGroup{
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(10)),
						},
					},
				},
			},
		},
		{
			name: "empty maxUnavailablePercentage value",
			nodeGroup: &manualv1alpha1.NodeGroup{
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(10)),
						},
					},
				},
			},
			options: map[string]string{
				maxUnavailablePercentage: "",
			},
			wantNeedUpdate: false,
			wantNodeGroup: &manualv1alpha1.NodeGroup{
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(10)),
						},
					},
				},
			},
		},
		{
			name: "zero node count",
			nodeGroup: &manualv1alpha1.NodeGroup{
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(0)),
						},
					},
				},
			},
			options: map[string]string{
				maxUnavailablePercentage: "25",
			},
			wantNeedUpdate: false,
			wantNodeGroup: &manualv1alpha1.NodeGroup{
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(0)),
						},
					},
				},
			},
		},
		{
			name: "nil desired size",
			nodeGroup: &manualv1alpha1.NodeGroup{
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: nil,
						},
					},
				},
			},
			options: map[string]string{
				maxUnavailablePercentage: "25",
			},
			wantNeedUpdate: false,
			wantNodeGroup: &manualv1alpha1.NodeGroup{
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: nil,
						},
					},
				},
			},
		},
		{
			name: "invalid percentage value",
			nodeGroup: &manualv1alpha1.NodeGroup{
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(10)),
						},
					},
				},
			},
			options: map[string]string{
				maxUnavailablePercentage: "invalid",
			},
			wantNeedUpdate: false,
			wantNodeGroup: &manualv1alpha1.NodeGroup{
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(10)),
						},
					},
				},
			},
		},
		{
			name: "percentage too low (0)",
			nodeGroup: &manualv1alpha1.NodeGroup{
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(10)),
						},
					},
				},
			},
			options: map[string]string{
				maxUnavailablePercentage: "0",
			},
			wantNeedUpdate: false,
			wantNodeGroup: &manualv1alpha1.NodeGroup{
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(10)),
						},
					},
				},
			},
		},
		{
			name: "percentage too high (101)",
			nodeGroup: &manualv1alpha1.NodeGroup{
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(10)),
						},
					},
				},
			},
			options: map[string]string{
				maxUnavailablePercentage: "101",
			},
			wantNeedUpdate: false,
			wantNodeGroup: &manualv1alpha1.NodeGroup{
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(10)),
						},
					},
				},
			},
		},
		{
			name: "nil UpdateConfig",
			nodeGroup: &manualv1alpha1.NodeGroup{
				Spec: manualv1alpha1.NodeGroupSpec{
					ForProvider: manualv1alpha1.NodeGroupParameters{
						UpdateConfig: nil,
					},
				},
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(10)),
						},
					},
				},
			},
			options: map[string]string{
				maxUnavailablePercentage: "25",
			},
			wantNeedUpdate: true,
			wantNodeGroup: &manualv1alpha1.NodeGroup{
				Spec: manualv1alpha1.NodeGroupSpec{
					ForProvider: manualv1alpha1.NodeGroupParameters{
						UpdateConfig: &manualv1alpha1.NodeGroupUpdateConfig{
							MaxUnavailable: ptr.To(int32(2)), // 25% of 10 = 2.5, truncated to 2
						},
					},
				},
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(10)),
						},
					},
				},
			},
		},
		{
			name: "existing UpdateConfig with different MaxUnavailable",
			nodeGroup: &manualv1alpha1.NodeGroup{
				Spec: manualv1alpha1.NodeGroupSpec{
					ForProvider: manualv1alpha1.NodeGroupParameters{
						UpdateConfig: &manualv1alpha1.NodeGroupUpdateConfig{
							MaxUnavailable: ptr.To(int32(1)),
						},
					},
				},
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(10)),
						},
					},
				},
			},
			options: map[string]string{
				maxUnavailablePercentage: "25",
			},
			wantNeedUpdate: true,
			wantNodeGroup: &manualv1alpha1.NodeGroup{
				Spec: manualv1alpha1.NodeGroupSpec{
					ForProvider: manualv1alpha1.NodeGroupParameters{
						UpdateConfig: &manualv1alpha1.NodeGroupUpdateConfig{
							MaxUnavailable: ptr.To(int32(2)), // 25% of 10 = 2.5, truncated to 2
						},
					},
				},
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(10)),
						},
					},
				},
			},
		},
		{
			name: "existing UpdateConfig with same MaxUnavailable",
			nodeGroup: &manualv1alpha1.NodeGroup{
				Spec: manualv1alpha1.NodeGroupSpec{
					ForProvider: manualv1alpha1.NodeGroupParameters{
						UpdateConfig: &manualv1alpha1.NodeGroupUpdateConfig{
							MaxUnavailable: ptr.To(int32(2)),
						},
					},
				},
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(10)),
						},
					},
				},
			},
			options: map[string]string{
				maxUnavailablePercentage: "25", // 25% of 10 = 2.5, truncated to 2
			},
			wantNeedUpdate: false, // No update needed as the value is already set correctly
			wantNodeGroup: &manualv1alpha1.NodeGroup{
				Spec: manualv1alpha1.NodeGroupSpec{
					ForProvider: manualv1alpha1.NodeGroupParameters{
						UpdateConfig: &manualv1alpha1.NodeGroupUpdateConfig{
							MaxUnavailable: ptr.To(int32(2)),
						},
					},
				},
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(10)),
						},
					},
				},
			},
		},
		{
			name: "fractional percentage calculation",
			nodeGroup: &manualv1alpha1.NodeGroup{
				Spec: manualv1alpha1.NodeGroupSpec{
					ForProvider: manualv1alpha1.NodeGroupParameters{
						UpdateConfig: &manualv1alpha1.NodeGroupUpdateConfig{
							MaxUnavailable: ptr.To(int32(1)),
						},
					},
				},
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(3)),
						},
					},
				},
			},
			options: map[string]string{
				maxUnavailablePercentage: "33.33", // 33.33% of 3 = 0.9999, truncated to 0
			},
			wantNeedUpdate: true,
			wantNodeGroup: &manualv1alpha1.NodeGroup{
				Spec: manualv1alpha1.NodeGroupSpec{
					ForProvider: manualv1alpha1.NodeGroupParameters{
						UpdateConfig: &manualv1alpha1.NodeGroupUpdateConfig{
							MaxUnavailable: ptr.To(int32(0)), // 33.33% of 3 = 0.9999, truncated to 0
						},
					},
				},
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(3)),
						},
					},
				},
			},
		},
		{
			name: "100% percentage",
			nodeGroup: &manualv1alpha1.NodeGroup{
				Spec: manualv1alpha1.NodeGroupSpec{
					ForProvider: manualv1alpha1.NodeGroupParameters{
						UpdateConfig: &manualv1alpha1.NodeGroupUpdateConfig{
							MaxUnavailable: ptr.To(int32(1)),
						},
					},
				},
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(5)),
						},
					},
				},
			},
			options: map[string]string{
				maxUnavailablePercentage: "100",
			},
			wantNeedUpdate: true,
			wantNodeGroup: &manualv1alpha1.NodeGroup{
				Spec: manualv1alpha1.NodeGroupSpec{
					ForProvider: manualv1alpha1.NodeGroupParameters{
						UpdateConfig: &manualv1alpha1.NodeGroupUpdateConfig{
							MaxUnavailable: ptr.To(int32(5)), // 100% of 5 = 5
						},
					},
				},
				Status: manualv1alpha1.NodeGroupStatus{
					AtProvider: manualv1alpha1.NodeGroupObservation{
						ScalingConfig: manualv1alpha1.NodeGroupScalingConfigStatus{
							DesiredSize: ptr.To(int32(5)),
						},
					},
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create a deep copy of the nodeGroup to avoid modifying the test case
			nodeGroup := tt.nodeGroup.DeepCopy()

			// Call the function
			gotNeedUpdate := setNodeGroupUpdateConfig(nodeGroup, tt.options)

			// Check if the needUpdate return value is as expected
			assert.Equal(t, tt.wantNeedUpdate, gotNeedUpdate, "setNodeGroupUpdateConfig() needUpdate = %v, want %v", gotNeedUpdate, tt.wantNeedUpdate)

			// Check if the nodeGroup was modified as expected
			assert.Equal(t, tt.wantNodeGroup, nodeGroup, "setNodeGroupUpdateConfig() modified nodeGroup incorrectly")
		})
	}
}
