/*
 *
 * Copyright (c) 2025 Bocloud Technologies Co., Ltd.
 * installer is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          <http://license.coscl.org.cn/MulanPSL2>
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 *
 */

package k8s

import (
	"errors"
	"net"
	"os"
	"testing"

	"github.com/agiledragon/gomonkey/v2"
	"github.com/stretchr/testify/assert"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/discovery"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/fake"
	"k8s.io/client-go/restmapper"
	"k8s.io/client-go/tools/clientcmd"

	"gopkg.openfuyao.cn/bkeadm/utils"
)

const (
	testNumericZero         = 0
	testNumericOne          = 1
	testNumericTwo          = 2
	testNumericThree        = 3
	testNamespaceLen        = 8
	testKubeconfigLen       = 20
	testNamespaceNameLen    = 10
	testNumericRestartCount = 0

	testIPv4SegmentA = 127
	testIPv4SegmentB = 0
	testIPv4SegmentC = 0
	testIPv4SegmentD = 1
)

var (
	testLoopbackIP = net.IPv4(
		testIPv4SegmentA,
		testIPv4SegmentB,
		testIPv4SegmentC,
		testIPv4SegmentD,
	).String()
)

func TestNewKubernetesClient(t *testing.T) {
	tests := []struct {
		name           string
		kubeConfig     string
		fileExists     bool
		buildConfigErr error
		newClientErr   error
		expectedError  bool
	}{
		{
			name:           "kubeconfig does not exist and no home dir",
			kubeConfig:     "",
			fileExists:     false,
			buildConfigErr: nil,
			newClientErr:   nil,
			expectedError:  true,
		},
		{
			name:           "kubeconfig exists but build config fails",
			kubeConfig:     "/tmp/test-kubeconfig",
			fileExists:     true,
			buildConfigErr: errors.New("build config error"),
			newClientErr:   nil,
			expectedError:  true,
		},
		{
			name:           "kubeconfig path provided but file does not exist",
			kubeConfig:     "/nonexistent/path/config",
			fileExists:     false,
			buildConfigErr: nil,
			newClientErr:   nil,
			expectedError:  true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			patches := gomonkey.NewPatches()
			defer patches.Reset()

			patches.ApplyFunc(utils.Exists, func(path string) bool {
				if tt.kubeConfig != "" && path == tt.kubeConfig {
					return tt.fileExists
				}
				return false
			})

			patches.ApplyFunc(clientcmd.BuildConfigFromFlags, func(masterUrl string, kubeconfigPath string) (interface{}, error) {
				return nil, tt.buildConfigErr
			})

			patches.ApplyFunc(kubernetes.NewForConfig, func(config interface{}) (*kubernetes.Clientset, error) {
				return nil, tt.newClientErr
			})

			client, err := NewKubernetesClient(tt.kubeConfig)
			if tt.expectedError {
				assert.Error(t, err)
				assert.Nil(t, client)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, client)
			}
		})
	}
}

func TestGetClient(t *testing.T) {
	t.Run("get client returns the underlying clientset", func(t *testing.T) {
		fakeClient := fake.NewSimpleClientset()
		mockClient := &MockK8sClient{}

		patches := gomonkey.NewPatches()

		defer patches.Reset()

		patches.ApplyFunc((*MockK8sClient).GetClient,
			func(_ *MockK8sClient) kubernetes.Interface {
				return fakeClient
			})

		result := mockClient.GetClient()
		assert.NotNil(t, result)
	})
}

func TestGetDynamicClient(t *testing.T) {
	t.Run("get dynamic client returns the underlying dynamic client", func(t *testing.T) {
		mockClient := &MockK8sClient{}

		result := mockClient.GetDynamicClient()
		assert.NotNil(t, result)
	})
}

func TestClientStruct(t *testing.T) {
	t.Run("client struct fields are properly initialized", func(t *testing.T) {
		mockClient := &MockK8sClient{}

		assert.NotNil(t, mockClient.GetClient())
		assert.NotNil(t, mockClient.GetDynamicClient())
	})
}

func TestDetermineNamespace(t *testing.T) {
	tests := []struct {
		name       string
		namespace  string
		unstructNs string
		expectedNs string
	}{
		{
			name:       "namespace parameter is provided",
			namespace:  "test-namespace",
			unstructNs: "other-namespace",
			expectedNs: "test-namespace",
		},
		{
			name:       "namespace parameter is empty, use unstructured namespace",
			namespace:  "",
			unstructNs: "unstruct-namespace",
			expectedNs: "unstruct-namespace",
		},
		{
			name:       "both are empty",
			namespace:  "",
			unstructNs: "",
			expectedNs: "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			unstruct := &unstructured.Unstructured{}
			unstruct.SetNamespace(tt.unstructNs)

			result := determineNamespaceMock(unstruct, tt.namespace)
			assert.Equal(t, tt.expectedNs, result)
		})
	}
}

func determineNamespaceMock(unstruct *unstructured.Unstructured, ns string) string {
	if ns != "" {
		return ns
	}
	return unstruct.GetNamespace()
}

func TestCreateNamespace(t *testing.T) {
	tests := []struct {
		name          string
		namespace     *corev1.Namespace
		expectedError bool
	}{
		{
			name: "namespace creation succeeds",
			namespace: &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{
					Name: "test-namespace",
				},
			},
			expectedError: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockClient := &MockK8sClient{}

			patches := gomonkey.NewPatches()

			defer patches.Reset()

			patches.ApplyFunc((*MockK8sClient).CreateNamespace,
				func(_ *MockK8sClient, _ *corev1.Namespace) error {
					return nil
				})

			err := mockClient.CreateNamespace(tt.namespace)
			if tt.expectedError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestCreateSecret(t *testing.T) {
	tests := []struct {
		name          string
		secret        *corev1.Secret
		expectedError bool
	}{
		{
			name: "secret creation succeeds",
			secret: &corev1.Secret{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-secret",
					Namespace: "default",
				},
			},
			expectedError: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockClient := &MockK8sClient{}

			patches := gomonkey.NewPatches()

			defer patches.Reset()

			patches.ApplyFunc((*MockK8sClient).CreateSecret,
				func(_ *MockK8sClient, _ *corev1.Secret) error {
					return nil
				})

			err := mockClient.CreateSecret(tt.secret)
			if tt.expectedError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestNamespaceRefStruct(t *testing.T) {
	t.Run("namespace ref struct initialization", func(t *testing.T) {
		namespace := &corev1.Namespace{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "test-namespace",
				Namespace: "",
			},
			Spec:   corev1.NamespaceSpec{},
			Status: corev1.NamespaceStatus{},
		}

		assert.Equal(t, "test-namespace", namespace.Name)
		assert.NotNil(t, namespace.Spec)
		assert.NotNil(t, namespace.Status)
	})
}

func TestSecretRefStruct(t *testing.T) {
	t.Run("secret ref struct initialization", func(t *testing.T) {
		secret := &corev1.Secret{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "test-secret",
				Namespace: "default",
			},
			Type: corev1.SecretTypeOpaque,
			Data: map[string][]byte{
				"key": []byte("value"),
			},
		}

		assert.Equal(t, "test-secret", secret.Name)
		assert.Equal(t, "default", secret.Namespace)
		assert.Equal(t, corev1.SecretTypeOpaque, secret.Type)
	})
}

func TestPackageCreateNamespace(t *testing.T) {
	tests := []struct {
		name          string
		namespaceName string
		createErr     error
		expectedError bool
	}{
		{
			name:          "create namespace successfully",
			namespaceName: "test-namespace",
			createErr:     nil,
			expectedError: false,
		},
		{
			name:          "create namespace fails",
			namespaceName: "fail-namespace",
			createErr:     errors.New("create error"),
			expectedError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockClient := &MockK8sClient{}

			patches := gomonkey.NewPatches()

			defer patches.Reset()

			patches.ApplyFunc((*MockK8sClient).CreateNamespace,
				func(_ *MockK8sClient, _ *corev1.Namespace) error {
					return tt.createErr
				})

			err := CreateNamespace(mockClient, tt.namespaceName)
			if tt.expectedError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestWatchEventByAnnotation(t *testing.T) {
	tests := []struct {
		name string
		ns   string
	}{
		{
			name: "watch event with default namespace",
			ns:   "default",
		},
		{
			name: "watch event with kube-system namespace",
			ns:   "kube-system",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockClient := &MockK8sClient{}

			assert.NotPanics(t, func() {
				mockClient.WatchEventByAnnotation(tt.ns)
			})
		})
	}
}

func TestKubernetesClientInterface(t *testing.T) {
	t.Run("kubernetes client interface can be used with mock client", func(t *testing.T) {
		var client KubernetesClient
		client = &MockK8sClient{}

		assert.NotNil(t, client.GetClient())
		assert.NotNil(t, client.GetDynamicClient())
	})
}

func TestMockK8sClientMethods(t *testing.T) {
	t.Run("mock k8s client returns nil for all interface methods", func(t *testing.T) {
		mockClient := &MockK8sClient{}

		assert.NotNil(t, mockClient.GetClient())
		assert.NotNil(t, mockClient.GetDynamicClient())

		assert.NoError(t, mockClient.InstallYaml("test.yaml", nil, "test"))
		assert.NoError(t, mockClient.PatchYaml("test.yaml", nil))
		assert.NoError(t, mockClient.UninstallYaml("test.yaml", "test"))
		assert.NoError(t, mockClient.CreateNamespace(&corev1.Namespace{}))
		assert.NoError(t, mockClient.CreateSecret(&corev1.Secret{}))

		ns, _ := mockClient.GetNamespace("test.yaml")
		assert.Equal(t, "", ns)
	})
}

func TestGetNamespace(t *testing.T) {
	tests := []struct {
		name          string
		namespace     string
		expectedError bool
	}{
		{
			name:          "get namespace returns empty string",
			namespace:     "",
			expectedError: false,
		},
		{
			name:          "get namespace returns namespace name",
			namespace:     "test-namespace",
			expectedError: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockClient := &MockK8sClient{}

			ns, err := mockClient.GetNamespace("/tmp/test.yaml")
			if tt.expectedError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, ns)
			}
		})
	}
}

func TestInstallYaml(t *testing.T) {
	tests := []struct {
		name          string
		variables     map[string]string
		expectedError bool
	}{
		{
			name: "install yaml with template rendering",
			variables: map[string]string{
				"Name": "test-namespace",
			},
			expectedError: false,
		},
		{
			name:          "install yaml with empty variables",
			variables:     map[string]string{},
			expectedError: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockClient := &MockK8sClient{}

			tmpFile, err := os.CreateTemp("", "test-template-*.yaml")
			assert.NoError(t, err)
			defer os.Remove(tmpFile.Name())

			_, err = tmpFile.WriteString(`apiVersion: v1
kind: Namespace
metadata:
  name: {{ .Name }}
`)
			assert.NoError(t, err)
			tmpFile.Close()

			err = mockClient.InstallYaml(tmpFile.Name(), tt.variables, "test-namespace")
			if tt.expectedError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestPatchYaml(t *testing.T) {
	tests := []struct {
		name          string
		variables     map[string]string
		expectedError bool
	}{
		{
			name: "patch yaml with template rendering",
			variables: map[string]string{
				"Key": "value",
			},
			expectedError: false,
		},
		{
			name:          "patch yaml with empty variables",
			variables:     map[string]string{},
			expectedError: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockClient := &MockK8sClient{}

			tmpFile, err := os.CreateTemp("", "test-template-*.yaml")
			assert.NoError(t, err)
			defer os.Remove(tmpFile.Name())

			_, err = tmpFile.WriteString(`apiVersion: v1
kind: ConfigMap
metadata:
  name: test-config
  namespace: default
`)
			assert.NoError(t, err)
			tmpFile.Close()

			err = mockClient.PatchYaml(tmpFile.Name(), tt.variables)
			if tt.expectedError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestUninstallYaml(t *testing.T) {
	tests := []struct {
		name          string
		yamlContent   string
		expectedError bool
	}{
		{
			name: "uninstall yaml processes resources",
			yamlContent: `apiVersion: v1
kind: Namespace
metadata:
  name: test-namespace
`,
			expectedError: false,
		},
		{
			name:          "uninstall yaml with empty content",
			yamlContent:   "",
			expectedError: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockClient := &MockK8sClient{}

			tmpFile, err := os.CreateTemp("", "test-*.yaml")
			assert.NoError(t, err)
			defer os.Remove(tmpFile.Name())

			_, err = tmpFile.WriteString(tt.yamlContent)
			assert.NoError(t, err)
			tmpFile.Close()

			err = mockClient.UninstallYaml(tmpFile.Name(), "")
			if tt.expectedError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestRenderTemplateToTempFile(t *testing.T) {
	tests := []struct {
		name            string
		templateContent string
		variables       map[string]string
		expectedFile    bool
		expectedError   bool
	}{
		{
			name: "render template successfully",
			templateContent: `apiVersion: v1
kind: Namespace
metadata:
  name: {{ .Name }}
`,
			variables: map[string]string{
				"Name": "test-namespace",
			},
			expectedFile:  true,
			expectedError: false,
		},
		{
			name:            "render template with empty variables",
			templateContent: "name: test",
			variables:       map[string]string{},
			expectedFile:    true,
			expectedError:   false,
		},
		{
			name:            "render non-existent file",
			templateContent: "",
			variables:       nil,
			expectedFile:    false,
			expectedError:   true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.templateContent != "" {
				tmpFile, err := os.CreateTemp("", "test-template-*.yaml")
				assert.NoError(t, err)
				defer os.Remove(tmpFile.Name())

				_, err = tmpFile.WriteString(tt.templateContent)
				assert.NoError(t, err)
				tmpFile.Close()

				result, cleanup, err := renderTemplateToTempFile(tmpFile.Name(), tt.variables)
				defer cleanup()

				if tt.expectedError {
					assert.Error(t, err)
					assert.Equal(t, "", result)
				} else {
					assert.NoError(t, err)
					assert.NotNil(t, cleanup)
					if tt.expectedFile {
						assert.NotEmpty(t, result)
						_, err := os.Stat(result)
						assert.NoError(t, err)
					}
				}
			} else {
				result, cleanup, err := renderTemplateToTempFile("/non/existent/file.yaml", tt.variables)
				if cleanup != nil {
					defer cleanup()
				}

				assert.Error(t, err)
				assert.Equal(t, "", result)
			}
		})
	}
}

func TestProcessYamlResources(t *testing.T) {
	t.Run("process yaml resources with valid namespace", func(t *testing.T) {
		patches := gomonkey.NewPatches()
		defer patches.Reset()

		patches.ApplyFunc(restmapper.GetAPIGroupResources, func(discovery discovery.DiscoveryInterface) ([]*restmapper.APIGroupResources, error) {
			return []*restmapper.APIGroupResources{
				{
					Group: metav1.APIGroup{
						Name: "",
						Versions: []metav1.GroupVersionForDiscovery{
							{Version: "v1"},
						},
					},
					VersionedResources: map[string][]metav1.APIResource{
						"v1": {
							{
								Name:    "namespaces",
								Kind:    "Namespace",
								Version: "v1",
							},
						},
					},
				},
			}, nil
		})

		patches.ApplyFunc(NewKubernetesClient, func(kubeConfig string) (KubernetesClient, error) {
			return &Client{
				ClientSet:     &kubernetes.Clientset{},
				DynamicClient: nil,
			}, nil
		})

		client, _ := NewKubernetesClient("")
		c := client.(*Client)

		tmpFile, err := os.CreateTemp("", "test-yaml-*.yaml")
		assert.NoError(t, err)
		defer os.Remove(tmpFile.Name())

		_, err = tmpFile.WriteString(`apiVersion: v1
kind: Namespace
metadata:
  name: test-namespace
`)
		assert.NoError(t, err)
		tmpFile.Close()

		err = c.processYamlResources(tmpFile.Name(), func(unstruct *unstructured.Unstructured, mapping *meta.RESTMapping) error {
			assert.NotNil(t, unstruct)
			assert.NotNil(t, mapping)
			return nil
		})
		assert.NoError(t, err)
	})

	t.Run("process yaml resources with empty file", func(t *testing.T) {
		patches := gomonkey.NewPatches()
		defer patches.Reset()

		patches.ApplyFunc(restmapper.GetAPIGroupResources, func(discovery discovery.DiscoveryInterface) ([]*restmapper.APIGroupResources, error) {
			return []*restmapper.APIGroupResources{}, nil
		})

		patches.ApplyFunc(NewKubernetesClient, func(kubeConfig string) (KubernetesClient, error) {
			return &Client{
				ClientSet:     &kubernetes.Clientset{},
				DynamicClient: nil,
			}, nil
		})

		client, _ := NewKubernetesClient("")
		c := client.(*Client)

		tmpFile, err := os.CreateTemp("", "test-empty-*.yaml")
		assert.NoError(t, err)
		defer os.Remove(tmpFile.Name())
		tmpFile.Close()

		err = c.processYamlResources(tmpFile.Name(), func(unstruct *unstructured.Unstructured, mapping *meta.RESTMapping) error {
			return nil
		})
		assert.NoError(t, err)
	})

	t.Run("process yaml resources with non-existent file", func(t *testing.T) {
		patches := gomonkey.NewPatches()
		defer patches.Reset()

		patches.ApplyFunc(restmapper.GetAPIGroupResources, func(discovery discovery.DiscoveryInterface) ([]*restmapper.APIGroupResources, error) {
			return nil, nil
		})

		patches.ApplyFunc(NewKubernetesClient, func(kubeConfig string) (KubernetesClient, error) {
			return &Client{
				ClientSet:     &kubernetes.Clientset{},
				DynamicClient: nil,
			}, nil
		})

		client, _ := NewKubernetesClient("")
		c := client.(*Client)

		err := c.processYamlResources("/non/existent/file.yaml", func(unstruct *unstructured.Unstructured, mapping *meta.RESTMapping) error {
			return nil
		})
		assert.Error(t, err)
	})
}

func TestCreateResource(t *testing.T) {
	t.Run("create resource with nil dynamic client panics", func(t *testing.T) {
		patches := gomonkey.NewPatches()
		defer patches.Reset()

		patches.ApplyFunc(restmapper.GetAPIGroupResources, func(discovery discovery.DiscoveryInterface) ([]*restmapper.APIGroupResources, error) {
			return []*restmapper.APIGroupResources{
				{
					Group: metav1.APIGroup{
						Name: "",
						Versions: []metav1.GroupVersionForDiscovery{
							{Version: "v1"},
						},
					},
					VersionedResources: map[string][]metav1.APIResource{
						"v1": {
							{
								Name:    "namespaces",
								Kind:    "Namespace",
								Version: "v1",
							},
						},
					},
				},
			}, nil
		})

		patches.ApplyFunc(NewKubernetesClient, func(kubeConfig string) (KubernetesClient, error) {
			return &Client{
				ClientSet:     &kubernetes.Clientset{},
				DynamicClient: nil,
			}, nil
		})

		client, _ := NewKubernetesClient("")
		c := client.(*Client)

		unstruct := &unstructured.Unstructured{
			Object: map[string]interface{}{
				"apiVersion": "v1",
				"kind":       "Namespace",
				"metadata": map[string]interface{}{
					"name":      "test-namespace",
					"namespace": "",
				},
			},
		}

		mapping := &meta.RESTMapping{
			Resource: schema.GroupVersionResource{
				Group:    "",
				Version:  "v1",
				Resource: "namespaces",
			},
		}

		assert.Panics(t, func() {
			_ = c.createResource(unstruct, mapping, "")
		})
	})

	t.Run("create resource with namespace and nil dynamic client panics", func(t *testing.T) {
		patches := gomonkey.NewPatches()
		defer patches.Reset()

		patches.ApplyFunc(restmapper.GetAPIGroupResources, func(discovery discovery.DiscoveryInterface) ([]*restmapper.APIGroupResources, error) {
			return []*restmapper.APIGroupResources{
				{
					Group: metav1.APIGroup{
						Name: "",
						Versions: []metav1.GroupVersionForDiscovery{
							{Version: "v1"},
						},
					},
					VersionedResources: map[string][]metav1.APIResource{
						"v1": {
							{
								Name:    "configmaps",
								Kind:    "ConfigMap",
								Version: "v1",
							},
						},
					},
				},
			}, nil
		})

		patches.ApplyFunc(NewKubernetesClient, func(kubeConfig string) (KubernetesClient, error) {
			return &Client{
				ClientSet:     &kubernetes.Clientset{},
				DynamicClient: nil,
			}, nil
		})

		client, _ := NewKubernetesClient("")
		c := client.(*Client)

		unstruct := &unstructured.Unstructured{
			Object: map[string]interface{}{
				"apiVersion": "v1",
				"kind":       "ConfigMap",
				"metadata": map[string]interface{}{
					"name":      "test-config",
					"namespace": "default",
				},
			},
		}

		mapping := &meta.RESTMapping{
			Resource: schema.GroupVersionResource{
				Group:    "",
				Version:  "v1",
				Resource: "configmaps",
			},
		}

		assert.Panics(t, func() {
			_ = c.createResource(unstruct, mapping, "test-namespace")
		})
	})
}

func TestDeleteResource(t *testing.T) {
	t.Run("delete resource with nil dynamic client panics", func(t *testing.T) {
		patches := gomonkey.NewPatches()
		defer patches.Reset()

		patches.ApplyFunc(restmapper.GetAPIGroupResources, func(discovery discovery.DiscoveryInterface) ([]*restmapper.APIGroupResources, error) {
			return []*restmapper.APIGroupResources{
				{
					Group: metav1.APIGroup{
						Name: "",
						Versions: []metav1.GroupVersionForDiscovery{
							{Version: "v1"},
						},
					},
					VersionedResources: map[string][]metav1.APIResource{
						"v1": {
							{
								Name:    "namespaces",
								Kind:    "Namespace",
								Version: "v1",
							},
						},
					},
				},
			}, nil
		})

		patches.ApplyFunc(NewKubernetesClient, func(kubeConfig string) (KubernetesClient, error) {
			return &Client{
				ClientSet:     &kubernetes.Clientset{},
				DynamicClient: nil,
			}, nil
		})

		client, _ := NewKubernetesClient("")
		c := client.(*Client)

		unstruct := &unstructured.Unstructured{
			Object: map[string]interface{}{
				"apiVersion": "v1",
				"kind":       "Namespace",
				"metadata": map[string]interface{}{
					"name":      "test-namespace",
					"namespace": "",
				},
			},
		}

		mapping := &meta.RESTMapping{
			Resource: schema.GroupVersionResource{
				Group:    "",
				Version:  "v1",
				Resource: "namespaces",
			},
		}

		assert.Panics(t, func() {
			_ = c.deleteResource(unstruct, mapping, "")
		})
	})

	t.Run("delete resource with namespace and nil dynamic client panics", func(t *testing.T) {
		patches := gomonkey.NewPatches()
		defer patches.Reset()

		patches.ApplyFunc(restmapper.GetAPIGroupResources, func(discovery discovery.DiscoveryInterface) ([]*restmapper.APIGroupResources, error) {
			return []*restmapper.APIGroupResources{
				{
					Group: metav1.APIGroup{
						Name: "",
						Versions: []metav1.GroupVersionForDiscovery{
							{Version: "v1"},
						},
					},
					VersionedResources: map[string][]metav1.APIResource{
						"v1": {
							{
								Name:    "configmaps",
								Kind:    "ConfigMap",
								Version: "v1",
							},
						},
					},
				},
			}, nil
		})

		patches.ApplyFunc(NewKubernetesClient, func(kubeConfig string) (KubernetesClient, error) {
			return &Client{
				ClientSet:     &kubernetes.Clientset{},
				DynamicClient: nil,
			}, nil
		})

		client, _ := NewKubernetesClient("")
		c := client.(*Client)

		unstruct := &unstructured.Unstructured{
			Object: map[string]interface{}{
				"apiVersion": "v1",
				"kind":       "ConfigMap",
				"metadata": map[string]interface{}{
					"name":      "test-config",
					"namespace": "default",
				},
			},
		}

		mapping := &meta.RESTMapping{
			Resource: schema.GroupVersionResource{
				Group:    "",
				Version:  "v1",
				Resource: "configmaps",
			},
		}

		assert.Panics(t, func() {
			_ = c.deleteResource(unstruct, mapping, "test-namespace")
		})
	})
}

func TestDetermineNamespaceWithClient(t *testing.T) {
	tests := []struct {
		name       string
		namespace  string
		unstructNs string
		expectedNs string
	}{
		{
			name:       "namespace parameter is provided",
			namespace:  "test-namespace",
			unstructNs: "other-namespace",
			expectedNs: "test-namespace",
		},
		{
			name:       "namespace parameter is empty, use unstructured namespace",
			namespace:  "",
			unstructNs: "unstruct-namespace",
			expectedNs: "unstruct-namespace",
		},
		{
			name:       "both are empty",
			namespace:  "",
			unstructNs: "",
			expectedNs: "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			patches := gomonkey.NewPatches()
			defer patches.Reset()

			patches.ApplyFunc(NewKubernetesClient, func(kubeConfig string) (KubernetesClient, error) {
				return &Client{
					ClientSet:     &kubernetes.Clientset{},
					DynamicClient: nil,
				}, nil
			})

			client, _ := NewKubernetesClient("")
			c := client.(*Client)

			unstruct := &unstructured.Unstructured{}
			unstruct.SetNamespace(tt.unstructNs)

			result := c.determineNamespace(unstruct, tt.namespace)
			assert.Equal(t, tt.expectedNs, result)
		})
	}
}

func TestInstallYamlWithClient(t *testing.T) {
	t.Run("install yaml with client panics due to nil dynamic client", func(t *testing.T) {
		patches := gomonkey.NewPatches()
		defer patches.Reset()

		patches.ApplyFunc(restmapper.GetAPIGroupResources, func(discovery discovery.DiscoveryInterface) ([]*restmapper.APIGroupResources, error) {
			return []*restmapper.APIGroupResources{
				{
					Group: metav1.APIGroup{
						Name: "",
						Versions: []metav1.GroupVersionForDiscovery{
							{Version: "v1"},
						},
					},
					VersionedResources: map[string][]metav1.APIResource{
						"v1": {
							{
								Name:    "namespaces",
								Kind:    "Namespace",
								Version: "v1",
							},
						},
					},
				},
			}, nil
		})

		patches.ApplyFunc(NewKubernetesClient, func(kubeConfig string) (KubernetesClient, error) {
			return &Client{
				ClientSet:     &kubernetes.Clientset{},
				DynamicClient: nil,
			}, nil
		})

		client, _ := NewKubernetesClient("")
		c := client.(*Client)

		tmpFile, err := os.CreateTemp("", "test-install-*.yaml")
		assert.NoError(t, err)
		defer os.Remove(tmpFile.Name())

		_, err = tmpFile.WriteString(`apiVersion: v1
kind: Namespace
metadata:
  name: {{ .Name }}
`)
		assert.NoError(t, err)
		tmpFile.Close()

		assert.Panics(t, func() {
			_ = c.InstallYaml(tmpFile.Name(), map[string]string{"Name": "test-ns"}, "test-namespace")
		})
	})
}

func TestPatchYamlWithClient(t *testing.T) {
	t.Run("patch yaml with client panics due to nil dynamic client", func(t *testing.T) {
		patches := gomonkey.NewPatches()
		defer patches.Reset()

		patches.ApplyFunc(restmapper.GetAPIGroupResources, func(discovery discovery.DiscoveryInterface) ([]*restmapper.APIGroupResources, error) {
			return []*restmapper.APIGroupResources{
				{
					Group: metav1.APIGroup{
						Name: "",
						Versions: []metav1.GroupVersionForDiscovery{
							{Version: "v1"},
						},
					},
					VersionedResources: map[string][]metav1.APIResource{
						"v1": {
							{
								Name:    "configmaps",
								Kind:    "ConfigMap",
								Version: "v1",
							},
						},
					},
				},
			}, nil
		})

		patches.ApplyFunc(NewKubernetesClient, func(kubeConfig string) (KubernetesClient, error) {
			return &Client{
				ClientSet:     &kubernetes.Clientset{},
				DynamicClient: nil,
			}, nil
		})

		client, _ := NewKubernetesClient("")
		c := client.(*Client)

		tmpFile, err := os.CreateTemp("", "test-patch-*.yaml")
		assert.NoError(t, err)
		defer os.Remove(tmpFile.Name())

		_, err = tmpFile.WriteString(`apiVersion: v1
kind: ConfigMap
metadata:
  name: test-config
  namespace: default
`)
		assert.NoError(t, err)
		tmpFile.Close()

		assert.Panics(t, func() {
			_ = c.PatchYaml(tmpFile.Name(), map[string]string{"Key": "value"})
		})
	})
}

func TestUninstallYamlWithClient(t *testing.T) {
	t.Run("uninstall yaml with client panics due to nil dynamic client", func(t *testing.T) {
		patches := gomonkey.NewPatches()
		defer patches.Reset()

		patches.ApplyFunc(restmapper.GetAPIGroupResources, func(discovery discovery.DiscoveryInterface) ([]*restmapper.APIGroupResources, error) {
			return []*restmapper.APIGroupResources{
				{
					Group: metav1.APIGroup{
						Name: "",
						Versions: []metav1.GroupVersionForDiscovery{
							{Version: "v1"},
						},
					},
					VersionedResources: map[string][]metav1.APIResource{
						"v1": {
							{
								Name:    "namespaces",
								Kind:    "Namespace",
								Version: "v1",
							},
						},
					},
				},
			}, nil
		})

		patches.ApplyFunc(NewKubernetesClient, func(kubeConfig string) (KubernetesClient, error) {
			return &Client{
				ClientSet:     &kubernetes.Clientset{},
				DynamicClient: nil,
			}, nil
		})

		client, _ := NewKubernetesClient("")
		c := client.(*Client)

		tmpFile, err := os.CreateTemp("", "test-uninstall-*.yaml")
		assert.NoError(t, err)
		defer os.Remove(tmpFile.Name())

		_, err = tmpFile.WriteString(`apiVersion: v1
kind: Namespace
metadata:
  name: test-namespace
`)
		assert.NoError(t, err)
		tmpFile.Close()

		assert.Panics(t, func() {
			_ = c.UninstallYaml(tmpFile.Name(), "")
		})
	})
}

func TestGetNamespaceWithClient(t *testing.T) {
	t.Run("get namespace from yaml file", func(t *testing.T) {
		patches := gomonkey.NewPatches()
		defer patches.Reset()

		patches.ApplyFunc(NewKubernetesClient, func(kubeConfig string) (KubernetesClient, error) {
			return &Client{
				ClientSet:     &kubernetes.Clientset{},
				DynamicClient: nil,
			}, nil
		})

		client, _ := NewKubernetesClient("")
		c := client.(*Client)

		tmpFile, err := os.CreateTemp("", "test-getns-*.yaml")
		assert.NoError(t, err)
		defer os.Remove(tmpFile.Name())

		_, err = tmpFile.WriteString(`apiVersion: v1
kind: Namespace
metadata:
  name: my-test-namespace
`)
		assert.NoError(t, err)
		tmpFile.Close()

		ns, err := c.GetNamespace(tmpFile.Name())
		assert.NoError(t, err)
		assert.Equal(t, "my-test-namespace", ns)
	})

	t.Run("get namespace from yaml without namespace resource", func(t *testing.T) {
		patches := gomonkey.NewPatches()
		defer patches.Reset()

		patches.ApplyFunc(NewKubernetesClient, func(kubeConfig string) (KubernetesClient, error) {
			return &Client{
				ClientSet:     &kubernetes.Clientset{},
				DynamicClient: nil,
			}, nil
		})

		client, _ := NewKubernetesClient("")
		c := client.(*Client)

		tmpFile, err := os.CreateTemp("", "test-no-ns-*.yaml")
		assert.NoError(t, err)
		defer os.Remove(tmpFile.Name())

		_, err = tmpFile.WriteString(`apiVersion: v1
kind: ConfigMap
metadata:
  name: test-config
`)
		assert.NoError(t, err)
		tmpFile.Close()

		ns, err := c.GetNamespace(tmpFile.Name())
		assert.NoError(t, err)
		assert.Equal(t, "", ns)
	})
}