// Copyright 2024 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package namespace

import (
	"bytes"
	"reflect"
	"testing"

	"github.com/pingcap/tidb-operator/pkg/webhook/util"
	admission "k8s.io/api/admission/v1beta1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
)

type testcase struct {
	name        string
	namespaces  []string
	ns          *corev1.Namespace
	operation   admission.Operation
	wantAllowed bool
}

func runTest(t *testing.T, tt testcase) {
	jsonInfo, ok := runtime.SerializerInfoForMediaType(util.Codecs.SupportedMediaTypes(), runtime.ContentTypeJSON)
	if !ok {
		t.Fatalf("unable to locate encoder -- %q is not a supported media type", runtime.ContentTypeJSON)
	}

	nc := NewNamespaceAdmissionControl(tt.namespaces)
	ar := &admission.AdmissionRequest{
		Operation: tt.operation,
		Kind:      validateNamespaceGVK,
	}
	if tt.ns != nil {
		buf := bytes.Buffer{}
		encoder := util.Codecs.EncoderForVersion(jsonInfo.Serializer, schema.GroupVersion{Version: "v1"})
		if err := encoder.Encode(tt.ns, &buf); err != nil {
			t.Fatal(err)
		}
		if ar.Operation != admission.Delete {
			ar.Object = runtime.RawExtension{
				Raw: buf.Bytes(),
			}
		} else {
			ar.OldObject = runtime.RawExtension{
				Raw: buf.Bytes(),
			}
		}
	}
	resp := nc.Validate(ar)
	if resp.Allowed != tt.wantAllowed {
		t.Errorf("want allowed %v, got %v", tt.wantAllowed, resp.Allowed)
	}
}

func TestNamespaceAdmissionControl(t *testing.T) {
	var typeMeta = metav1.TypeMeta{
		Kind:       "Namespace",
		APIVersion: "v1",
	}
	var tests = []testcase{
		{
			name:       "create protected ns",
			namespaces: []string{"tidb-cluster"},
			operation:  admission.Create,
			ns: &corev1.Namespace{
				TypeMeta: typeMeta,
				ObjectMeta: metav1.ObjectMeta{
					Name: "tidb-cluster",
				},
			},
			wantAllowed: true,
		},
		{
			name:       "create unprotected ns",
			namespaces: []string{"tidb-cluster"},
			operation:  admission.Create,
			ns: &corev1.Namespace{
				TypeMeta: typeMeta,
				ObjectMeta: metav1.ObjectMeta{
					Name: "tidb",
				},
			},
			wantAllowed: true,
		},
		{
			name:       "delete protected ns",
			namespaces: []string{"tidb-cluster"},
			operation:  admission.Delete,
			ns: &corev1.Namespace{
				TypeMeta: typeMeta,
				ObjectMeta: metav1.ObjectMeta{
					Name: "tidb-cluster",
				},
			},
			wantAllowed: false,
		},
		{
			name:       "delete unprotected ns",
			namespaces: []string{"tidb-cluster"},
			operation:  admission.Delete,
			ns: &corev1.Namespace{
				TypeMeta: typeMeta,
				ObjectMeta: metav1.ObjectMeta{
					Name: "tidb",
				},
			},
			wantAllowed: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			runTest(t, tt)
		})
	}
}

func TestValidatingResource(t *testing.T) {
	nc := NewNamespaceAdmissionControl(nil)
	wantGvr := schema.GroupVersionResource{
		Group:    "admission.tidb.pingcap.com",
		Version:  "v1alpha1",
		Resource: "namespacevalidations",
	}
	gvr, _ := nc.ValidatingResource()
	if !reflect.DeepEqual(wantGvr, gvr) {
		t.Fatalf("want: %v, got: %v", wantGvr, gvr)
	}
}
