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

package ruleengine

import (
	"strings"
	"testing"

	corev1 "k8s.io/api/core/v1"
	apimeta "k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/utils/ptr"

	"github.com/projectcapsule/capsule/pkg/api/rules"
	"github.com/projectcapsule/capsule/pkg/api/runtime"
)

func TestValidateRuleStatusBody(t *testing.T) {
	t.Parallel()

	mapper := newRuleValidationRESTMapper()

	tests := []struct {
		name    string
		bodies  []*rules.NamespaceRuleBodyNamespace
		mapper  apimeta.RESTMapper
		wantErr string
	}{
		{
			name:   "nil bodies are valid",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				nil,
				{},
				{
					Enforce: nil,
				},
			},
		},
		{
			name:   "valid quota-only rule",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{{
				Quota: []rules.ResourceQuotaRule{{
					Name: "shared-compute",
					ResourceQuotaSpec: corev1.ResourceQuotaSpec{Hard: corev1.ResourceList{
						corev1.ResourceRequestsCPU: resource.MustParse("8"),
					}},
				}},
			}},
		},
		{
			name:   "quota name is required",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{{
				Quota: []rules.ResourceQuotaRule{{
					ResourceQuotaSpec: corev1.ResourceQuotaSpec{Hard: corev1.ResourceList{
						corev1.ResourceRequestsCPU: resource.MustParse("8"),
					}},
				}},
			}},
			wantErr: "rules[0].quota[0].name",
		},
		{
			name:   "quota name must be a DNS label",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{{
				Quota: []rules.ResourceQuotaRule{{
					Name: "Shared_Compute",
					ResourceQuotaSpec: corev1.ResourceQuotaSpec{Hard: corev1.ResourceList{
						corev1.ResourceRequestsCPU: resource.MustParse("8"),
					}},
				}},
			}},
			wantErr: `rules[0].quota[0].name "Shared_Compute" is invalid`,
		},
		{
			name:   "quota hard must not be empty",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{{
				Quota: []rules.ResourceQuotaRule{{Name: "shared-compute"}},
			}},
			wantErr: "rules[0].quota[0].hard is invalid",
		},
		{
			name:   "quota hard must not be negative",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{{
				Quota: []rules.ResourceQuotaRule{{
					Name: "shared-compute",
					ResourceQuotaSpec: corev1.ResourceQuotaSpec{Hard: corev1.ResourceList{
						corev1.ResourceRequestsCPU: resource.MustParse("-1"),
					}},
				}},
			}},
			wantErr: `rules[0].quota[0].hard["requests.cpu"] is invalid`,
		},
		{
			name:   "quota names must be unique across rules",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{Quota: []rules.ResourceQuotaRule{{Name: "shared-compute", ResourceQuotaSpec: corev1.ResourceQuotaSpec{Hard: corev1.ResourceList{corev1.ResourceCPU: resource.MustParse("1")}}}}},
				{Quota: []rules.ResourceQuotaRule{{Name: "shared-compute", ResourceQuotaSpec: corev1.ResourceQuotaSpec{Hard: corev1.ResourceList{corev1.ResourceMemory: resource.MustParse("1Gi")}}}}},
			},
			wantErr: `rules[1].quota[0].name "shared-compute" is invalid: quota name is already used by rules[0].quota[0]`,
		},
		{
			name:   "valid workload service and metadata rules",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Action: rules.ActionTypeAllow,
						Metadata: []rules.MetadataRule{
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"*",
									},
									Kinds: []string{
										"ConfigMap",
										"Service",
										"Deployment",
									},
								},
								Labels: map[string]rules.MetadataValueRule{
									"env": {
										Required: true,
										Values: []runtime.ExpressionMatch{
											{
												ExpressionRegex: runtime.ExpressionRegex{
													Expression: "^(prod|test|dev)$",
												},
											},
											{
												Exact: []string{
													"prod",
													"test",
												},
											},
										},
									},
									"presence-only": {
										Required: true,
									},
								},
								Annotations: map[string]rules.MetadataValueRule{
									"example.corp/cost-center": {
										Required: false,
										Values: []runtime.ExpressionMatch{
											{
												ExpressionRegex: runtime.ExpressionRegex{
													Expression: "^INV-[0-9]{4}$",
												},
											},
											{
												Exact: []string{
													"prod",
													"test",
												},
											},
										},
									},
								},
							},
						},
						Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
							Registries: []rules.OCIRegistry{
								{
									ExpressionMatch: runtime.ExpressionMatch{
										ExpressionRegex: runtime.ExpressionRegex{
											Expression: "harbor/.*",
										},
									},
								},
								{
									ExpressionMatch: runtime.ExpressionMatch{
										Exact: []string{
											"harbor/platform/debian:latest",
										},
									},
								},
							},
							Schedulers: []runtime.ExpressionMatch{
								{
									ExpressionRegex: runtime.ExpressionRegex{
										Expression: "tenant-[a-z0-9-]+",
									},
								},
							},
						},
						Services: rules.NamespaceRuleEnforceServicesBody{
							Types: []rules.ServiceType{
								rules.ServiceTypeClusterIP,
								rules.ServiceTypeNodePort,
								rules.ServiceTypeLoadBalancer,
								rules.ServiceTypeExternalName,
							},
							LoadBalancers: &rules.ServiceLoadBalancerRule{
								CIDRs: []string{
									"10.0.0.2/32",
									"10.0.1.0/24",
									"2001:db8::/32",
									"10.0.0.3",
								},
							},
							ExternalNames: &rules.ServiceExternalNameRule{
								Hostnames: []runtime.ExpressionMatch{
									{
										Exact: []string{
											"internal.git.com",
										},
									},
									{
										ExpressionRegex: runtime.ExpressionRegex{
											Expression: ".*\\.example\\.com",
										},
									},
									{
										ExpressionRegex: runtime.ExpressionRegex{
											Expression: "trusted\\..*",
											Negate:     true,
										},
									},
								},
							},
							NodePorts: &rules.ServiceNodePortRule{
								Ports: []rules.ServiceNodePortRange{
									{
										From: 30000,
										To:   32767,
									},
									{
										From: 30500,
										To:   30500,
									},
								},
							},
						},
					},
				},
			},
		},
		{
			name:   "valid metadata rule with empty apiVersion meaning core v1",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Action: rules.ActionTypeAllow,
						Metadata: []rules.MetadataRule{
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"v1",
									},
									Kinds: []string{
										"ConfigMap",
										"Service",
									},
								},
								Labels: map[string]rules.MetadataValueRule{
									"env": {
										Required: true,
										Values: []runtime.ExpressionMatch{
											{
												Exact: []string{
													"prod",
												},
											},
										},
									},
								},
							},
						},
					},
				},
			},
		},
		{
			name:   "valid metadata rule with wildcard apiVersion and wildcard kind",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Action: rules.ActionTypeAudit,
						Metadata: []rules.MetadataRule{
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"*",
									},
									Kinds: []string{
										"*",
									},
								},
								Annotations: map[string]rules.MetadataValueRule{
									"example.corp/audit": {
										Values: []runtime.ExpressionMatch{
											{
												ExpressionRegex: runtime.ExpressionRegex{
													Expression: "^audit-.*",
												},
											},
										},
									},
								},
							},
						},
					},
				},
			},
		},
		{
			name:   "valid metadata rule with partial wildcards",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Action: rules.ActionTypeAllow,
						Metadata: []rules.MetadataRule{
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"apps/*",
									},
									Kinds: []string{
										"*Set",
									},
								},
								Labels: map[string]rules.MetadataValueRule{
									"env": {
										Required: true,
										Values: []runtime.ExpressionMatch{
											{
												Exact: []string{
													"prod",
												},
											},
										},
									},
								},
							},
						},
					},
				},
			},
		},
		{
			name:   "invalid metadata label regex",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Metadata: []rules.MetadataRule{
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"*",
									},
									Kinds: []string{
										"ConfigMap",
									},
								},
								Labels: map[string]rules.MetadataValueRule{
									"env": {
										Required: true,
										Values: []runtime.ExpressionMatch{
											{
												ExpressionRegex: runtime.ExpressionRegex{
													Expression: "[",
												},
											},
										},
									},
								},
							},
						},
					},
				},
			},
			wantErr: `rules[0].enforce.metadata[0].labels["env"].values[0].exp "[" is invalid`,
		},
		{
			name:   "invalid metadata annotation regex",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Metadata: []rules.MetadataRule{
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"*",
									},
									Kinds: []string{
										"ConfigMap",
									},
								},
								Annotations: map[string]rules.MetadataValueRule{
									"example.corp/cost-center": {
										Values: []runtime.ExpressionMatch{
											{
												ExpressionRegex: runtime.ExpressionRegex{
													Expression: "[",
												},
											},
										},
									},
								},
							},
						},
					},
				},
			},
			wantErr: `rules[0].enforce.metadata[0].annotations["example.corp/cost-center"].values[0].exp "[" is invalid`,
		},
		{
			name:   "invalid metadata label key",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Metadata: []rules.MetadataRule{
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"*",
									},
									Kinds: []string{
										"ConfigMap",
									},
								},
								Labels: map[string]rules.MetadataValueRule{
									"bad/key/again": {
										Required: true,
										Values: []runtime.ExpressionMatch{
											{
												Exact: []string{
													"prod",
												},
											},
										},
									},
								},
							},
						},
					},
				},
			},
			wantErr: `rules[0].enforce.metadata[0].labels["bad/key/again"] is invalid`,
		},
		{
			name:   "invalid metadata annotation key",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Metadata: []rules.MetadataRule{
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"*",
									},
									Kinds: []string{
										"ConfigMap",
									},
								},
								Annotations: map[string]rules.MetadataValueRule{
									"bad/key/again": {
										Values: []runtime.ExpressionMatch{
											{
												Exact: []string{
													"value",
												},
											},
										},
									},
								},
							},
						},
					},
				},
			},
			wantErr: `rules[0].enforce.metadata[0].annotations["bad/key/again"] is invalid`,
		},
		{
			name:   "reports correct metadata indexes across multiple rules and values",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Metadata: []rules.MetadataRule{
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"*",
									},
									Kinds: []string{
										"ConfigMap",
									},
								},
								Labels: map[string]rules.MetadataValueRule{
									"env": {
										Values: []runtime.ExpressionMatch{
											{
												ExpressionRegex: runtime.ExpressionRegex{
													Expression: "valid-.*",
												},
											},
										},
									},
								},
							},
						},
					},
				},
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Metadata: []rules.MetadataRule{
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"*",
									},
									Kinds: []string{
										"Service",
									},
								},
								Labels: map[string]rules.MetadataValueRule{
									"team": {
										Values: []runtime.ExpressionMatch{
											{
												Exact: []string{
													"platform",
												},
											},
											{
												ExpressionRegex: runtime.ExpressionRegex{
													Expression: "[",
												},
											},
										},
									},
								},
							},
						},
					},
				},
			},
			wantErr: `rules[1].enforce.metadata[0].labels["team"].values[1].exp "[" is invalid`,
		},
		{
			name:   "invalid workload registry regex",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
							Registries: []rules.OCIRegistry{
								{
									ExpressionMatch: runtime.ExpressionMatch{
										ExpressionRegex: runtime.ExpressionRegex{
											Expression: "[",
										},
									},
								},
							},
						},
					},
				},
			},
			wantErr: `rules[0].enforce.workloads.registries[0].exp "[" is invalid`,
		},
		{
			name:   "invalid workload scheduler regex",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
							Schedulers: []runtime.ExpressionMatch{
								{
									ExpressionRegex: runtime.ExpressionRegex{
										Expression: "[",
									},
								},
							},
						},
					},
				},
			},
			wantErr: `rules[0].enforce.workloads.schedulers[0].exp "[" is invalid`,
		},
		{
			name:   "invalid service type",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Services: rules.NamespaceRuleEnforceServicesBody{
							Types: []rules.ServiceType{
								rules.ServiceTypeClusterIP,
								rules.ServiceType("InvalidType"),
							},
						},
					},
				},
			},
			wantErr: `rules[0].enforce.services.types[1] "InvalidType" is invalid`,
		},
		{
			name:   "invalid loadBalancer CIDR",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Services: rules.NamespaceRuleEnforceServicesBody{
							LoadBalancers: &rules.ServiceLoadBalancerRule{
								CIDRs: []string{
									"10.0.0.0/33",
								},
							},
						},
					},
				},
			},
			wantErr: `rules[0].enforce.services.loadBalancers.cidrs[0] "10.0.0.0/33" is invalid`,
		},
		{
			name:   "empty loadBalancer CIDR",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Services: rules.NamespaceRuleEnforceServicesBody{
							LoadBalancers: &rules.ServiceLoadBalancerRule{
								CIDRs: []string{
									"",
								},
							},
						},
					},
				},
			},
			wantErr: `rules[0].enforce.services.loadBalancers.cidrs[0] "" is invalid: CIDR is empty`,
		},
		{
			name:   "invalid external IP CIDR",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Services: rules.NamespaceRuleEnforceServicesBody{
							ExternalIPs: &rules.ServiceExternalIPRule{
								CIDRs: []string{
									"10.20.0.0/33",
								},
							},
						},
					},
				},
			},
			wantErr: `rules[0].enforce.services.externalIPs.cidrs[0] "10.20.0.0/33" is invalid`,
		},
		{
			name:   "empty external IP CIDR entry",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Services: rules.NamespaceRuleEnforceServicesBody{
							ExternalIPs: &rules.ServiceExternalIPRule{
								CIDRs: []string{
									"",
								},
							},
						},
					},
				},
			},
			wantErr: `rules[0].enforce.services.externalIPs.cidrs[0] "" is invalid: CIDR is empty`,
		},
		{
			name:   "external IP CIDRs are valid",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Action: rules.ActionTypeAllow,
						Services: rules.NamespaceRuleEnforceServicesBody{
							ExternalIPs: &rules.ServiceExternalIPRule{
								CIDRs: []string{
									"10.20.0.0/16",
									"192.168.1.2",
									"2001:db8::/32",
								},
							},
						},
					},
				},
			},
		},
		{
			name:   "empty external IP CIDR deny rule is valid",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Action: rules.ActionTypeDeny,
						Services: rules.NamespaceRuleEnforceServicesBody{
							ExternalIPs: &rules.ServiceExternalIPRule{
								CIDRs: []string{},
							},
						},
					},
				},
			},
		},
		{
			name:   "invalid externalName hostname regex",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Services: rules.NamespaceRuleEnforceServicesBody{
							ExternalNames: &rules.ServiceExternalNameRule{
								Hostnames: []runtime.ExpressionMatch{
									{
										ExpressionRegex: runtime.ExpressionRegex{
											Expression: "[",
										},
									},
								},
							},
						},
					},
				},
			},
			wantErr: `rules[0].enforce.services.externalNames.hostnames[0].exp "[" is invalid`,
		},
		{
			name:   "nodePort from greater than to",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Services: rules.NamespaceRuleEnforceServicesBody{
							NodePorts: &rules.ServiceNodePortRule{
								Ports: []rules.ServiceNodePortRange{
									{
										From: 32767,
										To:   30000,
									},
								},
							},
						},
					},
				},
			},
			wantErr: `rules[0].enforce.services.nodePorts.ports[0] is invalid: from 32767 must be lower than or equal to 30000`,
		},
		{
			name:   "nodePort from below valid port range",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Services: rules.NamespaceRuleEnforceServicesBody{
							NodePorts: &rules.ServiceNodePortRule{
								Ports: []rules.ServiceNodePortRange{
									{
										From: 0,
										To:   30000,
									},
								},
							},
						},
					},
				},
			},
			wantErr: `rules[0].enforce.services.nodePorts.ports[0] is invalid: from 0 must be between 1 and 65535`,
		},
		{
			name:   "nodePort to above valid port range",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Services: rules.NamespaceRuleEnforceServicesBody{
							NodePorts: &rules.ServiceNodePortRule{
								Ports: []rules.ServiceNodePortRange{
									{
										From: 30000,
										To:   70000,
									},
								},
							},
						},
					},
				},
			},
			wantErr: `rules[0].enforce.services.nodePorts.ports[0] is invalid: to 70000 must be between 1 and 65535`,
		},
		{
			name:   "single nodePort range is valid",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Services: rules.NamespaceRuleEnforceServicesBody{
							NodePorts: &rules.ServiceNodePortRule{
								Ports: []rules.ServiceNodePortRange{
									{
										From: 30500,
										To:   30500,
									},
								},
							},
						},
					},
				},
			},
		},
		{
			name:   "reports correct indexes across multiple service rules",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Services: rules.NamespaceRuleEnforceServicesBody{
							Types: []rules.ServiceType{
								rules.ServiceTypeClusterIP,
							},
						},
					},
				},
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Services: rules.NamespaceRuleEnforceServicesBody{
							ExternalNames: &rules.ServiceExternalNameRule{
								Hostnames: []runtime.ExpressionMatch{
									{
										ExpressionRegex: runtime.ExpressionRegex{
											Expression: "valid\\..*",
										},
									},
									{
										ExpressionRegex: runtime.ExpressionRegex{
											Expression: "[",
										},
									},
								},
							},
						},
					},
				},
			},
			wantErr: `rules[1].enforce.services.externalNames.hostnames[1].exp "[" is invalid`,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			err := ValidateRuleStatusBody(tt.mapper, tt.bodies)

			if tt.wantErr == "" {
				if err != nil {
					t.Fatalf("expected no error, got %v", err)
				}

				return
			}

			if err == nil {
				t.Fatalf("expected error containing %q, got nil", tt.wantErr)
			}

			if !strings.Contains(err.Error(), tt.wantErr) {
				t.Fatalf("expected error containing %q, got %q", tt.wantErr, err.Error())
			}
		})
	}
}

func TestValidateMetadataKeyPatterns(t *testing.T) {
	t.Parallel()

	if err := validateMetadataKey("example.corp/*"); err != nil {
		t.Fatalf("expected wildcard key selector to be valid: %v", err)
	}
	if err := validateMetadataKey(`example\.corp/.*`); err != nil {
		t.Fatalf("expected regexp key selector to be valid: %v", err)
	}
	if err := validateMetadataKey("example.corp/["); err == nil {
		t.Fatal("expected invalid regexp key selector to be rejected")
	}
}

func TestMutableMetadataRequiresConcreteKey(t *testing.T) {
	t.Parallel()
	policy := rules.MetadataValueRule{Managed: ptr.To("controlled")}
	if err := validateMutableMetadataKey("example.corp/key", policy); err != nil {
		t.Fatalf("concrete key rejected: %v", err)
	}
	if err := validateMutableMetadataKey("example.corp/*", policy); err == nil {
		t.Fatal("wildcard managed key was accepted")
	}
	if err := validateMutableMetadataKey(`example\.corp/.*`, policy); err == nil {
		t.Fatal("regexp managed key was accepted")
	}

	rule := rules.MetadataRule{
		VersionKinds: runtime.VersionKinds{APIGroups: []string{"*"}, Kinds: []string{"ConfigMap"}},
		Labels:       map[string]rules.MetadataValueRule{"example.corp/key": policy},
	}
	if err := validateMetadataRules(0, []rules.MetadataRule{rule}, nil); err == nil || !strings.Contains(err.Error(), "managed metadata requires concrete apiGroups and kinds") {
		t.Fatalf("wildcard managed target error = %v", err)
	}
}

func TestValidateRuleStatusBodyWithRESTMapper(t *testing.T) {
	t.Parallel()

	mapper := newRuleValidationRESTMapper()

	tests := []struct {
		name    string
		bodies  []*rules.NamespaceRuleBodyNamespace
		mapper  apimeta.RESTMapper
		wantErr []string
	}{
		{
			name:   "nil mapper skips discovery validation",
			mapper: nil,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Metadata: []rules.MetadataRule{
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"v1",
									},
									Kinds: []string{
										"NotAThing",
									},
								},
								Labels: map[string]rules.MetadataValueRule{
									"env": {
										Required: true,
									},
								},
							},
						},
					},
				},
			},
		},
		{
			name:   "known core v1 multiple kinds are valid",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Metadata: []rules.MetadataRule{
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"v1",
									},
									Kinds: []string{
										"ConfigMap",
										"Service",
										"Pod",
									},
								},
								Labels: map[string]rules.MetadataValueRule{
									"env": {
										Required: true,
										Values: []runtime.ExpressionMatch{
											{
												Exact: []string{
													"prod",
												},
											},
										},
									},
								},
							},
						},
					},
				},
			},
		},
		{
			name:   "core namespace is a valid metadata target",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{{
				Enforce: &rules.NamespaceRuleEnforceBody{Metadata: []rules.MetadataRule{{
					VersionKinds: runtime.VersionKinds{APIGroups: []string{""}, Kinds: []string{"Namespace"}},
					Annotations: map[string]rules.MetadataValueRule{
						"example.corp/*": {Values: []runtime.ExpressionMatch{{Exact: []string{"allowed"}}}},
					},
				}}},
			}},
		},
		{
			name:   "known grouped apiVersion kind is valid",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Metadata: []rules.MetadataRule{
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"apps/v1",
									},
									Kinds: []string{
										"Deployment",
										"StatefulSet",
									},
								},
								Labels: map[string]rules.MetadataValueRule{
									"env": {
										Required: true,
									},
								},
							},
						},
					},
				},
			},
		},
		{
			name:   "unknown core kind is invalid",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Metadata: []rules.MetadataRule{
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"v1",
									},
									Kinds: []string{
										"NotAThing",
									},
								},
								Labels: map[string]rules.MetadataValueRule{
									"env": {
										Required: true,
									},
								},
							},
						},
					},
				},
			},
			wantErr: []string{
				`rules[0].enforce.metadata[0].kinds[0]`,
				`NotAThing`,
			},
		},
		{
			name:   "wrong apiVersion kind combination is invalid",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Metadata: []rules.MetadataRule{
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"v1",
									},
									Kinds: []string{
										"Deployment",
									},
								},
								Labels: map[string]rules.MetadataValueRule{
									"env": {
										Required: true,
									},
								},
							},
						},
					},
				},
			},
			wantErr: []string{
				`rules[0].enforce.metadata[0].kinds[0]`,
				`Deployment`,
			},
		},
		{
			name:   "unknown grouped kind is invalid",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Metadata: []rules.MetadataRule{
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"apps/v1",
									},
									Kinds: []string{
										"NotADeployment",
									},
								},
								Labels: map[string]rules.MetadataValueRule{
									"env": {
										Required: true,
									},
								},
							},
						},
					},
				},
			},
			wantErr: []string{
				`rules[0].enforce.metadata[0].kinds[0]`,
				`NotADeployment`,
			},
		},
		{
			name:   "wildcard apiVersion skips discovery validation",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Metadata: []rules.MetadataRule{
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"*",
									},
									Kinds: []string{
										"NotAThing",
									},
								},
								Labels: map[string]rules.MetadataValueRule{
									"env": {
										Required: true,
									},
								},
							},
						},
					},
				},
			},
		},
		{
			name:   "wildcard kind skips discovery validation",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Metadata: []rules.MetadataRule{
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"v1",
									},
									Kinds: []string{
										"*",
									},
								},
								Labels: map[string]rules.MetadataValueRule{
									"env": {
										Required: true,
									},
								},
							},
						},
					},
				},
			},
		},
		{
			name:   "partial wildcard kind skips discovery validation",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Metadata: []rules.MetadataRule{
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"apps/v1",
									},
									Kinds: []string{
										"*Set",
									},
								},
								Labels: map[string]rules.MetadataValueRule{
									"env": {
										Required: true,
									},
								},
							},
						},
					},
				},
			},
		},
		{
			name:   "reports correct indexes across multiple metadata rules and kinds",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Metadata: []rules.MetadataRule{
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"v1",
									},
									Kinds: []string{
										"ConfigMap",
									},
								},
								Labels: map[string]rules.MetadataValueRule{
									"env": {
										Required: true,
									},
								},
							},
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"apps/v1",
									},
									Kinds: []string{
										"Deployment",
										"NotADeployment",
									},
								},
								Labels: map[string]rules.MetadataValueRule{
									"team": {
										Required: true,
									},
								},
							},
						},
					},
				},
			},
			wantErr: []string{
				`rules[0].enforce.metadata[1].kinds[1]`,
				`NotADeployment`,
			},
		},
		{
			name:   "still validates metadata syntax when mapper is enabled",
			mapper: mapper,
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Metadata: []rules.MetadataRule{
							{
								VersionKinds: runtime.VersionKinds{
									APIGroups: []string{
										"v1",
									},
									Kinds: []string{
										"ConfigMap",
									},
								},
								Labels: map[string]rules.MetadataValueRule{
									"env": {
										Required: true,
										Values: []runtime.ExpressionMatch{
											{
												ExpressionRegex: runtime.ExpressionRegex{
													Expression: "[",
												},
											},
										},
									},
								},
							},
						},
					},
				},
			},
			wantErr: []string{
				`rules[0].enforce.metadata[0].labels["env"].values[0].exp "[" is invalid`,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			err := ValidateRuleStatusBody(tt.mapper, tt.bodies)

			if len(tt.wantErr) == 0 {
				if err != nil {
					t.Fatalf("expected no error, got %v", err)
				}

				return
			}

			if err == nil {
				t.Fatalf("expected error containing %q, got nil", tt.wantErr)
			}

			for _, expected := range tt.wantErr {
				if !strings.Contains(err.Error(), expected) {
					t.Fatalf("expected error containing %q, got %q", expected, err.Error())
				}
			}
		})
	}
}

func newRuleValidationRESTMapper() apimeta.RESTMapper {
	mapper := apimeta.NewDefaultRESTMapper([]schema.GroupVersion{
		{
			Group:   "",
			Version: "v1",
		},
		{
			Group:   "apps",
			Version: "v1",
		},
	})

	mapper.Add(
		schema.GroupVersionKind{
			Group:   "",
			Version: "v1",
			Kind:    "Namespace",
		},
		apimeta.RESTScopeRoot,
	)

	mapper.Add(
		schema.GroupVersionKind{
			Group:   "",
			Version: "v1",
			Kind:    "ConfigMap",
		},
		apimeta.RESTScopeNamespace,
	)

	mapper.Add(
		schema.GroupVersionKind{
			Group:   "",
			Version: "v1",
			Kind:    "Service",
		},
		apimeta.RESTScopeNamespace,
	)

	mapper.Add(
		schema.GroupVersionKind{
			Group:   "",
			Version: "v1",
			Kind:    "Pod",
		},
		apimeta.RESTScopeNamespace,
	)

	mapper.Add(
		schema.GroupVersionKind{
			Group:   "apps",
			Version: "v1",
			Kind:    "Deployment",
		},
		apimeta.RESTScopeNamespace,
	)

	mapper.Add(
		schema.GroupVersionKind{
			Group:   "apps",
			Version: "v1",
			Kind:    "StatefulSet",
		},
		apimeta.RESTScopeNamespace,
	)

	return mapper
}
