package v1

import (
	"context"
	"fmt"
	"regexp"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/validation/field"
	logf "sigs.k8s.io/controller-runtime/pkg/log"
)

var projectlog = logf.Log.WithName("project helper")

// ProjectLabel denotes the Infra Project of the resources, Infra API re-use kubernetes namespace for Project isolation,
// the name of the namespace will be used as the ID of the Infra Project by default, this behavior can be overridden by client
// when creating the namespace with an explicitly configured project label.
// Infra Project is applicable to be used as an isolation unit for client applications.
const (
	ProjectLabel = "cloud.pingcap.com/project"
)

var (
	projectRegex = regexp.MustCompile("^[a-z0-9-]{1,32}$")
)

// ProjectId get the projectId of current namespace
func ProjectId(ns *corev1.Namespace) string {
	// default
	id := ns.Name
	// override
	if v, ok := ns.Labels[ProjectLabel]; ok {
		id = v
	}
	return id
}

// ValidateProjectId validates an Object have a project label associated and the project name is valid
func ValidateProjectId(meta *metav1.ObjectMeta) *field.Error {
	project, ok := meta.Labels[ProjectLabel]
	parent := field.NewPath("metadata")
	if !ok {
		return field.Invalid(parent.Child("labels"), "", fmt.Sprintf("%s label must be set", ProjectLabel))
	}
	if !projectRegex.MatchString(project) {
		return field.Invalid(parent.Child("labels").Child(ProjectLabel), project, fmt.Sprintf("project does not match %s", projectRegex.String()))
	}
	return nil
}

// ValidateUpdateProjectId validates the project label is immutable
func ValidateUpdateProjectId(before, after *metav1.ObjectMeta) *field.Error {
	parent := field.NewPath("metadata")
	if before.Labels[ProjectLabel] != "" && before.Labels[ProjectLabel] != after.Labels[ProjectLabel] {
		return field.Invalid(parent.Child("labels").Child(ProjectLabel), after.Labels[ProjectLabel], "field is immutable")
	}
	return nil
}

func setDefaultProjectId(reader readThroughReader, meta *metav1.ObjectMeta) {
	if _, ok := meta.Labels[ProjectLabel]; ok {
		// already set, skip
		return
	}
	ns := &corev1.Namespace{}
	err := reader.Get(context.TODO(), types.NamespacedName{Name: meta.Namespace}, ns)
	if err != nil {
		projectlog.Error(err, "error get namespace of the object when setting project label, forward to validation")
		return
	}
	if meta.Labels == nil {
		meta.Labels = map[string]string{}
	}
	meta.Labels[ProjectLabel] = ProjectId(ns)
}
