package k8s

import (
	"context"
	"testing"

	"github.com/pingcap/tidb-operator/api/v2/core/v1alpha1"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func TestGenerateUnstructuredFromYaml(t *testing.T) {
	tests := []struct {
		name      string
		yamlStr   string
		expectErr bool
	}{
		{
			name: "valid yaml",
			yamlStr: `apiVersion: v1
kind: Pod
metadata:
  name: test-pod
  namespace: default
spec:
  containers:
  - name: nginx
    image: nginx:latest`,
			expectErr: false,
		},
		{
			name: "valid yaml with custom resource",
			yamlStr: `apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: test
  template:
    metadata:
      labels:
        app: test
    spec:
      containers:
      - name: app
        image: test:latest`,
			expectErr: false,
		},
		{
			name:      "empty yaml",
			yamlStr:   "",
			expectErr: true,
		},
		{
			name:      "invalid yaml",
			yamlStr:   "invalid: yaml: content:",
			expectErr: true,
		},
		{
			name: "yaml without apiVersion",
			yamlStr: `kind: Pod
apiVersion: apps/v1
kind: xxx
metadata:
  name: test-pod`,
			expectErr: false,
		},
		{
			name: "yaml without kind",
			yamlStr: `apiVersion: v1
apiVersion: apps/v1
kind: xxx
metadata:
  name: test-pod`,
			expectErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			obj, gvk, err := GenerateUnstructuredFromYaml(tt.yamlStr)

			if tt.expectErr {
				if err == nil {
					t.Error("GenerateUnstructuredFromYaml() expected error but got none")
				}
				return
			}

			if err != nil {
				t.Errorf("GenerateUnstructuredFromYaml() unexpected error: %v", err)
				return
			}

			if obj == nil {
				t.Error("GenerateUnstructuredFromYaml() returned nil object")
				return
			}

			if gvk == nil {
				t.Error("GenerateUnstructuredFromYaml() returned nil GroupVersionKind")
				return
			}

			// Verify that the object has the expected structure
			if obj.GetAPIVersion() == "" {
				t.Error("Generated object should have APIVersion")
			}

			if obj.GetKind() == "" {
				t.Error("Generated object should have Kind")
			}
		})
	}
}

func TestEnsureNamespace(t *testing.T) {
	// This test would require a mock client
	// For now, we'll test the function signature and basic validation
	tests := []struct {
		name        string
		namespace   string
		tenantId    string
		expectError bool
	}{
		{
			name:        "valid namespace and tenant",
			namespace:   "test-namespace",
			tenantId:    "tenant-123",
			expectError: false,
		},
		{
			name:        "empty namespace",
			namespace:   "",
			tenantId:    "tenant-123",
			expectError: true,
		},
		{
			name:        "empty tenant id",
			namespace:   "test-namespace",
			tenantId:    "",
			expectError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// This would require a mock client, so we'll just test the function signature
			_ = EnsureNamespace

			// In a real test, you would:
			// mockClient := &MockClient{}
			// err := EnsureNamespace(context.Background(), mockClient, tt.namespace, tt.tenantId)
			// if tt.expectError && err == nil {
			//     t.Error("EnsureNamespace() expected error but got none")
			// }
		})
	}
}

func TestK8sClientDisablesCacheForSharedPoolPolicyObjects(t *testing.T) {
	disabledObjects := k8sClientCacheDisabledObjects()

	assert.Contains(t, disabledObjects, &corev1.ResourceQuota{})
	assert.Contains(t, disabledObjects, &corev1.LimitRange{})
	assert.Contains(t, disabledObjects, &v1alpha1.PlacementPolicy{})
}

func TestEnsureSharedPoolLimitRange(t *testing.T) {
	tests := []struct {
		name           string
		vcpusLimit     uint64
		initialObjects []client.Object
		repeat         bool
		expectNotFound bool
	}{
		{
			name:       "create limit range",
			vcpusLimit: 106,
			repeat:     true,
		},
		{
			name:       "update existing limit range",
			vcpusLimit: 106,
			initialObjects: []client.Object{&corev1.LimitRange{
				ObjectMeta: metav1.ObjectMeta{
					Name:      SharedPoolDefaultCPURequestLimitRangeName,
					Namespace: "test-namespace",
				},
				Spec: corev1.LimitRangeSpec{
					Limits: []corev1.LimitRangeItem{{
						Type: corev1.LimitTypeContainer,
						DefaultRequest: corev1.ResourceList{
							corev1.ResourceCPU: mustParseQuantity(t, "100m"),
						},
					}},
				},
			}},
		},
		{
			name:       "delete limit range when limit is zero",
			vcpusLimit: 0,
			initialObjects: []client.Object{&corev1.LimitRange{
				ObjectMeta: metav1.ObjectMeta{
					Name:      SharedPoolDefaultCPURequestLimitRangeName,
					Namespace: "test-namespace",
				},
			}},
			expectNotFound: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			scheme := runtime.NewScheme()
			require.NoError(t, corev1.AddToScheme(scheme))

			builder := fake.NewClientBuilder().WithScheme(scheme)
			if len(tt.initialObjects) > 0 {
				builder = builder.WithObjects(tt.initialObjects...)
			}
			k8sClient := builder.Build()

			require.NoError(t, EnsureSharedPoolLimitRange(context.Background(), k8sClient, "test-namespace", tt.vcpusLimit))
			if tt.repeat {
				require.NoError(t, EnsureSharedPoolLimitRange(context.Background(), k8sClient, "test-namespace", tt.vcpusLimit))
			}

			stored := &corev1.LimitRange{}
			err := k8sClient.Get(context.Background(), types.NamespacedName{
				Name:      SharedPoolDefaultCPURequestLimitRangeName,
				Namespace: "test-namespace",
			}, stored)
			if tt.expectNotFound {
				require.Error(t, err)
				assert.True(t, apierrors.IsNotFound(err))
				return
			}

			require.NoError(t, err)
			require.Len(t, stored.Spec.Limits, 1)
			assert.Equal(t, corev1.LimitTypeContainer, stored.Spec.Limits[0].Type)
			cpuRequest, ok := stored.Spec.Limits[0].DefaultRequest[corev1.ResourceCPU]
			require.True(t, ok)
			assert.Equal(t, sharedPoolDefaultCPURequest, cpuRequest.String())
		})
	}
}

func TestRESTMapper(t *testing.T) {
	// Test that RESTMapper function exists and returns a valid mapper
	t.Run("function exists", func(t *testing.T) {
		mapper := RESTMapper()
		if mapper == nil {
			t.Error("RESTMapper() should return non-nil mapper")
		}
	})
}

// Mock client for testing
type MockClient struct {
	client.Client
}

func (m *MockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error {
	// Mock implementation
	return nil
}

func (m *MockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
	// Mock implementation
	return nil
}

func (m *MockClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
	// Mock implementation
	return nil
}

func TestYamlParsingEdgeCases(t *testing.T) {
	tests := []struct {
		name        string
		yamlStr     string
		expectErr   bool
		description string
	}{
		{
			name: "yaml with comments",
			yamlStr: `# This is a comment
apiVersion: v1
kind: Pod
metadata:
  name: test-pod # inline comment`,
			expectErr:   false,
			description: "YAML with comments should be parsed correctly",
		},
		{
			name: "yaml with multiple documents",
			yamlStr: `---
apiVersion: v1
kind: Pod
metadata:
  name: pod1
---
apiVersion: v1
kind: Pod
metadata:
  name: pod2`,
			expectErr:   true,
			description: "Multiple YAML documents should cause error",
		},
		{
			name: "yaml with leading separator only",
			yamlStr: `---
apiVersion: v1
kind: Pod
metadata:
  name: pod1`,
			expectErr:   false,
			description: "A leading --- with a single doc should still parse",
		},
		{
			name: "yaml with trailing empty doc",
			yamlStr: `apiVersion: v1
kind: Pod
metadata:
  name: pod1
---
`,
			expectErr:   false,
			description: "A trailing empty doc separator should not count as multi-doc",
		},
		{
			name: "yaml with three documents",
			yamlStr: `apiVersion: v1
kind: Pod
metadata:
  name: pod1
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: cm1
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy1`,
			expectErr:   true,
			description: "Three documents must error, not just keep the first like sigs.k8s.io/yaml does",
		},
		{
			name: "yaml with special characters",
			yamlStr: `apiVersion: v1
kind: Pod
metadata:
  name: "test-pod-with-special-chars-@#$%"
  labels:
    "label.with.dots": "value"
    "label-with-dashes": "value"`,
			expectErr:   false,
			description: "YAML with special characters should be parsed correctly",
		},
		{
			name: "yaml with numeric values",
			yamlStr: `apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  replicas: 3
  ports:
  - port: 8080
    targetPort: 80`,
			expectErr:   false,
			description: "YAML with numeric values should be parsed correctly",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			obj, gvk, err := GenerateUnstructuredFromYaml(tt.yamlStr)

			if tt.expectErr {
				if err == nil {
					t.Errorf("GenerateUnstructuredFromYaml() expected error but got none: %s", tt.description)
				}
				return
			}

			if err != nil {
				t.Errorf("GenerateUnstructuredFromYaml() unexpected error: %v - %s", err, tt.description)
				return
			}

			if obj == nil {
				t.Errorf("GenerateUnstructuredFromYaml() returned nil object: %s", tt.description)
				return
			}

			if gvk == nil {
				t.Errorf("GenerateUnstructuredFromYaml() returned nil GroupVersionKind: %s", tt.description)
				return
			}
		})
	}
}

func mustParseQuantity(t *testing.T, value string) resource.Quantity {
	t.Helper()
	return resource.MustParse(value)
}

func TestConfigValidation(t *testing.T) {
	// Test that the config validation logic works correctly
	t.Run("config timeout", func(t *testing.T) {
		// This test would verify that the config timeout is set to 0
		// In a real implementation, you'd check the config.Timeout field
		_ = NewClient
	})

	t.Run("config QPS and Burst", func(t *testing.T) {
		// This test would verify that QPS and Burst are set correctly
		// In a real implementation, you'd check the config.QPS and config.Burst fields
		_ = NewClient
	})
}

func TestCompareK8sVersion(t *testing.T) {
	tests := []struct {
		name        string
		v1          string
		v2          string
		expected    int
		expectError bool
	}{
		// Basic version comparisons
		{
			name:     "equal versions",
			v1:       "1.34",
			v2:       "1.34",
			expected: 0,
		},
		{
			name:     "v1 greater major version",
			v1:       "2.0",
			v2:       "1.34",
			expected: 1,
		},
		{
			name:     "v1 less major version",
			v1:       "1.34",
			v2:       "2.0",
			expected: -1,
		},
		{
			name:     "v1 greater minor version",
			v1:       "1.35",
			v2:       "1.34",
			expected: 1,
		},
		{
			name:     "v1 less minor version",
			v1:       "1.33",
			v2:       "1.34",
			expected: -1,
		},

		// Three-part versions
		{
			name:     "equal three-part versions",
			v1:       "1.34.1",
			v2:       "1.34.1",
			expected: 0,
		},
		{
			name:     "v1 greater patch version",
			v1:       "1.34.2",
			v2:       "1.34.1",
			expected: 1,
		},
		{
			name:     "v1 less patch version",
			v1:       "1.34.0",
			v2:       "1.34.1",
			expected: -1,
		},

		// Different version lengths
		{
			name:     "two-part vs three-part equal",
			v1:       "1.34",
			v2:       "1.34.0",
			expected: 0,
		},
		{
			name:     "two-part vs three-part v1 less",
			v1:       "1.34",
			v2:       "1.34.1",
			expected: -1,
		},
		{
			name:     "three-part vs two-part v1 greater",
			v1:       "1.34.1",
			v2:       "1.34",
			expected: 1,
		},

		// Versions with "v" prefix
		{
			name:     "v prefix on v1",
			v1:       "v1.34",
			v2:       "1.34",
			expected: 0,
		},
		{
			name:     "v prefix on v2",
			v1:       "1.34",
			v2:       "v1.34",
			expected: 0,
		},
		{
			name:     "v prefix on both",
			v1:       "v1.35",
			v2:       "v1.34",
			expected: 1,
		},

		// Versions with vendor suffix (alicloud format)
		{
			name:     "alicloud format equal base version",
			v1:       "1.34.1-aliyun.1",
			v2:       "1.34.1-aliyun.2",
			expected: 0, // suffix is stripped, base versions are equal
		},
		{
			name:     "alicloud format v1 greater",
			v1:       "1.34.2-aliyun.1",
			v2:       "1.34.1-aliyun.1",
			expected: 1,
		},
		{
			name:     "alicloud format v1 less",
			v1:       "1.34.0-aliyun.1",
			v2:       "1.34.1-aliyun.1",
			expected: -1,
		},
		{
			name:     "alicloud format vs plain version",
			v1:       "1.34.1-aliyun.1",
			v2:       "1.34.1",
			expected: 0,
		},

		// Versions with + suffix
		{
			name:     "plus suffix stripped",
			v1:       "1.34.1+build.123",
			v2:       "1.34.1",
			expected: 0,
		},

		// AWS format (simple two-part)
		{
			name:     "aws format comparison",
			v1:       "1.34",
			v2:       "1.33",
			expected: 1,
		},

		// Mixed formats
		{
			name:     "aws vs alicloud format equal",
			v1:       "1.34",
			v2:       "1.34.0-aliyun.1",
			expected: 0,
		},
		{
			name:     "aws vs alicloud format v1 less",
			v1:       "1.34",
			v2:       "1.34.1-aliyun.1",
			expected: -1,
		},

		// Edge cases
		{
			name:     "single digit versions",
			v1:       "1",
			v2:       "2",
			expected: -1,
		},
		{
			name:     "zero versions",
			v1:       "0.0.0",
			v2:       "0.0.0",
			expected: 0,
		},
		{
			name:     "large version numbers",
			v1:       "1.100.999",
			v2:       "1.100.998",
			expected: 1,
		},

		// Error cases
		{
			name:        "invalid version v1 with letters",
			v1:          "1.beta.0",
			v2:          "1.34.0",
			expectError: true,
		},
		{
			name:        "invalid version v2 with letters",
			v1:          "1.34.0",
			v2:          "1.beta.0",
			expectError: true,
		},
		{
			name:        "empty v1",
			v1:          "",
			v2:          "1.34",
			expectError: true,
		},
		{
			name:        "empty v2",
			v1:          "1.34",
			v2:          "",
			expectError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := CompareK8sVersion(tt.v1, tt.v2)

			if tt.expectError {
				if err == nil {
					t.Errorf("CompareK8sVersion(%q, %q) expected error but got none", tt.v1, tt.v2)
				}
				return
			}

			if err != nil {
				t.Errorf("CompareK8sVersion(%q, %q) unexpected error: %v", tt.v1, tt.v2, err)
				return
			}

			if result != tt.expected {
				t.Errorf("CompareK8sVersion(%q, %q) = %d, expected %d", tt.v1, tt.v2, result, tt.expected)
			}
		})
	}
}

func TestCompareK8sVersion_Symmetry(t *testing.T) {
	// Test that comparison is symmetric: if v1 > v2, then v2 < v1
	testCases := []struct {
		v1 string
		v2 string
	}{
		{"1.34", "1.33"},
		{"1.34.1", "1.34.0"},
		{"2.0", "1.99"},
		{"1.34.1-aliyun.1", "1.34.0-aliyun.1"},
	}

	for _, tc := range testCases {
		t.Run(tc.v1+"_vs_"+tc.v2, func(t *testing.T) {
			result1, err1 := CompareK8sVersion(tc.v1, tc.v2)
			result2, err2 := CompareK8sVersion(tc.v2, tc.v1)

			if err1 != nil || err2 != nil {
				t.Errorf("Unexpected error: err1=%v, err2=%v", err1, err2)
				return
			}

			if result1 != -result2 {
				t.Errorf("Symmetry violation: CompareK8sVersion(%q, %q)=%d, CompareK8sVersion(%q, %q)=%d",
					tc.v1, tc.v2, result1, tc.v2, tc.v1, result2)
			}
		})
	}
}

func TestCompareK8sVersion_Transitivity(t *testing.T) {
	// Test transitivity: if v1 > v2 and v2 > v3, then v1 > v3
	versions := []string{"1.32", "1.33", "1.34", "1.34.1", "1.35"}

	for i := 0; i < len(versions)-2; i++ {
		v1, v2, v3 := versions[i+2], versions[i+1], versions[i]
		t.Run(v1+"_"+v2+"_"+v3, func(t *testing.T) {
			r1, _ := CompareK8sVersion(v1, v2)
			r2, _ := CompareK8sVersion(v2, v3)
			r3, _ := CompareK8sVersion(v1, v3)

			if r1 > 0 && r2 > 0 && r3 <= 0 {
				t.Errorf("Transitivity violation: %s > %s and %s > %s, but %s not > %s",
					v1, v2, v2, v3, v1, v3)
			}
		})
	}
}
