// Copyright The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kubernetes

import (
	"context"
	"fmt"
	"maps"
	"testing"

	"github.com/prometheus/common/model"
	appsv1 "k8s.io/api/apps/v1"
	batchv1 "k8s.io/api/batch/v1"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"

	"github.com/prometheus/prometheus/discovery/targetgroup"
)

func makeOptionalBool(v bool) *bool {
	return &v
}

func makeMultiPortPods() *v1.Pod {
	return &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:        "testpod",
			Namespace:   "default",
			Labels:      map[string]string{"test/label": "testvalue"},
			Annotations: map[string]string{"test/annotation": "testannotationvalue"},
			UID:         types.UID("abc123"),
			OwnerReferences: []metav1.OwnerReference{
				{
					Kind:       "testcontrollerkind",
					Name:       "testcontrollername",
					Controller: makeOptionalBool(true),
				},
			},
		},
		Spec: v1.PodSpec{
			NodeName: "testnode",
			Containers: []v1.Container{
				{
					Name:  "testcontainer0",
					Image: "testcontainer0:latest",
					Ports: []v1.ContainerPort{
						{
							Name:          "testport0",
							Protocol:      v1.ProtocolTCP,
							ContainerPort: int32(9000),
						},
						{
							Name:          "testport1",
							Protocol:      v1.ProtocolUDP,
							ContainerPort: int32(9001),
						},
					},
				},
				{
					Name:  "testcontainer1",
					Image: "testcontainer1:latest",
				},
			},
		},
		Status: v1.PodStatus{
			PodIP:  "1.2.3.4",
			HostIP: "2.3.4.5",
			Phase:  "Running",
			Conditions: []v1.PodCondition{
				{
					Type:   v1.PodReady,
					Status: v1.ConditionTrue,
				},
			},
			ContainerStatuses: []v1.ContainerStatus{
				{
					Name:        "testcontainer0",
					ContainerID: "docker://a1b2c3d4e5f6",
				},
				{
					Name:        "testcontainer1",
					ContainerID: "containerd://6f5e4d3c2b1a",
				},
			},
		},
	}
}

func makePods(namespace string) *v1.Pod {
	return &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "testpod",
			Namespace: namespace,
			UID:       types.UID("abc123"),
		},
		Spec: v1.PodSpec{
			NodeName: "testnode",
			Containers: []v1.Container{
				{
					Name:  "testcontainer",
					Image: "testcontainer:latest",
					Ports: []v1.ContainerPort{
						{
							Name:          "testport",
							Protocol:      v1.ProtocolTCP,
							ContainerPort: int32(9000),
						},
					},
				},
			},
		},
		Status: v1.PodStatus{
			PodIP:  "1.2.3.4",
			HostIP: "2.3.4.5",
			Phase:  "Running",
			Conditions: []v1.PodCondition{
				{
					Type:   v1.PodReady,
					Status: v1.ConditionTrue,
				},
			},
			ContainerStatuses: []v1.ContainerStatus{
				{
					Name:        "testcontainer",
					ContainerID: "docker://a1b2c3d4e5f6",
				},
			},
		},
	}
}

func makeInitContainerPods() *v1.Pod {
	return &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "testpod",
			Namespace: "default",
			UID:       types.UID("abc123"),
		},
		Spec: v1.PodSpec{
			NodeName: "testnode",
			Containers: []v1.Container{
				{
					Name:  "testcontainer",
					Image: "testcontainer:latest",
					Ports: []v1.ContainerPort{
						{
							Name:          "testport",
							Protocol:      v1.ProtocolTCP,
							ContainerPort: int32(9000),
						},
					},
				},
			},

			InitContainers: []v1.Container{
				{
					Name:  "initcontainer",
					Image: "initcontainer:latest",
				},
			},
		},
		Status: v1.PodStatus{
			PodIP:  "1.2.3.4",
			HostIP: "2.3.4.5",
			Phase:  "Pending",
			Conditions: []v1.PodCondition{
				{
					Type:   v1.PodReady,
					Status: v1.ConditionFalse,
				},
			},
			ContainerStatuses: []v1.ContainerStatus{
				{
					Name:        "testcontainer",
					ContainerID: "docker://a1b2c3d4e5f6",
				},
			},
			InitContainerStatuses: []v1.ContainerStatus{
				{
					Name:        "initcontainer",
					ContainerID: "containerd://6f5e4d3c2b1a",
				},
			},
		},
	}
}

func expectedPodTargetGroups(ns string) map[string]*targetgroup.Group {
	key := fmt.Sprintf("pod/%s/testpod", ns)
	return map[string]*targetgroup.Group{
		key: {
			Targets: []model.LabelSet{
				{
					"__address__":                                   "1.2.3.4:9000",
					"__meta_kubernetes_pod_container_name":          "testcontainer",
					"__meta_kubernetes_pod_container_image":         "testcontainer:latest",
					"__meta_kubernetes_pod_container_port_name":     "testport",
					"__meta_kubernetes_pod_container_port_number":   "9000",
					"__meta_kubernetes_pod_container_port_protocol": "TCP",
					"__meta_kubernetes_pod_container_init":          "false",
					"__meta_kubernetes_pod_container_id":            "docker://a1b2c3d4e5f6",
				},
			},
			Labels: model.LabelSet{
				"__meta_kubernetes_pod_name":      "testpod",
				"__meta_kubernetes_namespace":     lv(ns),
				"__meta_kubernetes_pod_node_name": "testnode",
				"__meta_kubernetes_pod_ip":        "1.2.3.4",
				"__meta_kubernetes_pod_host_ip":   "2.3.4.5",
				"__meta_kubernetes_pod_ready":     "true",
				"__meta_kubernetes_pod_phase":     "Running",
				"__meta_kubernetes_pod_uid":       "abc123",
			},
			Source: key,
		},
	}
}

func expectedPodTargetGroupsWithNodeMeta(ns, nodeName string, nodeLabels map[string]string) map[string]*targetgroup.Group {
	result := expectedPodTargetGroups(ns)
	for _, tg := range result {
		tg.Labels["__meta_kubernetes_node_name"] = lv(nodeName)
		for k, v := range nodeLabels {
			tg.Labels[model.LabelName("__meta_kubernetes_node_label_"+k)] = lv(v)
			tg.Labels[model.LabelName("__meta_kubernetes_node_labelpresent_"+k)] = lv("true")
		}
	}

	return result
}

func TestPodDiscoveryBeforeRun(t *testing.T) {
	t.Parallel()
	n, c := makeDiscovery(RolePod, NamespaceDiscovery{})

	k8sDiscoveryTest{
		discovery: n,
		beforeRun: func() {
			obj := makeMultiPortPods()
			c.CoreV1().Pods(obj.Namespace).Create(context.Background(), obj, metav1.CreateOptions{})
		},
		expectedMaxItems: 1,
		expectedRes: map[string]*targetgroup.Group{
			"pod/default/testpod": {
				Targets: []model.LabelSet{
					{
						"__address__":                                   "1.2.3.4:9000",
						"__meta_kubernetes_pod_container_name":          "testcontainer0",
						"__meta_kubernetes_pod_container_image":         "testcontainer0:latest",
						"__meta_kubernetes_pod_container_port_name":     "testport0",
						"__meta_kubernetes_pod_container_port_number":   "9000",
						"__meta_kubernetes_pod_container_port_protocol": "TCP",
						"__meta_kubernetes_pod_container_init":          "false",
						"__meta_kubernetes_pod_container_id":            "docker://a1b2c3d4e5f6",
					},
					{
						"__address__":                                   "1.2.3.4:9001",
						"__meta_kubernetes_pod_container_name":          "testcontainer0",
						"__meta_kubernetes_pod_container_image":         "testcontainer0:latest",
						"__meta_kubernetes_pod_container_port_name":     "testport1",
						"__meta_kubernetes_pod_container_port_number":   "9001",
						"__meta_kubernetes_pod_container_port_protocol": "UDP",
						"__meta_kubernetes_pod_container_init":          "false",
						"__meta_kubernetes_pod_container_id":            "docker://a1b2c3d4e5f6",
					},
					{
						"__address__":                           "1.2.3.4",
						"__meta_kubernetes_pod_container_name":  "testcontainer1",
						"__meta_kubernetes_pod_container_image": "testcontainer1:latest",
						"__meta_kubernetes_pod_container_init":  "false",
						"__meta_kubernetes_pod_container_id":    "containerd://6f5e4d3c2b1a",
					},
				},
				Labels: model.LabelSet{
					"__meta_kubernetes_pod_name":                              "testpod",
					"__meta_kubernetes_namespace":                             "default",
					"__meta_kubernetes_pod_label_test_label":                  "testvalue",
					"__meta_kubernetes_pod_labelpresent_test_label":           "true",
					"__meta_kubernetes_pod_annotation_test_annotation":        "testannotationvalue",
					"__meta_kubernetes_pod_annotationpresent_test_annotation": "true",
					"__meta_kubernetes_pod_node_name":                         "testnode",
					"__meta_kubernetes_pod_ip":                                "1.2.3.4",
					"__meta_kubernetes_pod_host_ip":                           "2.3.4.5",
					"__meta_kubernetes_pod_ready":                             "true",
					"__meta_kubernetes_pod_phase":                             "Running",
					"__meta_kubernetes_pod_uid":                               "abc123",
					"__meta_kubernetes_pod_controller_kind":                   "testcontrollerkind",
					"__meta_kubernetes_pod_controller_name":                   "testcontrollername",
				},
				Source: "pod/default/testpod",
			},
		},
	}.Run(t)
}

func TestPodDiscoveryInitContainer(t *testing.T) {
	t.Parallel()
	n, c := makeDiscovery(RolePod, NamespaceDiscovery{})

	ns := "default"
	key := fmt.Sprintf("pod/%s/testpod", ns)
	expected := expectedPodTargetGroups(ns)
	expected[key].Targets = append(expected[key].Targets, model.LabelSet{
		"__address__":                           "1.2.3.4",
		"__meta_kubernetes_pod_container_name":  "initcontainer",
		"__meta_kubernetes_pod_container_image": "initcontainer:latest",
		"__meta_kubernetes_pod_container_init":  "true",
		"__meta_kubernetes_pod_container_id":    "containerd://6f5e4d3c2b1a",
	})
	expected[key].Labels["__meta_kubernetes_pod_phase"] = "Pending"
	expected[key].Labels["__meta_kubernetes_pod_ready"] = "false"

	k8sDiscoveryTest{
		discovery: n,
		beforeRun: func() {
			obj := makeInitContainerPods()
			c.CoreV1().Pods(obj.Namespace).Create(context.Background(), obj, metav1.CreateOptions{})
		},
		expectedMaxItems: 1,
		expectedRes:      expected,
	}.Run(t)
}

func TestPodDiscoveryAdd(t *testing.T) {
	t.Parallel()
	n, c := makeDiscovery(RolePod, NamespaceDiscovery{})

	k8sDiscoveryTest{
		discovery: n,
		afterStart: func() {
			obj := makePods("default")
			c.CoreV1().Pods(obj.Namespace).Create(context.Background(), obj, metav1.CreateOptions{})
		},
		expectedMaxItems: 1,
		expectedRes:      expectedPodTargetGroups("default"),
	}.Run(t)
}

func TestPodDiscoveryDelete(t *testing.T) {
	t.Parallel()
	obj := makePods("default")
	n, c := makeDiscovery(RolePod, NamespaceDiscovery{}, obj)

	k8sDiscoveryTest{
		discovery: n,
		afterStart: func() {
			obj := makePods("default")
			c.CoreV1().Pods(obj.Namespace).Delete(context.Background(), obj.Name, metav1.DeleteOptions{})
		},
		expectedMaxItems: 2,
		expectedRes: map[string]*targetgroup.Group{
			"pod/default/testpod": {
				Source: "pod/default/testpod",
			},
		},
	}.Run(t)
}

func TestPodDiscoveryUpdate(t *testing.T) {
	t.Parallel()
	obj := &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "testpod",
			Namespace: "default",
			UID:       "xyz321",
		},
		Spec: v1.PodSpec{
			NodeName: "testnode",
			Containers: []v1.Container{
				{
					Name:  "testcontainer",
					Image: "testcontainer:latest",
					Ports: []v1.ContainerPort{
						{
							Name:          "testport",
							Protocol:      v1.ProtocolTCP,
							ContainerPort: int32(9000),
						},
					},
				},
			},
		},
		Status: v1.PodStatus{
			PodIP:  "1.2.3.4",
			HostIP: "2.3.4.5",
		},
	}
	n, c := makeDiscovery(RolePod, NamespaceDiscovery{}, obj)

	k8sDiscoveryTest{
		discovery: n,
		afterStart: func() {
			obj := makePods("default")
			c.CoreV1().Pods(obj.Namespace).Update(context.Background(), obj, metav1.UpdateOptions{})
		},
		expectedMaxItems: 2,
		expectedRes:      expectedPodTargetGroups("default"),
	}.Run(t)
}

func TestPodDiscoveryUpdateEmptyPodIP(t *testing.T) {
	t.Parallel()
	n, c := makeDiscovery(RolePod, NamespaceDiscovery{})
	initialPod := makePods("default")

	updatedPod := makePods("default")
	updatedPod.Status.PodIP = ""

	k8sDiscoveryTest{
		discovery: n,
		beforeRun: func() {
			c.CoreV1().Pods(initialPod.Namespace).Create(context.Background(), initialPod, metav1.CreateOptions{})
		},
		afterStart: func() {
			c.CoreV1().Pods(updatedPod.Namespace).Update(context.Background(), updatedPod, metav1.UpdateOptions{})
		},
		expectedMaxItems: 2,
		expectedRes: map[string]*targetgroup.Group{
			"pod/default/testpod": {
				Source: "pod/default/testpod",
			},
		},
	}.Run(t)
}

func TestPodDiscoveryNamespaces(t *testing.T) {
	t.Parallel()
	n, c := makeDiscovery(RolePod, NamespaceDiscovery{Names: []string{"ns1", "ns2"}})

	expected := expectedPodTargetGroups("ns1")
	maps.Copy(expected, expectedPodTargetGroups("ns2"))
	k8sDiscoveryTest{
		discovery: n,
		beforeRun: func() {
			for _, ns := range []string{"ns1", "ns2"} {
				pod := makePods("default")
				pod.Namespace = ns
				c.CoreV1().Pods(pod.Namespace).Create(context.Background(), pod, metav1.CreateOptions{})
			}
		},
		expectedMaxItems: 2,
		expectedRes:      expected,
	}.Run(t)
}

func TestPodDiscoveryOwnNamespace(t *testing.T) {
	t.Parallel()
	n, c := makeDiscovery(RolePod, NamespaceDiscovery{IncludeOwnNamespace: true})

	expected := expectedPodTargetGroups("own-ns")
	k8sDiscoveryTest{
		discovery: n,
		beforeRun: func() {
			for _, ns := range []string{"own-ns", "non-own-ns"} {
				pod := makePods("default")
				pod.Namespace = ns
				c.CoreV1().Pods(pod.Namespace).Create(context.Background(), pod, metav1.CreateOptions{})
			}
		},
		expectedMaxItems: 1,
		expectedRes:      expected,
	}.Run(t)
}

func TestPodDiscoveryWithNodeMetadata(t *testing.T) {
	t.Parallel()
	attachMetadata := AttachMetadataConfig{Node: true}
	n, c := makeDiscoveryWithMetadata(RolePod, NamespaceDiscovery{}, attachMetadata)
	nodeLbls := map[string]string{"l1": "v1"}

	k8sDiscoveryTest{
		discovery: n,
		afterStart: func() {
			nodes := makeNode("testnode", "", "", nodeLbls, nil, nil)
			c.CoreV1().Nodes().Create(context.Background(), nodes, metav1.CreateOptions{})

			pods := makePods("default")
			c.CoreV1().Pods(pods.Namespace).Create(context.Background(), pods, metav1.CreateOptions{})
		},
		expectedMaxItems: 2,
		expectedRes:      expectedPodTargetGroupsWithNodeMeta("default", "testnode", nodeLbls),
	}.Run(t)
}

func TestPodDiscoveryWithNodeMetadataUpdateNode(t *testing.T) {
	t.Parallel()
	nodeLbls := map[string]string{"l2": "v2"}
	attachMetadata := AttachMetadataConfig{Node: true}
	n, c := makeDiscoveryWithMetadata(RolePod, NamespaceDiscovery{}, attachMetadata)

	k8sDiscoveryTest{
		discovery: n,
		beforeRun: func() {
			oldNodeLbls := map[string]string{"l1": "v1"}
			nodes := makeNode("testnode", "", "", oldNodeLbls, nil, nil)
			c.CoreV1().Nodes().Create(context.Background(), nodes, metav1.CreateOptions{})
		},
		afterStart: func() {
			pods := makePods("default")
			c.CoreV1().Pods(pods.Namespace).Create(context.Background(), pods, metav1.CreateOptions{})

			nodes := makeNode("testnode", "", "", nodeLbls, nil, nil)
			c.CoreV1().Nodes().Update(context.Background(), nodes, metav1.UpdateOptions{})
		},
		expectedMaxItems: 2,
		expectedRes:      expectedPodTargetGroupsWithNodeMeta("default", "testnode", nodeLbls),
	}.Run(t)
}

func TestPodDiscoveryWithNamespaceMetadata(t *testing.T) {
	t.Parallel()

	ns := "test-ns"
	nsLabels := map[string]string{"app": "web", "tier": "frontend"}
	nsAnnotations := map[string]string{"maintainer": "devops", "build": "v3.4.5"}

	n, _ := makeDiscoveryWithMetadata(RolePod, NamespaceDiscovery{}, AttachMetadataConfig{Namespace: true}, makeNamespace(ns, nsLabels, nsAnnotations), makePods(ns))
	k8sDiscoveryTest{
		discovery:        n,
		expectedMaxItems: 1,
		expectedRes: map[string]*targetgroup.Group{
			fmt.Sprintf("pod/%s/testpod", ns): {
				Targets: []model.LabelSet{
					{
						"__address__":                                   "1.2.3.4:9000",
						"__meta_kubernetes_pod_container_image":         "testcontainer:latest",
						"__meta_kubernetes_pod_container_name":          "testcontainer",
						"__meta_kubernetes_pod_container_port_name":     "testport",
						"__meta_kubernetes_pod_container_port_number":   "9000",
						"__meta_kubernetes_pod_container_port_protocol": "TCP",
						"__meta_kubernetes_pod_container_init":          "false",
						"__meta_kubernetes_pod_container_id":            "docker://a1b2c3d4e5f6",
					},
				},
				Labels: model.LabelSet{
					"__meta_kubernetes_namespace":                              model.LabelValue(ns),
					"__meta_kubernetes_namespace_annotation_maintainer":        "devops",
					"__meta_kubernetes_namespace_annotationpresent_maintainer": "true",
					"__meta_kubernetes_namespace_annotation_build":             "v3.4.5",
					"__meta_kubernetes_namespace_annotationpresent_build":      "true",
					"__meta_kubernetes_namespace_label_app":                    "web",
					"__meta_kubernetes_namespace_labelpresent_app":             "true",
					"__meta_kubernetes_namespace_label_tier":                   "frontend",
					"__meta_kubernetes_namespace_labelpresent_tier":            "true",
					"__meta_kubernetes_pod_name":                               "testpod",
					"__meta_kubernetes_pod_ip":                                 "1.2.3.4",
					"__meta_kubernetes_pod_ready":                              "true",
					"__meta_kubernetes_pod_phase":                              "Running",
					"__meta_kubernetes_pod_node_name":                          "testnode",
					"__meta_kubernetes_pod_host_ip":                            "2.3.4.5",
					"__meta_kubernetes_pod_uid":                                "abc123",
				},
				Source: fmt.Sprintf("pod/%s/testpod", ns),
			},
		},
	}.Run(t)
}

func TestPodDiscoveryWithUpdatedNamespaceMetadata(t *testing.T) {
	t.Parallel()

	ns := "test-ns"
	nsLabels := map[string]string{"app": "api", "tier": "backend"}
	nsAnnotations := map[string]string{"maintainer": "platform", "build": "v4.5.6"}

	namespace := makeNamespace(ns, nsLabels, nsAnnotations)
	n, c := makeDiscoveryWithMetadata(RolePod, NamespaceDiscovery{}, AttachMetadataConfig{Namespace: true}, namespace, makePods(ns))

	k8sDiscoveryTest{
		discovery: n,
		afterStart: func() {
			namespace.Labels["app"] = "service"
			namespace.Labels["zone"] = "us-east"
			namespace.Annotations["maintainer"] = "sre"
			namespace.Annotations["deployment"] = "canary"
			c.CoreV1().Namespaces().Update(context.Background(), namespace, metav1.UpdateOptions{})
		},
		expectedMaxItems: 2,
		expectedRes: map[string]*targetgroup.Group{
			fmt.Sprintf("pod/%s/testpod", ns): {
				Targets: []model.LabelSet{
					{
						"__address__":                                   "1.2.3.4:9000",
						"__meta_kubernetes_pod_container_image":         "testcontainer:latest",
						"__meta_kubernetes_pod_container_name":          "testcontainer",
						"__meta_kubernetes_pod_container_port_name":     "testport",
						"__meta_kubernetes_pod_container_port_number":   "9000",
						"__meta_kubernetes_pod_container_port_protocol": "TCP",
						"__meta_kubernetes_pod_container_init":          "false",
						"__meta_kubernetes_pod_container_id":            "docker://a1b2c3d4e5f6",
					},
				},
				Labels: model.LabelSet{
					"__meta_kubernetes_namespace":                              model.LabelValue(ns),
					"__meta_kubernetes_namespace_annotation_maintainer":        "sre",
					"__meta_kubernetes_namespace_annotationpresent_maintainer": "true",
					"__meta_kubernetes_namespace_annotation_build":             "v4.5.6",
					"__meta_kubernetes_namespace_annotationpresent_build":      "true",
					"__meta_kubernetes_namespace_annotation_deployment":        "canary",
					"__meta_kubernetes_namespace_annotationpresent_deployment": "true",
					"__meta_kubernetes_namespace_label_app":                    "service",
					"__meta_kubernetes_namespace_labelpresent_app":             "true",
					"__meta_kubernetes_namespace_label_tier":                   "backend",
					"__meta_kubernetes_namespace_labelpresent_tier":            "true",
					"__meta_kubernetes_namespace_label_zone":                   "us-east",
					"__meta_kubernetes_namespace_labelpresent_zone":            "true",
					"__meta_kubernetes_pod_name":                               "testpod",
					"__meta_kubernetes_pod_ip":                                 "1.2.3.4",
					"__meta_kubernetes_pod_ready":                              "true",
					"__meta_kubernetes_pod_phase":                              "Running",
					"__meta_kubernetes_pod_node_name":                          "testnode",
					"__meta_kubernetes_pod_host_ip":                            "2.3.4.5",
					"__meta_kubernetes_pod_uid":                                "abc123",
				},
				Source: fmt.Sprintf("pod/%s/testpod", ns),
			},
		},
	}.Run(t)
}

func TestPodDiscoveryWithNodeSelector(t *testing.T) {
	t.Parallel()

	workerNode := makeNode("worker-node", "10.0.0.1", "", map[string]string{"node-type": "worker"}, nil, nil)
	filteredNode := makeNode("filtered-node", "10.0.0.2", "", map[string]string{"node-type": "master"}, nil, nil)

	attachMetadata := AttachMetadataConfig{
		Node: true, // necessary for node role selectos to work for pod role
	}
	n, c := makeDiscoveryWithMetadata(RolePod, NamespaceDiscovery{}, attachMetadata, workerNode, filteredNode)
	n.selectors = roleSelector{
		node: resourceSelector{
			label: "node-type=worker",
		},
	}

	podOnWorker := makePods("default")
	podOnWorker.Name = "pod-on-worker"
	podOnWorker.UID = types.UID("worker-pod-123")
	podOnWorker.Spec.NodeName = "worker-node"
	podOnWorker.Status.PodIP = "192.168.1.1"

	podOnFilteredNode := makePods("default")
	podOnFilteredNode.Name = "pod-on-filtered-node"
	podOnFilteredNode.UID = types.UID("filtered-pod-456")
	podOnFilteredNode.Spec.NodeName = "filtered-node"
	podOnFilteredNode.Status.PodIP = "192.168.1.2"

	k8sDiscoveryTest{
		discovery: n,
		beforeRun: func() {
			c.CoreV1().Pods("default").Create(context.Background(), podOnWorker, metav1.CreateOptions{})
			c.CoreV1().Pods("default").Create(context.Background(), podOnFilteredNode, metav1.CreateOptions{})
		},
		expectedMaxItems: 2,
		expectedRes: map[string]*targetgroup.Group{
			"pod/default/pod-on-worker": {
				Targets: []model.LabelSet{
					{
						"__address__":                                   "192.168.1.1:9000",
						"__meta_kubernetes_pod_container_image":         "testcontainer:latest",
						"__meta_kubernetes_pod_container_name":          "testcontainer",
						"__meta_kubernetes_pod_container_port_name":     "testport",
						"__meta_kubernetes_pod_container_port_number":   "9000",
						"__meta_kubernetes_pod_container_port_protocol": "TCP",
						"__meta_kubernetes_pod_container_init":          "false",
						"__meta_kubernetes_pod_container_id":            "docker://a1b2c3d4e5f6",
					},
				},
				Labels: model.LabelSet{
					"__meta_kubernetes_namespace":                   "default",
					"__meta_kubernetes_pod_name":                    "pod-on-worker",
					"__meta_kubernetes_pod_ip":                      "192.168.1.1",
					"__meta_kubernetes_pod_ready":                   "true",
					"__meta_kubernetes_pod_phase":                   "Running",
					"__meta_kubernetes_pod_node_name":               "worker-node",
					"__meta_kubernetes_pod_host_ip":                 "2.3.4.5",
					"__meta_kubernetes_pod_uid":                     "worker-pod-123",
					"__meta_kubernetes_node_name":                   "worker-node",
					"__meta_kubernetes_node_label_node_type":        "worker",
					"__meta_kubernetes_node_labelpresent_node_type": "true",
				},
				Source: "pod/default/pod-on-worker",
			},
			"pod/default/pod-on-filtered-node": {
				Source: "pod/default/pod-on-filtered-node",
			},
		},
	}.Run(t)
}

func makeReplicaSet(name, namespace, deploymentName string, deploymentUID types.UID) *appsv1.ReplicaSet {
	return &appsv1.ReplicaSet{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: namespace,
			UID:       types.UID("rs123"),
			OwnerReferences: []metav1.OwnerReference{
				{
					Kind:       "Deployment",
					Name:       deploymentName,
					UID:        deploymentUID,
					Controller: makeOptionalBool(true),
				},
			},
		},
	}
}

func makeDeployment(name, namespace string) *appsv1.Deployment {
	return &appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: namespace,
			UID:       types.UID("deploy123"),
		},
	}
}

func makeJob(name, namespace, cronJobName string, cronJobUID types.UID) *batchv1.Job {
	job := &batchv1.Job{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: namespace,
			UID:       types.UID("job123"),
		},
	}

	if cronJobName != "" {
		job.OwnerReferences = []metav1.OwnerReference{
			{
				Kind:       "CronJob",
				Name:       cronJobName,
				UID:        cronJobUID,
				Controller: makeOptionalBool(true),
			},
		}
	}

	return job
}

func makeCronJob(name, namespace string) *batchv1.CronJob {
	return &batchv1.CronJob{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: namespace,
			UID:       types.UID("cronjob123"),
		},
	}
}

func makeReplicaSetOwnedPod(namespace, replicaSetName string, replicaSetUID types.UID) *v1.Pod {
	return &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "testpod",
			Namespace: namespace,
			UID:       types.UID("pod123"),
			OwnerReferences: []metav1.OwnerReference{
				{
					Kind:       "ReplicaSet",
					Name:       replicaSetName,
					UID:        replicaSetUID,
					Controller: makeOptionalBool(true),
				},
			},
		},
		Spec: v1.PodSpec{
			NodeName: "testnode",
			Containers: []v1.Container{
				{
					Name:  "testcontainer",
					Image: "testcontainer:latest",
					Ports: []v1.ContainerPort{
						{
							Name:          "testport",
							Protocol:      v1.ProtocolTCP,
							ContainerPort: int32(9000),
						},
					},
				},
			},
		},
		Status: v1.PodStatus{
			PodIP:  "1.2.3.4",
			HostIP: "2.3.4.5",
			Phase:  "Running",
			Conditions: []v1.PodCondition{
				{
					Type:   v1.PodReady,
					Status: v1.ConditionTrue,
				},
			},
			ContainerStatuses: []v1.ContainerStatus{
				{
					Name:        "testcontainer",
					ContainerID: "docker://a1b2c3d4e5f6",
				},
			},
		},
	}
}

func makeJobOwnedPod(namespace, jobName string, jobUID types.UID) *v1.Pod {
	return &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "testpod",
			Namespace: namespace,
			UID:       types.UID("pod123"),
			OwnerReferences: []metav1.OwnerReference{
				{
					Kind:       "Job",
					Name:       jobName,
					UID:        jobUID,
					Controller: makeOptionalBool(true),
				},
			},
		},
		Spec: v1.PodSpec{
			NodeName: "testnode",
			Containers: []v1.Container{
				{
					Name:  "testcontainer",
					Image: "testcontainer:latest",
					Ports: []v1.ContainerPort{
						{
							Name:          "testport",
							Protocol:      v1.ProtocolTCP,
							ContainerPort: int32(9000),
						},
					},
				},
			},
		},
		Status: v1.PodStatus{
			PodIP:  "1.2.3.4",
			HostIP: "2.3.4.5",
			Phase:  "Running",
			Conditions: []v1.PodCondition{
				{
					Type:   v1.PodReady,
					Status: v1.ConditionTrue,
				},
			},
			ContainerStatuses: []v1.ContainerStatus{
				{
					Name:        "testcontainer",
					ContainerID: "docker://a1b2c3d4e5f6",
				},
			},
		},
	}
}

func TestPodDiscoveryWithDeployment(t *testing.T) {
	t.Parallel()

	deployment := makeDeployment("testdeployment", "default")
	replicaSet := makeReplicaSet("testdeployment-rs-abc", "default", deployment.Name, deployment.UID)
	pod := makeReplicaSetOwnedPod("default", replicaSet.Name, replicaSet.UID)

	n, _ := makeDiscoveryWithMetadata(RolePod, NamespaceDiscovery{}, AttachMetadataConfig{
		PodMetadataConfig: PodMetadataConfig{Deployment: true},
	}, pod, replicaSet, deployment)

	k8sDiscoveryTest{
		discovery:        n,
		expectedMaxItems: 1,
		expectedRes: map[string]*targetgroup.Group{
			"pod/default/testpod": {
				Targets: []model.LabelSet{
					{
						"__address__":                                   "1.2.3.4:9000",
						"__meta_kubernetes_pod_container_name":          "testcontainer",
						"__meta_kubernetes_pod_container_image":         "testcontainer:latest",
						"__meta_kubernetes_pod_container_port_name":     "testport",
						"__meta_kubernetes_pod_container_port_number":   "9000",
						"__meta_kubernetes_pod_container_port_protocol": "TCP",
						"__meta_kubernetes_pod_container_init":          "false",
						"__meta_kubernetes_pod_container_id":            "docker://a1b2c3d4e5f6",
					},
				},
				Labels: model.LabelSet{
					"__meta_kubernetes_pod_name":            "testpod",
					"__meta_kubernetes_namespace":           "default",
					"__meta_kubernetes_pod_node_name":       "testnode",
					"__meta_kubernetes_pod_ip":              "1.2.3.4",
					"__meta_kubernetes_pod_host_ip":         "2.3.4.5",
					"__meta_kubernetes_pod_ready":           "true",
					"__meta_kubernetes_pod_phase":           "Running",
					"__meta_kubernetes_pod_uid":             "pod123",
					"__meta_kubernetes_pod_controller_kind": "ReplicaSet",
					"__meta_kubernetes_pod_controller_name": "testdeployment-rs-abc",
					"__meta_kubernetes_pod_deployment_name": "testdeployment",
				},
				Source: "pod/default/testpod",
			},
		},
	}.Run(t)
}

func TestPodDiscoveryWithJob(t *testing.T) {
	t.Parallel()

	job := makeJob("testjob", "default", "", "")
	pod := makeJobOwnedPod("default", job.Name, job.UID)

	n, _ := makeDiscoveryWithMetadata(RolePod, NamespaceDiscovery{}, AttachMetadataConfig{
		PodMetadataConfig: PodMetadataConfig{Job: true},
	}, pod, job)

	k8sDiscoveryTest{
		discovery:        n,
		expectedMaxItems: 1,
		expectedRes: map[string]*targetgroup.Group{
			"pod/default/testpod": {
				Targets: []model.LabelSet{
					{
						"__address__":                                   "1.2.3.4:9000",
						"__meta_kubernetes_pod_container_name":          "testcontainer",
						"__meta_kubernetes_pod_container_image":         "testcontainer:latest",
						"__meta_kubernetes_pod_container_port_name":     "testport",
						"__meta_kubernetes_pod_container_port_number":   "9000",
						"__meta_kubernetes_pod_container_port_protocol": "TCP",
						"__meta_kubernetes_pod_container_init":          "false",
						"__meta_kubernetes_pod_container_id":            "docker://a1b2c3d4e5f6",
					},
				},
				Labels: model.LabelSet{
					"__meta_kubernetes_pod_name":            "testpod",
					"__meta_kubernetes_namespace":           "default",
					"__meta_kubernetes_pod_node_name":       "testnode",
					"__meta_kubernetes_pod_ip":              "1.2.3.4",
					"__meta_kubernetes_pod_host_ip":         "2.3.4.5",
					"__meta_kubernetes_pod_ready":           "true",
					"__meta_kubernetes_pod_phase":           "Running",
					"__meta_kubernetes_pod_uid":             "pod123",
					"__meta_kubernetes_pod_controller_kind": "Job",
					"__meta_kubernetes_pod_controller_name": "testjob",
					"__meta_kubernetes_pod_job_name":        "testjob",
				},
				Source: "pod/default/testpod",
			},
		},
	}.Run(t)
}

func TestPodDiscoveryWithCronJob(t *testing.T) {
	t.Parallel()

	cronJob := makeCronJob("testcronjob", "default")
	job := makeJob("testcronjob-job-abc", "default", cronJob.Name, cronJob.UID)
	pod := makeJobOwnedPod("default", job.Name, job.UID)

	n, _ := makeDiscoveryWithMetadata(RolePod, NamespaceDiscovery{}, AttachMetadataConfig{
		PodMetadataConfig: PodMetadataConfig{Job: true},
	}, pod, job, cronJob)

	k8sDiscoveryTest{
		discovery:        n,
		expectedMaxItems: 1,
		expectedRes: map[string]*targetgroup.Group{
			"pod/default/testpod": {
				Targets: []model.LabelSet{
					{
						"__address__":                                   "1.2.3.4:9000",
						"__meta_kubernetes_pod_container_name":          "testcontainer",
						"__meta_kubernetes_pod_container_image":         "testcontainer:latest",
						"__meta_kubernetes_pod_container_port_name":     "testport",
						"__meta_kubernetes_pod_container_port_number":   "9000",
						"__meta_kubernetes_pod_container_port_protocol": "TCP",
						"__meta_kubernetes_pod_container_init":          "false",
						"__meta_kubernetes_pod_container_id":            "docker://a1b2c3d4e5f6",
					},
				},
				Labels: model.LabelSet{
					"__meta_kubernetes_pod_name":            "testpod",
					"__meta_kubernetes_namespace":           "default",
					"__meta_kubernetes_pod_node_name":       "testnode",
					"__meta_kubernetes_pod_ip":              "1.2.3.4",
					"__meta_kubernetes_pod_host_ip":         "2.3.4.5",
					"__meta_kubernetes_pod_ready":           "true",
					"__meta_kubernetes_pod_phase":           "Running",
					"__meta_kubernetes_pod_uid":             "pod123",
					"__meta_kubernetes_pod_controller_kind": "Job",
					"__meta_kubernetes_pod_controller_name": "testcronjob-job-abc",
					"__meta_kubernetes_pod_job_name":        "testcronjob-job-abc",
				},
				Source: "pod/default/testpod",
			},
		},
	}.Run(t)
}

func TestPodDiscoveryWithDeploymentUpdate(t *testing.T) {
	t.Parallel()

	deployment := makeDeployment("testdeployment", "default")
	deployment.Labels = map[string]string{"version": "v1"}

	replicaSet := makeReplicaSet("testdeployment-rs-abc", "default", deployment.Name, deployment.UID)
	pod := makeReplicaSetOwnedPod("default", replicaSet.Name, replicaSet.UID)

	n, c := makeDiscoveryWithMetadata(RolePod, NamespaceDiscovery{}, AttachMetadataConfig{
		PodMetadataConfig: PodMetadataConfig{Deployment: true},
	}, pod, replicaSet, deployment)

	k8sDiscoveryTest{
		discovery: n,
		afterStart: func() {
			// Update deployment with new labels
			deployment.Labels["version"] = "v2"
			deployment.Labels["env"] = "prod"
			c.AppsV1().Deployments(deployment.Namespace).Update(context.Background(), deployment, metav1.UpdateOptions{})
		},
		expectedMaxItems: 2,
		expectedRes: map[string]*targetgroup.Group{
			"pod/default/testpod": {
				Targets: []model.LabelSet{
					{
						"__address__":                                   "1.2.3.4:9000",
						"__meta_kubernetes_pod_container_name":          "testcontainer",
						"__meta_kubernetes_pod_container_image":         "testcontainer:latest",
						"__meta_kubernetes_pod_container_port_name":     "testport",
						"__meta_kubernetes_pod_container_port_number":   "9000",
						"__meta_kubernetes_pod_container_port_protocol": "TCP",
						"__meta_kubernetes_pod_container_init":          "false",
						"__meta_kubernetes_pod_container_id":            "docker://a1b2c3d4e5f6",
					},
				},
				Labels: model.LabelSet{
					"__meta_kubernetes_pod_name":            "testpod",
					"__meta_kubernetes_namespace":           "default",
					"__meta_kubernetes_pod_node_name":       "testnode",
					"__meta_kubernetes_pod_ip":              "1.2.3.4",
					"__meta_kubernetes_pod_host_ip":         "2.3.4.5",
					"__meta_kubernetes_pod_ready":           "true",
					"__meta_kubernetes_pod_phase":           "Running",
					"__meta_kubernetes_pod_uid":             "pod123",
					"__meta_kubernetes_pod_controller_kind": "ReplicaSet",
					"__meta_kubernetes_pod_controller_name": "testdeployment-rs-abc",
					"__meta_kubernetes_pod_deployment_name": "testdeployment",
				},
				Source: "pod/default/testpod",
			},
		},
	}.Run(t)
}

func TestPodDiscoveryWithJobUpdate(t *testing.T) {
	t.Parallel()

	job := makeJob("testjob", "default", "", "")
	job.Labels = map[string]string{"batch": "v1"}

	pod := makeJobOwnedPod("default", job.Name, job.UID)

	n, c := makeDiscoveryWithMetadata(RolePod, NamespaceDiscovery{}, AttachMetadataConfig{
		PodMetadataConfig: PodMetadataConfig{Job: true},
	}, pod, job)

	k8sDiscoveryTest{
		discovery: n,
		afterStart: func() {
			// Update job with new labels
			job.Labels["batch"] = "v2"
			job.Labels["priority"] = "high"
			c.BatchV1().Jobs(job.Namespace).Update(context.Background(), job, metav1.UpdateOptions{})
		},
		expectedMaxItems: 2,
		expectedRes: map[string]*targetgroup.Group{
			"pod/default/testpod": {
				Targets: []model.LabelSet{
					{
						"__address__":                                   "1.2.3.4:9000",
						"__meta_kubernetes_pod_container_name":          "testcontainer",
						"__meta_kubernetes_pod_container_image":         "testcontainer:latest",
						"__meta_kubernetes_pod_container_port_name":     "testport",
						"__meta_kubernetes_pod_container_port_number":   "9000",
						"__meta_kubernetes_pod_container_port_protocol": "TCP",
						"__meta_kubernetes_pod_container_init":          "false",
						"__meta_kubernetes_pod_container_id":            "docker://a1b2c3d4e5f6",
					},
				},
				Labels: model.LabelSet{
					"__meta_kubernetes_pod_name":            "testpod",
					"__meta_kubernetes_namespace":           "default",
					"__meta_kubernetes_pod_node_name":       "testnode",
					"__meta_kubernetes_pod_ip":              "1.2.3.4",
					"__meta_kubernetes_pod_host_ip":         "2.3.4.5",
					"__meta_kubernetes_pod_ready":           "true",
					"__meta_kubernetes_pod_phase":           "Running",
					"__meta_kubernetes_pod_uid":             "pod123",
					"__meta_kubernetes_pod_controller_kind": "Job",
					"__meta_kubernetes_pod_controller_name": "testjob",
					"__meta_kubernetes_pod_job_name":        "testjob",
				},
				Source: "pod/default/testpod",
			},
		},
	}.Run(t)
}

func TestPodDiscoveryWithCronJobUpdate(t *testing.T) {
	t.Parallel()

	cronJob := makeCronJob("testcronjob", "default")
	cronJob.Labels = map[string]string{"schedule": "hourly"}

	job := makeJob("testcronjob-job-abc", "default", cronJob.Name, cronJob.UID)
	pod := makeJobOwnedPod("default", job.Name, job.UID)

	n, c := makeDiscoveryWithMetadata(RolePod, NamespaceDiscovery{}, AttachMetadataConfig{
		PodMetadataConfig: PodMetadataConfig{CronJob: true},
	}, pod, job, cronJob)

	k8sDiscoveryTest{
		discovery: n,
		afterStart: func() {
			// Update cronjob with new labels
			cronJob.Labels["schedule"] = "daily"
			cronJob.Labels["retention"] = "7d"
			c.BatchV1().CronJobs(cronJob.Namespace).Update(context.Background(), cronJob, metav1.UpdateOptions{})
		},
		expectedMaxItems: 2,
		expectedRes: map[string]*targetgroup.Group{
			"pod/default/testpod": {
				Targets: []model.LabelSet{
					{
						"__address__":                                   "1.2.3.4:9000",
						"__meta_kubernetes_pod_container_name":          "testcontainer",
						"__meta_kubernetes_pod_container_image":         "testcontainer:latest",
						"__meta_kubernetes_pod_container_port_name":     "testport",
						"__meta_kubernetes_pod_container_port_number":   "9000",
						"__meta_kubernetes_pod_container_port_protocol": "TCP",
						"__meta_kubernetes_pod_container_init":          "false",
						"__meta_kubernetes_pod_container_id":            "docker://a1b2c3d4e5f6",
					},
				},
				Labels: model.LabelSet{
					"__meta_kubernetes_pod_name":            "testpod",
					"__meta_kubernetes_namespace":           "default",
					"__meta_kubernetes_pod_node_name":       "testnode",
					"__meta_kubernetes_pod_ip":              "1.2.3.4",
					"__meta_kubernetes_pod_host_ip":         "2.3.4.5",
					"__meta_kubernetes_pod_ready":           "true",
					"__meta_kubernetes_pod_phase":           "Running",
					"__meta_kubernetes_pod_uid":             "pod123",
					"__meta_kubernetes_pod_controller_kind": "Job",
					"__meta_kubernetes_pod_controller_name": "testcronjob-job-abc",
					"__meta_kubernetes_pod_cronjob_name":    "testcronjob",
				},
				Source: "pod/default/testpod",
			},
		},
	}.Run(t)
}
