// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0

package tenant

import (
	"maps"
	"strings"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
	"github.com/projectcapsule/capsule/pkg/api/meta"
	tpl "github.com/projectcapsule/capsule/pkg/template"
	"github.com/projectcapsule/capsule/pkg/utils"
)

func TenanLabelValue(ns *corev1.Namespace) string {
	if ns.GetLabels() == nil {
		return ""
	}

	return ns.GetLabels()[meta.TenantLabel]
}

func HasTenantReference(ns *corev1.Namespace) bool {
	if ns.Labels != nil && ns.Labels[meta.TenantLabel] != "" {
		return true
	}

	//nolint:modernize
	for _, ref := range ns.OwnerReferences {
		if IsTenantOwnerReference(ref) {
			return true
		}
	}

	return false
}

// HasConsistentTenantReference reports whether Tenant ownership is either
// completely absent or represented by one matching label and ownerReference.
func HasConsistentTenantReference(ns *corev1.Namespace) bool {
	if ns == nil {
		return true
	}

	label := TenanLabelValue(ns)
	refs := TenantOwnerReferences(ns)

	if label == "" {
		return len(refs) == 0
	}

	return len(refs) == 1 && refs[0].Name == label
}

func TenantOwnerReferences(ns *corev1.Namespace) []metav1.OwnerReference {
	if ns == nil {
		return nil
	}

	refs := make([]metav1.OwnerReference, 0)

	for _, ref := range ns.GetOwnerReferences() {
		if IsTenantOwnerReference(ref) {
			refs = append(refs, ref)
		}
	}

	return refs
}

func TenantOwnerReferenceName(ns *corev1.Namespace) string {
	for _, ref := range ns.GetOwnerReferences() {
		if IsTenantOwnerReference(ref) {
			return ref.Name
		}
	}

	return ""
}

func HasTenantOwnership(ns *corev1.Namespace) bool {
	return TenanLabelValue(ns) != "" || TenantOwnerReferenceName(ns) != ""
}

func TenantOwnershipChanged(oldNs, newNs *corev1.Namespace) bool {
	return TenanLabelValue(oldNs) != TenanLabelValue(newNs) ||
		HasTenantOwnership(oldNs) != HasTenantOwnership(newNs)
}

func AddNamespaceNameLabels(labels map[string]string, ns *corev1.Namespace) {
	labels["kubernetes.io/metadata.name"] = ns.GetName()
}

func AddTenantNameLabel(labels map[string]string, tnt *capsulev1beta2.Tenant) {
	labels[meta.TenantLabel] = tnt.GetName()
}

func HasCustomResourceQuotaAnnotation(tenant *capsulev1beta2.Tenant) bool {
	for k := range tenant.GetAnnotations() {
		if !strings.HasPrefix(k, meta.ResourceQuotaAnnotationPrefix) {
			continue
		}

		return true
	}

	return false
}

func BuildInstanceMetadataForNamespace(ns *corev1.Namespace, tnt *capsulev1beta2.Tenant) (labels map[string]string, annotations map[string]string) {
	annotations = make(map[string]string)
	labels = make(map[string]string)

	instance := tnt.Status.GetInstance(&capsulev1beta2.TenantStatusNamespaceItem{
		Name: ns.GetName(),
		UID:  ns.GetUID(),
	})

	if instance == nil {
		return labels, annotations
	}

	annotations = instance.Metadata.Annotations
	labels = instance.Metadata.Labels

	return labels, annotations
}

//nolint:staticcheck
func BuildNamespaceMetadataForTenant(ns *corev1.Namespace, tnt *capsulev1beta2.Tenant) (labels map[string]string, annotations map[string]string, err error) {
	annotations = BuildNamespaceAnnotationsForTenant(tnt)
	labels = BuildNamespaceLabelsForTenant(tnt)

	fastContext := FastContextForTenantAndNamespace(tnt, ns)

	if opts := tnt.Spec.NamespaceOptions; opts != nil && len(opts.AdditionalMetadataList) > 0 {
		for _, md := range opts.AdditionalMetadataList {
			var ok bool

			ok, err = utils.IsNamespaceSelectedBySelector(ns, md.NamespaceSelector)
			if err != nil {
				return nil, nil, err
			}

			if !ok {
				continue
			}

			tLabels := tpl.FastTemplateMap(md.Labels, fastContext)
			tAnnotations := tpl.FastTemplateMap(md.Annotations, fastContext)

			utils.MapMergeNoOverrite(labels, tLabels)
			utils.MapMergeNoOverrite(annotations, tAnnotations)
		}
	}

	return labels, annotations, nil
}

func BuildNamespaceAnnotationsForTenant(tnt *capsulev1beta2.Tenant) map[string]string {
	annotations := make(map[string]string)

	//nolint:staticcheck
	if md := tnt.Spec.NamespaceOptions; md != nil && md.AdditionalMetadata != nil {
		maps.Copy(annotations, md.AdditionalMetadata.Annotations)
	}

	if tnt.Spec.NodeSelector != nil {
		annotations = utils.BuildNodeSelector(tnt, annotations)
	}

	if ic := tnt.Spec.IngressOptions.AllowedClasses; ic != nil {
		if len(ic.Exact) > 0 {
			annotations[meta.AvailableIngressClassesAnnotation] = strings.Join(ic.Exact, ",")
		}

		//nolint:staticcheck,nolintlint // Preserve annotations for the deprecated v1beta2 field until it is removed.
		if len(ic.Regex) > 0 {
			annotations[meta.AvailableIngressClassesRegexpAnnotation] = ic.Regex
		}
	}

	if sc := tnt.Spec.StorageClasses; sc != nil {
		if len(sc.Exact) > 0 {
			annotations[meta.AvailableStorageClassesAnnotation] = strings.Join(sc.Exact, ",")
		}

		//nolint:staticcheck,nolintlint // Preserve annotations for the deprecated v1beta2 field until it is removed.
		if len(sc.Regex) > 0 {
			annotations[meta.AvailableStorageClassesRegexpAnnotation] = sc.Regex
		}
	}

	//nolint:staticcheck
	if cr := tnt.Spec.ContainerRegistries; cr != nil {
		if len(cr.Exact) > 0 {
			annotations[meta.AllowedRegistriesAnnotation] = strings.Join(cr.Exact, ",")
		}

		//nolint:staticcheck
		if len(cr.Regex) > 0 {
			annotations[meta.AllowedRegistriesRegexpAnnotation] = cr.Regex
		}
	}

	for _, key := range []string{
		meta.ForbiddenNamespaceLabelsAnnotation,
		meta.ForbiddenNamespaceLabelsRegexpAnnotation,
		meta.ForbiddenNamespaceAnnotationsAnnotation,
		meta.ForbiddenNamespaceAnnotationsRegexpAnnotation,
	} {
		if value, ok := tnt.Annotations[key]; ok {
			annotations[key] = value
		}
	}

	return annotations
}

func BuildNamespaceLabelsForTenant(tnt *capsulev1beta2.Tenant) map[string]string {
	labels := make(map[string]string)

	if HasCustomResourceQuotaAnnotation(tnt) {
		labels[meta.CustomResourcesLabel] = meta.ValueTrue
	}

	//nolint:staticcheck
	if md := tnt.Spec.NamespaceOptions; md != nil && md.AdditionalMetadata != nil {
		maps.Copy(labels, md.AdditionalMetadata.Labels)
	}

	if tnt.Spec.Cordoned {
		labels[meta.CordonedLabel] = meta.ValueTrue
	}

	return labels
}
