// 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 (
	"fmt"
	"os"
	"strings"
	"sync"

	"github.com/openshift/generic-admission-server/pkg/apiserver"
	"github.com/pingcap/tidb-operator/pkg/webhook/util"
	admission "k8s.io/api/admission/v1beta1"
	admissionv1beta1 "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"
	"k8s.io/client-go/rest"
	"k8s.io/klog/v2"
)

var (
	deserializer runtime.Decoder = util.Codecs.UniversalDeserializer()
)

type NamespaceAdmissionControl struct {
	lock       sync.RWMutex
	namespaces map[string]struct{}
}

var _ apiserver.ValidatingAdmissionHook = &NamespaceAdmissionControl{}

func NewNamespaceAdmissionControl(namespaces []string) *NamespaceAdmissionControl {
	nc := &NamespaceAdmissionControl{namespaces: map[string]struct{}{}}
	for _, namespace := range namespaces {
		namespace = strings.TrimSpace(namespace)
		if len(namespace) > 0 {
			nc.namespaces[namespace] = struct{}{}
		}
	}
	ns := os.Getenv("NAMESPACE")
	if len(ns) > 0 {
		if _, ok := nc.namespaces[ns]; !ok {
			namespaces = append(namespaces, ns)
			nc.namespaces[ns] = struct{}{}
		}
	}
	klog.Infof("namespace admission control protect namespaces %v", namespaces)
	return nc
}

func (nc *NamespaceAdmissionControl) ValidatingResource() (plural schema.GroupVersionResource, singular string) {
	return schema.GroupVersionResource{
			Group:    "admission.tidb.pingcap.com",
			Version:  "v1alpha1",
			Resource: "namespacevalidations",
		},
		"namespacevalidation"
}

var validateNamespaceGVK = metav1.GroupVersionKind{
	Group:   "",
	Version: "v1",
	Kind:    "Namespace",
}

func (nc *NamespaceAdmissionControl) Validate(ar *admission.AdmissionRequest) *admission.AdmissionResponse {
	if ar.Kind != validateNamespaceGVK {
		return util.ARSuccess()
	}
	if ar.Operation != admissionv1beta1.Delete {
		return util.ARSuccess()
	}
	klog.Infof("admission validating deletion %s", ar.Kind)
	ns := corev1.Namespace{}
	if _, _, err := deserializer.Decode(ar.OldObject.Raw, nil, &ns); err != nil {
		klog.Errorf("admission validating failed: cannot decode %s to %T", ar.Kind, ns)
		return util.ARFail(err)
	}
	nc.lock.RLock()
	defer nc.lock.RUnlock()
	if _, ok := nc.namespaces[ns.Name]; ok {
		err := fmt.Errorf("namespace %s is protected and cannot be deleted", ns.Name)
		klog.Warningf("admission validating deletion %s, %s", ar.Kind, err)
		return util.ARFail(err)
	}
	klog.Infof("admission validating deletion %s, namespace %s can be deleted", ar.Kind, ns.Name)
	return util.ARSuccess()
}

func (nc *NamespaceAdmissionControl) Initialize(cfg *rest.Config, stopCh <-chan struct{}) error {
	return nil
}
