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

package utils_test

import (
	"testing"

	rbacv1 "k8s.io/api/rbac/v1"

	"github.com/projectcapsule/capsule/pkg/api/rbac"
	"github.com/projectcapsule/capsule/pkg/utils"
)

func TestRoleBindingHashFunc_Deterministic(t *testing.T) {
	b := rbac.AdditionalRoleBindingsSpec{
		ClusterRoleName: "admin",
		Subjects: []rbacv1.Subject{
			{Kind: "User", Name: "alice"},
			{Kind: "Group", Name: "devops"},
		},
	}

	h1 := utils.RoleBindingHashFunc(b)
	h2 := utils.RoleBindingHashFunc(b)

	if h1 != h2 {
		t.Fatalf("expected deterministic hash, got %q and %q", h1, h2)
	}
	if h1 == "" {
		t.Fatalf("expected non-empty hash")
	}
}

func TestRoleBindingHashFunc_ChangesWhenClusterRoleChanges(t *testing.T) {
	b1 := rbac.AdditionalRoleBindingsSpec{
		ClusterRoleName: "admin",
		Subjects:        []rbacv1.Subject{{Kind: "User", Name: "alice"}},
	}
	b2 := rbac.AdditionalRoleBindingsSpec{
		ClusterRoleName: "view",
		Subjects:        []rbacv1.Subject{{Kind: "User", Name: "alice"}},
	}

	h1 := utils.RoleBindingHashFunc(b1)
	h2 := utils.RoleBindingHashFunc(b2)

	if h1 == h2 {
		t.Fatalf("expected different hashes when ClusterRoleName changes, got %q", h1)
	}
}

func TestRoleBindingHashFunc_ChangesWhenSubjectKindChanges(t *testing.T) {
	b1 := rbac.AdditionalRoleBindingsSpec{
		ClusterRoleName: "admin",
		Subjects:        []rbacv1.Subject{{Kind: "User", Name: "alice"}},
	}
	b2 := rbac.AdditionalRoleBindingsSpec{
		ClusterRoleName: "admin",
		Subjects:        []rbacv1.Subject{{Kind: "Group", Name: "alice"}},
	}

	h1 := utils.RoleBindingHashFunc(b1)
	h2 := utils.RoleBindingHashFunc(b2)

	if h1 == h2 {
		t.Fatalf("expected different hashes when subject Kind changes, got %q", h1)
	}
}

func TestRoleBindingHashFunc_ChangesWhenSubjectNameChanges(t *testing.T) {
	b1 := rbac.AdditionalRoleBindingsSpec{
		ClusterRoleName: "admin",
		Subjects:        []rbacv1.Subject{{Kind: "User", Name: "alice"}},
	}
	b2 := rbac.AdditionalRoleBindingsSpec{
		ClusterRoleName: "admin",
		Subjects:        []rbacv1.Subject{{Kind: "User", Name: "bob"}},
	}

	h1 := utils.RoleBindingHashFunc(b1)
	h2 := utils.RoleBindingHashFunc(b2)

	if h1 == h2 {
		t.Fatalf("expected different hashes when subject Name changes, got %q", h1)
	}
}

func TestRoleBindingHashFunc_ChangesWhenSubjectNamespaceChanges(t *testing.T) {
	b1 := rbac.AdditionalRoleBindingsSpec{
		ClusterRoleName: "admin",
		Subjects: []rbacv1.Subject{{
			Kind:      rbacv1.ServiceAccountKind,
			Namespace: "team-a",
			Name:      "deployer",
		}},
	}
	b2 := rbac.AdditionalRoleBindingsSpec{
		ClusterRoleName: "admin",
		Subjects: []rbacv1.Subject{{
			Kind:      rbacv1.ServiceAccountKind,
			Namespace: "team-b",
			Name:      "deployer",
		}},
	}

	h1 := utils.RoleBindingHashFunc(b1)
	h2 := utils.RoleBindingHashFunc(b2)

	if h1 == h2 {
		t.Fatalf("expected different hashes when subject Namespace changes, got %q", h1)
	}
}

func TestRoleBindingHashFunc_ChangesWhenSubjectAPIGroupChanges(t *testing.T) {
	b1 := rbac.AdditionalRoleBindingsSpec{
		ClusterRoleName: "admin",
		Subjects: []rbacv1.Subject{{
			APIGroup: rbacv1.GroupName,
			Kind:     rbacv1.UserKind,
			Name:     "alice",
		}},
	}
	b2 := rbac.AdditionalRoleBindingsSpec{
		ClusterRoleName: "admin",
		Subjects: []rbacv1.Subject{{
			APIGroup: "example.com",
			Kind:     rbacv1.UserKind,
			Name:     "alice",
		}},
	}

	h1 := utils.RoleBindingHashFunc(b1)
	h2 := utils.RoleBindingHashFunc(b2)

	if h1 == h2 {
		t.Fatalf("expected different hashes when subject APIGroup changes, got %q", h1)
	}
}

func TestRoleBindingHashFunc_UsesUnambiguousFieldBoundaries(t *testing.T) {
	b1 := rbac.AdditionalRoleBindingsSpec{
		ClusterRoleName: "ab",
		Subjects:        []rbacv1.Subject{{Kind: "c", Name: "d"}},
	}
	b2 := rbac.AdditionalRoleBindingsSpec{
		ClusterRoleName: "a",
		Subjects:        []rbacv1.Subject{{Kind: "bc", Name: "d"}},
	}

	h1 := utils.RoleBindingHashFunc(b1)
	h2 := utils.RoleBindingHashFunc(b2)

	if h1 == h2 {
		t.Fatalf("expected different hashes for differently bounded fields, got %q", h1)
	}
}

func TestRoleBindingHashFunc_EmptyInputsStillProduceHash(t *testing.T) {
	b := rbac.AdditionalRoleBindingsSpec{
		ClusterRoleName: "",
		Subjects:        nil,
	}

	h := utils.RoleBindingHashFunc(b)
	if h == "" {
		t.Fatalf("expected non-empty hash even for empty input")
	}
}

func TestRoleBindingHashFunc_SubjectOrderMatters(t *testing.T) {
	b1 := rbac.AdditionalRoleBindingsSpec{
		ClusterRoleName: "admin",
		Subjects: []rbacv1.Subject{
			{Kind: "User", Name: "alice"},
			{Kind: "Group", Name: "devops"},
		},
	}
	b2 := rbac.AdditionalRoleBindingsSpec{
		ClusterRoleName: "admin",
		Subjects: []rbacv1.Subject{
			{Kind: "Group", Name: "devops"},
			{Kind: "User", Name: "alice"},
		},
	}

	h1 := utils.RoleBindingHashFunc(b1)
	h2 := utils.RoleBindingHashFunc(b2)

	if h1 == h2 {
		t.Fatalf("expected different hashes when subject order changes, got %q", h1)
	}
}
