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

package template

import (
	"strings"
	"testing"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"

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

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

	tests := []struct {
		name     string
		context  map[string]any
		key      MissingKeyOption
		bodies   []*rules.NamespaceRuleBodyNamespace
		assertFn func(t *testing.T, got []*rules.NamespaceRuleBodyNamespace)
		wantErr  string
	}{
		{
			name: "empty input returns nil",
			key:  MissingKeyOption("error"),
			assertFn: func(t *testing.T, got []*rules.NamespaceRuleBodyNamespace) {
				t.Helper()

				if got != nil {
					t.Fatalf("RenderNamespaceRuleBodies() = %#v, want nil", got)
				}
			},
		},
		{
			name: "renders tenant and namespace values in registry exact match",
			context: map[string]any{
				"tenant": map[string]any{
					"metadata": map[string]any{
						"name": "solar",
					},
				},
				"namespace": map[string]any{
					"metadata": map[string]any{
						"name": "solar-prod",
					},
				},
			},
			key: MissingKeyOption("error"),
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Action: rules.ActionTypeAllow,
						Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
							Registries: []rules.OCIRegistry{
								{
									ExpressionMatch: runtime.ExpressionMatch{
										Exact: []string{
											"{{ .tenant.metadata.name }}/{{ .namespace.metadata.name }}/app:1",
										},
									},
								},
							},
						},
					},
				},
			},
			assertFn: func(t *testing.T, got []*rules.NamespaceRuleBodyNamespace) {
				t.Helper()

				if len(got) != 1 {
					t.Fatalf("len(got) = %d, want 1", len(got))
				}

				body := got[0]
				if body == nil || body.Enforce == nil {
					t.Fatalf("got[0].Enforce = nil, want rendered enforce body")
				}

				if body.Enforce.Action != rules.ActionTypeAllow {
					t.Fatalf("Action = %q, want %q", body.Enforce.Action, rules.ActionTypeAllow)
				}

				registries := body.Enforce.Workloads.Registries
				if len(registries) != 1 {
					t.Fatalf("len(Registries) = %d, want 1", len(registries))
				}

				exact := registries[0].Exact
				if len(exact) != 1 || exact[0] != "solar/solar-prod/app:1" {
					t.Fatalf("Exact = %#v, want [solar/solar-prod/app:1]", exact)
				}
			},
		},
		{
			name: "renders namespace labels using index",
			context: map[string]any{
				"namespace": map[string]any{
					"metadata": map[string]any{
						"labels": map[string]any{
							"registry-prefix": "harbor/team-a",
						},
					},
				},
			},
			key: MissingKeyOption("error"),
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Action: rules.ActionTypeAllow,
						Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
							Registries: []rules.OCIRegistry{
								{
									ExpressionMatch: runtime.ExpressionMatch{
										Exact: []string{
											`{{ index .namespace.metadata.labels "registry-prefix" }}/app:1`,
										},
									},
								},
							},
						},
					},
				},
			},
			assertFn: func(t *testing.T, got []*rules.NamespaceRuleBodyNamespace) {
				t.Helper()

				exact := got[0].Enforce.Workloads.Registries[0].Exact
				if len(exact) != 1 || exact[0] != "harbor/team-a/app:1" {
					t.Fatalf("Exact = %#v, want [harbor/team-a/app:1]", exact)
				}
			},
		},
		{
			name: "renders multiple bodies while preserving order",
			context: map[string]any{
				"tenant": map[string]any{
					"metadata": map[string]any{
						"name": "solar",
					},
				},
			},
			key: MissingKeyOption("error"),
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Action: rules.ActionTypeAllow,
						Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
							Registries: []rules.OCIRegistry{
								{
									ExpressionMatch: runtime.ExpressionMatch{
										ExpressionRegex: runtime.ExpressionRegex{
											Expression: "{{ .tenant.metadata.name }}/allow/.*",
										},
									},
								},
							},
						},
					},
				},
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Action: rules.ActionTypeDeny,
						Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
							QoSClasses: []corev1.PodQOSClass{
								corev1.PodQOSBestEffort,
							},
							Registries: []rules.OCIRegistry{
								{
									ExpressionMatch: runtime.ExpressionMatch{
										ExpressionRegex: runtime.ExpressionRegex{
											Expression: "{{ .tenant.metadata.name }}/deny/.*",
										},
									},
								},
							},
						},
					},
				},
			},
			assertFn: func(t *testing.T, got []*rules.NamespaceRuleBodyNamespace) {
				t.Helper()

				if len(got) != 2 {
					t.Fatalf("len(got) = %d, want 2", len(got))
				}

				if got[0].Enforce.Action != rules.ActionTypeAllow {
					t.Fatalf("got[0].Action = %q, want allow", got[0].Enforce.Action)
				}

				if got[1].Enforce.Action != rules.ActionTypeDeny {
					t.Fatalf("got[1].Action = %q, want deny", got[1].Enforce.Action)
				}

				first := got[0].Enforce.Workloads.Registries[0].Expression
				if first != "solar/allow/.*" {
					t.Fatalf("first expression = %q, want solar/allow/.*", first)
				}

				second := got[1].Enforce.Workloads.Registries[0].Expression
				if second != "solar/deny/.*" {
					t.Fatalf("second expression = %q, want solar/deny/.*", second)
				}

				qos := got[1].Enforce.Workloads.QoSClasses
				if len(qos) != 1 || qos[0] != corev1.PodQOSBestEffort {
					t.Fatalf("QoSClasses = %#v, want [%q]", qos, corev1.PodQOSBestEffort)
				}
			},
		},
		{
			name: "missing key is wrapped with render namespace rule bodies template",
			context: map[string]any{
				"namespace": map[string]any{
					"metadata": map[string]any{
						"labels": map[string]any{},
					},
				},
			},
			key: MissingKeyOption("error"),
			bodies: []*rules.NamespaceRuleBodyNamespace{
				{
					Enforce: &rules.NamespaceRuleEnforceBody{
						Action: rules.ActionTypeAllow,
						Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
							Registries: []rules.OCIRegistry{
								{
									ExpressionMatch: runtime.ExpressionMatch{
										Exact: []string{
											"{{ .namespace.metadata.labels.registry }}/app:1",
										},
									},
								},
							},
						},
					},
				},
			},
			wantErr: "render namespace rule bodies template",
		},
	}

	for _, tt := range tests {
		tt := tt

		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			got, err := RenderNamespaceRuleBodies(tt.context, tt.key, tt.bodies)
			if tt.wantErr != "" {
				if err == nil {
					t.Fatalf("RenderNamespaceRuleBodies() error = nil, want containing %q", tt.wantErr)
				}

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

				return
			}

			if err != nil {
				t.Fatalf("RenderNamespaceRuleBodies() unexpected error: %v", err)
			}

			if tt.assertFn != nil {
				tt.assertFn(t, got)
			}
		})
	}
}

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

	ratio := resource.MustParse("1.5")
	request := resource.MustParse("250m")
	bodies := []*rules.NamespaceRuleBodyNamespace{{
		Enforce: &rules.NamespaceRuleEnforceBody{
			Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
				Resources: &rules.WorkloadResourceRules{
					Requests: map[corev1.ResourceName]rules.WorkloadResourceRequestPolicy{
						corev1.ResourceCPU: {
							Policy: rules.WorkloadResourceRequestPolicyDefault,
							Value:  &request,
						},
					},
					Limits: map[corev1.ResourceName]rules.WorkloadResourceLimitPolicy{
						corev1.ResourceCPU: {
							Policy: rules.WorkloadResourceLimitPolicyRatio,
							Value:  &ratio,
						},
					},
				},
			},
		},
	}}

	got, err := RenderNamespaceRuleBodies(nil, MissingKeyError, bodies)
	if err != nil {
		t.Fatalf("RenderNamespaceRuleBodies() unexpected error: %v", err)
	}

	if len(got) != 1 || got[0] == nil || got[0].Enforce == nil {
		t.Fatalf("RenderNamespaceRuleBodies() = %#v, want one enforce body", got)
	}

	resources := got[0].Enforce.Workloads.Resources
	if resources == nil {
		t.Fatal("rendered resources are nil")
	}

	gotRequest := resources.Requests[corev1.ResourceCPU].Value
	if gotRequest == nil || gotRequest.Cmp(request) != 0 {
		t.Fatalf("rendered request = %v, want %s", gotRequest, request.String())
	}

	gotRatio := resources.Limits[corev1.ResourceCPU].Value
	if gotRatio == nil || gotRatio.Cmp(ratio) != 0 {
		t.Fatalf("rendered ratio = %v, want %s", gotRatio, ratio.String())
	}
}

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

	bodies := []*rules.NamespaceRuleBodyNamespace{
		{
			Enforce: &rules.NamespaceRuleEnforceBody{
				Action: rules.ActionTypeAllow,
				Workloads: rules.NamespaceRuleEnforceWorkloadsBody{
					Registries: []rules.OCIRegistry{
						{
							ExpressionMatch: runtime.ExpressionMatch{
								Exact: []string{
									"{{ .tenant.metadata.name }}/app:1",
								},
							},
						},
					},
				},
			},
		},
	}

	got, err := RenderNamespaceRuleBodies(
		map[string]any{
			"tenant": map[string]any{
				"metadata": map[string]any{
					"name": "solar",
				},
			},
		},
		MissingKeyOption("error"),
		bodies,
	)
	if err != nil {
		t.Fatalf("RenderNamespaceRuleBodies() unexpected error: %v", err)
	}

	if len(got) != 1 {
		t.Fatalf("len(got) = %d, want 1", len(got))
	}

	originalExact := bodies[0].Enforce.Workloads.Registries[0].Exact[0]
	if originalExact != "{{ .tenant.metadata.name }}/app:1" {
		t.Fatalf("input body was mutated: exact = %q", originalExact)
	}

	renderedExact := got[0].Enforce.Workloads.Registries[0].Exact[0]
	if renderedExact != "solar/app:1" {
		t.Fatalf("rendered exact = %q, want solar/app:1", renderedExact)
	}
}
