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

package meta

import (
	"strings"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/labels"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
	ResourcesLabel = "capsule.clastix.io/resources"

	TenantNameLabel = "kubernetes.io/metadata.name"

	TenantLabel    = "capsule.clastix.io/tenant"
	NewTenantLabel = "projectcapsule.dev/tenant"

	ResourcePoolLabel = "projectcapsule.dev/pool"

	GlobalResourceQuotaLabel = "projectcapsule.dev/global-resource-quota"

	FreezeLabel = "projectcapsule.dev/freeze"

	OwnerPromotionLabel          = "owner.projectcapsule.dev/promote"
	ServiceAccountPromotionLabel = "projectcapsule.dev/promote"

	CordonedLabel = "projectcapsule.dev/cordoned"

	CapsuleNameLabel = "projectcapsule.dev/name"

	CreatedByCapsuleLabel = "projectcapsule.dev/created-by"
	CustomResourcesLabel  = "projectcapsule.dev/custom-resources"
	ResourceOriginLabel   = "projectcapsule.dev/resource-origin"

	NewManagedByCapsuleLabel = "projectcapsule.dev/managed-by"
	ManagedByCapsuleLabel    = "capsule.clastix.io/managed-by"

	LimitRangeLabel    = "capsule.clastix.io/limit-range"
	NetworkPolicyLabel = "capsule.clastix.io/network-policy"
	ResourceQuotaLabel = "capsule.clastix.io/resource-quota"
	RuleQuotaLabel     = "projectcapsule.dev/rule-quota"
	RolebindingLabel   = "capsule.clastix.io/role-binding"
)

func FreezeLabelTriggers(obj client.Object) bool {
	return labelTriggers(obj, FreezeLabel, ValueTrue)
}

func FreezeLabelRemove(obj client.Object) {
	labelRemove(obj, FreezeLabel)
}

func OwnerPromotionLabelTriggers(obj client.Object) bool {
	return labelTriggers(obj, OwnerPromotionLabel, ValueTrue)
}

func OwnerPromotionLabelRemove(obj client.Object) {
	labelRemove(obj, OwnerPromotionLabel)
}

func labelRemove(obj client.Object, anno string) {
	annotations := obj.GetLabels()

	if _, ok := annotations[anno]; ok {
		delete(annotations, anno)

		obj.SetLabels(annotations)
	}
}

func labelTriggers(obj client.Object, anno string, trigger string) bool {
	annotations := obj.GetLabels()

	if val, ok := annotations[anno]; ok {
		if strings.ToLower(val) == trigger {
			return true
		}
	}

	return false
}

// SetFilteredLabels Removes given labels by key.
func SetFilteredLabels(obj *unstructured.Unstructured, filter map[string]struct{}) {
	if obj == nil || len(filter) == 0 {
		return
	}

	labels := obj.GetLabels()
	if labels == nil {
		return
	}

	for k := range labels {
		if _, reserved := filter[k]; reserved {
			delete(labels, k)
		}
	}

	obj.SetLabels(labels)
}

// LabelsChanged indicates if the given label keys have changed.
func LabelsChanged(keys []string, oldLabels, newLabels map[string]string) bool {
	for _, key := range keys {
		oldVal, oldOK := oldLabels[key]
		newVal, newOK := newLabels[key]

		if oldOK != newOK || oldVal != newVal {
			return true
		}
	}

	return false
}

func LabelsChangedUnstructured(oldObj, newObj unstructured.Unstructured) bool {
	return !labels.Equals(labels.Set(oldObj.GetLabels()), labels.Set(newObj.GetLabels()))
}

// Collect all mentioned keys from a LabelSelector.
func LabelSelectorKeys(sel *metav1.LabelSelector) map[string]struct{} {
	out := map[string]struct{}{}
	if sel == nil {
		return out
	}

	for k := range sel.MatchLabels {
		out[k] = struct{}{}
	}

	for _, expr := range sel.MatchExpressions {
		if expr.Key != "" {
			out[expr.Key] = struct{}{}
		}
	}

	return out
}
