package inject

import (
	"fmt"
	"testing"

	"github.com/linkerd/linkerd2/pkg/k8s"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestInjectable(t *testing.T) {
	var testCases = []struct {
		podSpec             *corev1.PodSpec
		podMeta             *metav1.ObjectMeta
		nsAnnotations       map[string]string
		unsupportedResource bool
		injectable          bool
		reasons             []string
	}{
		{
			podSpec: &corev1.PodSpec{HostNetwork: false},
			podMeta: &metav1.ObjectMeta{
				Annotations: map[string]string{
					k8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,
				},
			},
			injectable: true,
		},
		{
			podSpec: &corev1.PodSpec{HostNetwork: true},
			podMeta: &metav1.ObjectMeta{
				Annotations: map[string]string{
					k8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,
				},
			},
			injectable: false,
			reasons:    []string{hostNetworkEnabled},
		},
		{
			podSpec: &corev1.PodSpec{
				Containers: []corev1.Container{
					{
						Name:  k8s.ProxyContainerName,
						Image: "gcr.io/linkerd-io/proxy:",
					},
				},
			},
			podMeta: &metav1.ObjectMeta{
				Annotations: map[string]string{
					k8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,
				},
			},
			injectable: false,
			reasons:    []string{sidecarExists},
		},
		{
			podSpec: &corev1.PodSpec{
				InitContainers: []corev1.Container{
					{
						Name:  k8s.InitContainerName,
						Image: "gcr.io/linkerd-io/proxy-init:",
					},
				},
			},
			podMeta: &metav1.ObjectMeta{
				Annotations: map[string]string{
					k8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,
				},
			},
			injectable: false,
			reasons:    []string{sidecarExists},
		},
		{
			unsupportedResource: true,
			podSpec:             &corev1.PodSpec{},
			podMeta: &metav1.ObjectMeta{
				Annotations: map[string]string{
					k8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,
				},
			},
			injectable: false,
			reasons:    []string{unsupportedResource},
		},
		{
			unsupportedResource: true,
			podSpec:             &corev1.PodSpec{HostNetwork: true},
			podMeta: &metav1.ObjectMeta{
				Annotations: map[string]string{
					k8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,
				},
			},

			injectable: false,
			reasons:    []string{hostNetworkEnabled, unsupportedResource},
		},
		{
			nsAnnotations: map[string]string{
				k8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,
			},
			podSpec: &corev1.PodSpec{HostNetwork: true},
			podMeta: &metav1.ObjectMeta{
				Annotations: map[string]string{
					k8s.ProxyInjectAnnotation: k8s.ProxyInjectDisabled,
				},
			},

			injectable: false,
			reasons:    []string{hostNetworkEnabled, injectDisableAnnotationPresent},
		},
		{
			nsAnnotations: map[string]string{
				k8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,
			},
			unsupportedResource: true,
			podSpec:             &corev1.PodSpec{HostNetwork: true},
			podMeta: &metav1.ObjectMeta{
				Annotations: map[string]string{
					k8s.ProxyInjectAnnotation: k8s.ProxyInjectDisabled,
				},
			},

			injectable: false,
			reasons:    []string{hostNetworkEnabled, unsupportedResource, injectDisableAnnotationPresent},
		},
		{
			unsupportedResource: true,
			podSpec:             &corev1.PodSpec{HostNetwork: true},
			podMeta: &metav1.ObjectMeta{
				Annotations: map[string]string{},
			},

			injectable: false,
			reasons:    []string{hostNetworkEnabled, unsupportedResource, injectEnableAnnotationAbsent},
		},
		{
			podSpec: &corev1.PodSpec{HostNetwork: true,
				Containers: []corev1.Container{
					{
						Name:  k8s.ProxyContainerName,
						Image: "gcr.io/linkerd-io/proxy:",
					},
				}},
			podMeta: &metav1.ObjectMeta{
				Annotations: map[string]string{},
			},

			injectable: false,
			reasons:    []string{hostNetworkEnabled, sidecarExists, injectEnableAnnotationAbsent},
		},
	}

	for i, testCase := range testCases {
		testCase := testCase
		t.Run(fmt.Sprintf("test case #%d", i), func(t *testing.T) {
			resourceConfig := &ResourceConfig{}
			resourceConfig.WithNsAnnotations(testCase.nsAnnotations)
			resourceConfig.pod.spec = testCase.podSpec
			resourceConfig.origin = OriginWebhook
			resourceConfig.pod.meta = testCase.podMeta

			report := newReport(resourceConfig)
			report.UnsupportedResource = testCase.unsupportedResource

			actual, reasons := report.Injectable()
			if testCase.injectable != actual {
				t.Errorf("Expected %t. Actual %t", testCase.injectable, actual)
			}

			if len(reasons) != len(testCase.reasons) {
				t.Errorf("Expected %d number of reasons. Actual %d", len(testCase.reasons), len(reasons))
			}

			for i := range reasons {
				if testCase.reasons[i] != reasons[i] {
					t.Errorf("Expected reason '%s'. Actual reason '%s'", testCase.reasons[i], reasons[i])
				}
			}

		})
	}
}

func TestDisableByAnnotation(t *testing.T) {
	t.Run("webhook origin", func(t *testing.T) {
		var testCases = []struct {
			podMeta       *metav1.ObjectMeta
			nsAnnotations map[string]string
			expected      bool
		}{
			{
				podMeta: &metav1.ObjectMeta{
					Annotations: map[string]string{
						k8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,
					},
				},
				expected: false,
			},
			{
				podMeta: &metav1.ObjectMeta{
					Annotations: map[string]string{
						k8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,
					},
				},
				nsAnnotations: map[string]string{
					k8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,
				},
				expected: false,
			},
			{
				podMeta: &metav1.ObjectMeta{
					Annotations: map[string]string{
						k8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,
					},
				},
				nsAnnotations: map[string]string{
					k8s.ProxyInjectAnnotation: k8s.ProxyInjectDisabled,
				},
				expected: false,
			},
			{
				podMeta: &metav1.ObjectMeta{},
				nsAnnotations: map[string]string{
					k8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,
				},
				expected: false,
			},
			{
				podMeta: &metav1.ObjectMeta{
					Annotations: map[string]string{
						k8s.ProxyInjectAnnotation: k8s.ProxyInjectDisabled,
					},
				},
				nsAnnotations: map[string]string{
					k8s.ProxyInjectAnnotation: k8s.ProxyInjectDisabled,
				},
				expected: true,
			},
			{
				podMeta: &metav1.ObjectMeta{
					Annotations: map[string]string{
						k8s.ProxyInjectAnnotation: k8s.ProxyInjectDisabled,
					},
				},
				nsAnnotations: map[string]string{
					k8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,
				},
				expected: true,
			},
			{
				podMeta: &metav1.ObjectMeta{
					Annotations: map[string]string{
						k8s.ProxyInjectAnnotation: k8s.ProxyInjectDisabled,
					},
				},
				nsAnnotations: map[string]string{},
				expected:      true,
			},
			{
				podMeta: &metav1.ObjectMeta{},
				nsAnnotations: map[string]string{
					k8s.ProxyInjectAnnotation: k8s.ProxyInjectDisabled,
				},
				expected: true,
			},
			{
				podMeta:       &metav1.ObjectMeta{},
				nsAnnotations: map[string]string{},
				expected:      true,
			},
		}

		for i, testCase := range testCases {
			testCase := testCase
			t.Run(fmt.Sprintf("test case #%d", i), func(t *testing.T) {
				resourceConfig := &ResourceConfig{origin: OriginWebhook}
				resourceConfig.WithNsAnnotations(testCase.nsAnnotations)
				resourceConfig.pod.meta = testCase.podMeta
				resourceConfig.pod.spec = &corev1.PodSpec{} //initialize empty spec to prevent test from failing

				report := newReport(resourceConfig)
				if actual, _, _ := report.disableByAnnotation(resourceConfig); testCase.expected != actual {
					t.Errorf("Expected %t. Actual %t", testCase.expected, actual)
				}
			})
		}
	})

	t.Run("CLI origin", func(t *testing.T) {
		var testCases = []struct {
			podMeta  *metav1.ObjectMeta
			expected bool
		}{
			{
				podMeta:  &metav1.ObjectMeta{},
				expected: false,
			},
			{
				podMeta: &metav1.ObjectMeta{
					Annotations: map[string]string{
						k8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled,
					},
				},
				expected: false,
			},
			{
				podMeta: &metav1.ObjectMeta{
					Annotations: map[string]string{
						k8s.ProxyInjectAnnotation: k8s.ProxyInjectDisabled,
					},
				},
				expected: true,
			},
		}

		for i, testCase := range testCases {
			testCase := testCase
			t.Run(fmt.Sprintf("test case #%d", i), func(t *testing.T) {
				resourceConfig := &ResourceConfig{origin: OriginCLI}
				resourceConfig.pod.meta = testCase.podMeta
				resourceConfig.pod.spec = &corev1.PodSpec{} //initialize empty spec to prevent test from failing

				report := newReport(resourceConfig)
				if actual, _, _ := report.disableByAnnotation(resourceConfig); testCase.expected != actual {
					t.Errorf("Expected %t. Actual %t", testCase.expected, actual)
				}
			})
		}
	})
}
