package overridemanager

import (
	"reflect"
	"testing"

	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
)

func generateDeploymentYaml() *unstructured.Unstructured {
	return &unstructured.Unstructured{
		Object: map[string]interface{}{
			"apiVersion": "apps/v1",
			"kind":       "Deployment",
			"metadata": map[string]interface{}{
				"name": "nginx",
				"labels": map[string]interface{}{
					"app": "nginx",
				},
			},
			"spec": map[string]interface{}{
				"replicas": 1,
				"selector": map[string]interface{}{
					"matchLabels": map[string]interface{}{
						"app": "nginx",
					},
				},
				"template": map[string]interface{}{
					"metadata": map[string]interface{}{
						"labels": map[string]interface{}{
							"app": "nginx",
						},
					},
					"spec": map[string]interface{}{
						"containers": []interface{}{
							map[string]interface{}{
								"image": "fictional.registry.example/imagename:v1.0.0",
								"name":  "nginx",
							}}}}}}}
}

func generatePodYaml() *unstructured.Unstructured {
	return &unstructured.Unstructured{
		Object: map[string]interface{}{
			"apiVersion": "v1",
			"kind":       "Pod",
			"metadata": map[string]interface{}{
				"name": "nginx",
				"labels": map[string]interface{}{
					"app": "nginx",
				},
			},
			"spec": map[string]interface{}{
				"containers": []interface{}{
					map[string]interface{}{
						"image": "fictional.registry.example/imagename:v1.0.0",
						"name":  "nginx",
					}}}}}
}

func generateStatefulSetYaml() *unstructured.Unstructured {
	return &unstructured.Unstructured{
		Object: map[string]interface{}{
			"apiVersion": "apps/v1",
			"kind":       "StatefulSet",
			"metadata": map[string]interface{}{
				"name": "web",
			},
			"spec": map[string]interface{}{
				"replicas": 2,
				"selector": map[string]interface{}{
					"matchLabels": map[string]interface{}{
						"app": "nginx",
					},
				},
				"template": map[string]interface{}{
					"metadata": map[string]interface{}{
						"labels": map[string]interface{}{
							"app": "nginx",
						},
					},
					"spec": map[string]interface{}{
						"containers": []interface{}{
							map[string]interface{}{
								"image": "fictional.registry.example/imagename:v1.0.0",
								"name":  "nginx",
							}}}}}}}
}

func generateReplicaSetYaml() *unstructured.Unstructured {
	return &unstructured.Unstructured{
		Object: map[string]interface{}{
			"apiVersion": "apps/v1",
			"kind":       "ReplicaSet",
			"metadata": map[string]interface{}{
				"name": "nginx",
				"labels": map[string]interface{}{
					"app": "nginx",
				},
			},
			"spec": map[string]interface{}{
				"replicas": 1,
				"selector": map[string]interface{}{
					"matchLabels": map[string]interface{}{
						"app": "nginx",
					},
				},
				"template": map[string]interface{}{
					"metadata": map[string]interface{}{
						"labels": map[string]interface{}{
							"app": "nginx",
						},
					},
					"spec": map[string]interface{}{
						"containers": []interface{}{
							map[string]interface{}{
								"image": "fictional.registry.example/imagename:v1.0.0",
								"name":  "nginx",
							}}}}}}}
}

func generateDaemonSetYaml() *unstructured.Unstructured {
	return &unstructured.Unstructured{
		Object: map[string]interface{}{
			"apiVersion": "apps/v1",
			"kind":       "DaemonSet",
			"metadata": map[string]interface{}{
				"name": "nginx",
				"labels": map[string]interface{}{
					"app": "nginx",
				},
			},
			"spec": map[string]interface{}{
				"selector": map[string]interface{}{
					"matchLabels": map[string]interface{}{
						"app": "nginx",
					},
				},
				"template": map[string]interface{}{
					"metadata": map[string]interface{}{
						"labels": map[string]interface{}{
							"app": "nginx",
						},
					},
					"spec": map[string]interface{}{
						"containers": []interface{}{
							map[string]interface{}{
								"image": "fictional.registry.example/imagename:v1.0.0",
								"name":  "nginx",
							}}}}}}}
}

func generateDeploymentYamlWithTwoContainer() *unstructured.Unstructured {
	return &unstructured.Unstructured{
		Object: map[string]interface{}{
			"apiVersion": "apps/v1",
			"kind":       "Deployment",
			"metadata": map[string]interface{}{
				"name": "nginx",
				"labels": map[string]interface{}{
					"app": "nginx",
				},
			},
			"spec": map[string]interface{}{
				"replicas": 1,
				"selector": map[string]interface{}{
					"matchLabels": map[string]interface{}{
						"app": "nginx",
					},
				},
				"template": map[string]interface{}{
					"metadata": map[string]interface{}{
						"labels": map[string]interface{}{
							"app": "nginx",
						},
					},
					"spec": map[string]interface{}{
						"containers": []interface{}{
							map[string]interface{}{
								"image": "fictional.registry.example/imagename:v1.0.0",
								"name":  "nginx",
							},
							map[string]interface{}{
								"image": "registry.k8s.io/nginx-slim:0.8",
								"name":  "nginx",
							}}}}}}}
}

func generateJobYaml() *unstructured.Unstructured {
	return &unstructured.Unstructured{
		Object: map[string]interface{}{
			"apiVersion": "batch/v1",
			"kind":       "Job",
			"metadata": map[string]interface{}{
				"name": "pi",
			},
			"spec": map[string]interface{}{
				"template": map[string]interface{}{
					"spec": map[string]interface{}{
						"containers": []interface{}{
							map[string]interface{}{
								"image": "perl:5.34.0",
								"name":  "perl",
							},
						}}}}}}
}

func TestParseJSONPatchesByImageOverrider(t *testing.T) {
	type args struct {
		rawObj         *unstructured.Unstructured
		imageOverrider *policyv1alpha1.ImageOverrider
	}
	tests := []struct {
		name    string
		args    args
		want    []overrideOption
		wantErr bool
	}{
		{
			name: "imageOverrider with empty predicate, resource kind: Job, component: Registry, operator: add",
			args: args{
				rawObj: generateJobYaml(),
				imageOverrider: &policyv1alpha1.ImageOverrider{
					Component: "Registry",
					Operator:  "add",
					Value:     "registry.k8s.io",
				},
			},
			want: []overrideOption{
				{
					Op:    "replace",
					Path:  "/spec/template/spec/containers/0/image",
					Value: "registry.k8s.io/perl:5.34.0",
				},
			},
			wantErr: false,
		},
		{
			name: "imageOverrider with predicate, resource kind: Job, component: Registry, operator: add",
			args: args{
				rawObj: generateJobYaml(),
				imageOverrider: &policyv1alpha1.ImageOverrider{
					Predicate: &policyv1alpha1.ImagePredicate{
						Path: "/spec/template/spec/containers/0/image",
					},
					Component: "Registry",
					Operator:  "add",
					Value:     "registry.k8s.io",
				},
			},
			want: []overrideOption{
				{
					Op:    "replace",
					Path:  "/spec/template/spec/containers/0/image",
					Value: "registry.k8s.io/perl:5.34.0",
				},
			},
			wantErr: false,
		},
		{
			name: "imageOverrider with empty predicate, resource kind: Deployment, component: Registry, operator: add",
			args: args{
				rawObj: generateDeploymentYaml(),
				imageOverrider: &policyv1alpha1.ImageOverrider{
					Component: "Registry",
					Operator:  "add",
					Value:     ".test",
				},
			},
			want: []overrideOption{
				{
					Op:    "replace",
					Path:  "/spec/template/spec/containers/0/image",
					Value: "fictional.registry.example.test/imagename:v1.0.0",
				},
			},
			wantErr: false,
		},
		{
			name: "imageOverrider with empty predicate, resource kind: Deployment, component: Registry, operator: replace",
			args: args{
				rawObj: generateDeploymentYaml(),
				imageOverrider: &policyv1alpha1.ImageOverrider{
					Component: "Registry",
					Operator:  "replace",
					Value:     "fictional.registry.us",
				},
			},
			want: []overrideOption{
				{
					Op:    "replace",
					Path:  "/spec/template/spec/containers/0/image",
					Value: "fictional.registry.us/imagename:v1.0.0",
				},
			},
			wantErr: false,
		},
		{
			name: "imageOverrider with empty predicate, resource kind: Deployment, component: Registry, operator: remove",
			args: args{
				rawObj: generateDeploymentYaml(),
				imageOverrider: &policyv1alpha1.ImageOverrider{
					Component: "Registry",
					Operator:  "remove",
					Value:     "fictional.registry.us",
				},
			},
			want: []overrideOption{
				{
					Op:    "replace",
					Path:  "/spec/template/spec/containers/0/image",
					Value: "imagename:v1.0.0",
				},
			},
			wantErr: false,
		},
		{
			name: "imageOverrider with empty predicate, resource kind: Deployment, component: Repository, operator: add",
			args: args{
				rawObj: generateDeploymentYaml(),
				imageOverrider: &policyv1alpha1.ImageOverrider{
					Component: "Repository",
					Operator:  "add",
					Value:     "/nginx",
				},
			},
			want: []overrideOption{
				{
					Op:    "replace",
					Path:  "/spec/template/spec/containers/0/image",
					Value: "fictional.registry.example/imagename/nginx:v1.0.0",
				},
			},
			wantErr: false,
		},
		{
			name: "imageOverrider with empty predicate, resource kind: Deployment, component: Repository, operator: replace",
			args: args{
				rawObj: generateDeploymentYaml(),
				imageOverrider: &policyv1alpha1.ImageOverrider{
					Component: "Repository",
					Operator:  "replace",
					Value:     "nginx",
				},
			},
			want: []overrideOption{
				{
					Op:    "replace",
					Path:  "/spec/template/spec/containers/0/image",
					Value: "fictional.registry.example/nginx:v1.0.0",
				},
			},
			wantErr: false,
		},
		{
			name: "imageOverrider with empty predicate, resource kind: Deployment, component: Repository, operator: remove",
			args: args{
				rawObj: generateDeploymentYaml(),
				imageOverrider: &policyv1alpha1.ImageOverrider{
					Component: "Repository",
					Operator:  "remove",
					Value:     "nginx",
				},
			},
			want: []overrideOption{
				{
					Op:    "replace",
					Path:  "/spec/template/spec/containers/0/image",
					Value: "fictional.registry.example/:v1.0.0",
				},
			},
			wantErr: false,
		},
		{
			name: "imageOverrider with empty predicate, resource kind: Deployment, component: Tag, operator: add",
			args: args{
				rawObj: generateDeploymentYaml(),
				imageOverrider: &policyv1alpha1.ImageOverrider{
					Component: "Tag",
					Operator:  "add",
					Value:     "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
				},
			},
			want: []overrideOption{
				{
					Op:    "replace",
					Path:  "/spec/template/spec/containers/0/image",
					Value: "fictional.registry.example/imagename:v1.0.0", // only one of tag and digest is valid.
				},
			},
			wantErr: false,
		},
		{
			name: "imageOverrider with empty predicate, resource kind: Deployment, component: Tag, operator: replace",
			args: args{
				rawObj: generateDeploymentYaml(),
				imageOverrider: &policyv1alpha1.ImageOverrider{
					Component: "Tag",
					Operator:  "replace",
					Value:     "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
				},
			},
			want: []overrideOption{
				{
					Op:    "replace",
					Path:  "/spec/template/spec/containers/0/image",
					Value: "fictional.registry.example/imagename@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
				},
			},
			wantErr: false,
		},
		{
			name: "imageOverrider with empty predicate, resource kind: Deployment, component: Tag, operator: remove",
			args: args{
				rawObj: generateDeploymentYaml(),
				imageOverrider: &policyv1alpha1.ImageOverrider{
					Component: "Tag",
					Operator:  "remove",
					Value:     "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
				},
			},
			want: []overrideOption{
				{
					Op:    "replace",
					Path:  "/spec/template/spec/containers/0/image",
					Value: "fictional.registry.example/imagename",
				},
			},
			wantErr: false,
		},
		{
			name: "imageOverrider with empty predicate, resource kind: Pod",
			args: args{
				rawObj: generatePodYaml(),
				imageOverrider: &policyv1alpha1.ImageOverrider{
					Component: "Repository",
					Operator:  "replace",
					Value:     "nginx",
				},
			},
			want: []overrideOption{
				{
					Op:    "replace",
					Path:  "/spec/containers/0/image",
					Value: "fictional.registry.example/nginx:v1.0.0",
				},
			},
			wantErr: false,
		},
		{
			name: "imageOverrider with empty predicate, resource kind: StatefulSet",
			args: args{
				rawObj: generateStatefulSetYaml(),
				imageOverrider: &policyv1alpha1.ImageOverrider{
					Component: "Repository",
					Operator:  "replace",
					Value:     "nginx",
				},
			},
			want: []overrideOption{
				{
					Op:    "replace",
					Path:  "/spec/template/spec/containers/0/image",
					Value: "fictional.registry.example/nginx:v1.0.0",
				},
			},
			wantErr: false,
		},
		{
			name: "imageOverrider with empty predicate, resource kind: ReplicaSet",
			args: args{
				rawObj: generateReplicaSetYaml(),
				imageOverrider: &policyv1alpha1.ImageOverrider{
					Component: "Repository",
					Operator:  "replace",
					Value:     "nginx",
				},
			},
			want: []overrideOption{
				{
					Op:    "replace",
					Path:  "/spec/template/spec/containers/0/image",
					Value: "fictional.registry.example/nginx:v1.0.0",
				},
			},
			wantErr: false,
		},
		{
			name: "imageOverrider with empty predicate, resource kind: DaemonSet",
			args: args{
				rawObj: generateDaemonSetYaml(),
				imageOverrider: &policyv1alpha1.ImageOverrider{
					Component: "Repository",
					Operator:  "replace",
					Value:     "nginx",
				},
			},
			want: []overrideOption{
				{
					Op:    "replace",
					Path:  "/spec/template/spec/containers/0/image",
					Value: "fictional.registry.example/nginx:v1.0.0",
				},
			},
			wantErr: false,
		},
		{
			name: "imageOverrider with empty predicate, resource has more than one container",
			args: args{
				rawObj: generateDeploymentYamlWithTwoContainer(),
				imageOverrider: &policyv1alpha1.ImageOverrider{
					Component: "Repository",
					Operator:  "replace",
					Value:     "nginx",
				},
			},
			want: []overrideOption{
				{
					Op:    "replace",
					Path:  "/spec/template/spec/containers/0/image",
					Value: "fictional.registry.example/nginx:v1.0.0",
				},
				{
					Op:    "replace",
					Path:  "/spec/template/spec/containers/1/image",
					Value: "registry.k8s.io/nginx:0.8",
				},
			},
			wantErr: false,
		},
		{
			name: "imageOverrider with predicate, resource has one container",
			args: args{
				rawObj: generateDeploymentYaml(),
				imageOverrider: &policyv1alpha1.ImageOverrider{
					Predicate: &policyv1alpha1.ImagePredicate{
						Path: "/spec/template/spec/containers/0/image",
					},
					Component: "Repository",
					Operator:  "replace",
					Value:     "nginx",
				},
			},
			want: []overrideOption{
				{
					Op:    "replace",
					Path:  "/spec/template/spec/containers/0/image",
					Value: "fictional.registry.example/nginx:v1.0.0",
				},
			},
			wantErr: false,
		},
		{
			name: "imageOverrider with predicate, resource has more than one container",
			args: args{
				rawObj: generateDeploymentYamlWithTwoContainer(),
				imageOverrider: &policyv1alpha1.ImageOverrider{
					Predicate: &policyv1alpha1.ImagePredicate{
						Path: "/spec/template/spec/containers/1/image",
					},
					Component: "Repository",
					Operator:  "replace",
					Value:     "nginx",
				},
			},
			want: []overrideOption{
				{
					Op:    "replace",
					Path:  "/spec/template/spec/containers/1/image",
					Value: "registry.k8s.io/nginx:0.8",
				},
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := buildPatches(tt.args.rawObj, tt.args.imageOverrider)
			if (err != nil) != tt.wantErr {
				t.Errorf("buildPatches() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("buildPatches() = %v, want %v", got, tt.want)
			}
		})
	}
}
