package rbac

import (
	"context"
	"testing"

	infrav1alpha1 "github.com/sandbox0-ai/sandbox0/infra-operator/api/v1alpha1"
	"github.com/sandbox0-ai/sandbox0/infra-operator/internal/controller/pkg/common"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	corev1 "k8s.io/api/core/v1"
	rbacv1 "k8s.io/api/rbac/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func TestReconcileManagerRBACIncludesNetworkPolicies(t *testing.T) {
	scheme := runtime.NewScheme()
	require.NoError(t, corev1.AddToScheme(scheme))
	require.NoError(t, rbacv1.AddToScheme(scheme))
	require.NoError(t, infrav1alpha1.AddToScheme(scheme))

	infra := &infrav1alpha1.Sandbox0Infra{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "demo",
			Namespace: "sandbox0-system",
		},
	}
	client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(infra).Build()
	reconciler := NewReconciler(&common.ResourceManager{
		Client: client,
		Scheme: scheme,
	})

	require.NoError(t, reconciler.ReconcileManagerRBAC(context.Background(), infra))

	role := &rbacv1.ClusterRole{}
	require.NoError(t, client.Get(context.Background(), types.NamespacedName{Name: "demo-manager"}, role))

	found := false
	for _, rule := range role.Rules {
		if !contains(rule.APIGroups, "networking.k8s.io") {
			continue
		}
		if !contains(rule.Resources, "networkpolicies") {
			continue
		}
		assert.ElementsMatch(t, []string{"get", "list", "watch", "create", "update", "patch", "delete"}, rule.Verbs)
		found = true
	}
	assert.True(t, found, "expected manager cluster role to include networkpolicies permissions")
}

func TestReconcileManagerRBACIncludesPodResize(t *testing.T) {
	scheme := runtime.NewScheme()
	require.NoError(t, corev1.AddToScheme(scheme))
	require.NoError(t, rbacv1.AddToScheme(scheme))
	require.NoError(t, infrav1alpha1.AddToScheme(scheme))

	infra := &infrav1alpha1.Sandbox0Infra{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "demo",
			Namespace: "sandbox0-system",
		},
	}
	client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(infra).Build()
	reconciler := NewReconciler(&common.ResourceManager{
		Client: client,
		Scheme: scheme,
	})

	require.NoError(t, reconciler.ReconcileManagerRBAC(context.Background(), infra))

	role := &rbacv1.ClusterRole{}
	require.NoError(t, client.Get(context.Background(), types.NamespacedName{Name: "demo-manager"}, role))

	found := false
	for _, rule := range role.Rules {
		if !contains(rule.APIGroups, "") {
			continue
		}
		if !contains(rule.Resources, "pods/resize") {
			continue
		}
		assert.ElementsMatch(t, []string{"get", "list", "watch", "create", "update", "patch", "delete"}, rule.Verbs)
		found = true
	}
	assert.True(t, found, "expected manager cluster role to include pods/resize permissions")
}

func TestReconcileManagerRBACIncludesPodLogsReadPermission(t *testing.T) {
	scheme := runtime.NewScheme()
	require.NoError(t, corev1.AddToScheme(scheme))
	require.NoError(t, rbacv1.AddToScheme(scheme))
	require.NoError(t, infrav1alpha1.AddToScheme(scheme))

	infra := &infrav1alpha1.Sandbox0Infra{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "demo",
			Namespace: "sandbox0-system",
		},
	}
	client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(infra).Build()
	reconciler := NewReconciler(&common.ResourceManager{
		Client: client,
		Scheme: scheme,
	})

	require.NoError(t, reconciler.ReconcileManagerRBAC(context.Background(), infra))

	role := &rbacv1.ClusterRole{}
	require.NoError(t, client.Get(context.Background(), types.NamespacedName{Name: "demo-manager"}, role))

	found := false
	for _, rule := range role.Rules {
		if !contains(rule.APIGroups, "") {
			continue
		}
		if !contains(rule.Resources, "pods/log") {
			continue
		}
		assert.ElementsMatch(t, []string{"get"}, rule.Verbs)
		found = true
	}
	assert.True(t, found, "expected manager cluster role to include pods/log read permission")
}

func TestReconcileCtldRBACIncludesPodReadPermissions(t *testing.T) {
	scheme := runtime.NewScheme()
	require.NoError(t, corev1.AddToScheme(scheme))
	require.NoError(t, rbacv1.AddToScheme(scheme))
	require.NoError(t, infrav1alpha1.AddToScheme(scheme))

	infra := &infrav1alpha1.Sandbox0Infra{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "demo",
			Namespace: "sandbox0-system",
		},
	}
	client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(infra).Build()
	reconciler := NewReconciler(&common.ResourceManager{Client: client, Scheme: scheme})

	require.NoError(t, reconciler.ReconcileCtldRBAC(context.Background(), infra))

	sa := &corev1.ServiceAccount{}
	require.NoError(t, client.Get(context.Background(), types.NamespacedName{Name: "demo-ctld", Namespace: "sandbox0-system"}, sa))

	role := &rbacv1.ClusterRole{}
	require.NoError(t, client.Get(context.Background(), types.NamespacedName{Name: "demo-ctld"}, role))

	found := false
	for _, rule := range role.Rules {
		if !contains(rule.Resources, "pods") {
			continue
		}
		assert.ElementsMatch(t, []string{"get", "list", "watch", "update", "patch"}, rule.Verbs)
		found = true
	}
	assert.True(t, found, "expected ctld cluster role to include pod reconcile permissions")

	found = false
	for _, rule := range role.Rules {
		if !contains(rule.Resources, "pods/resize") {
			continue
		}
		assert.ElementsMatch(t, []string{"update", "patch"}, rule.Verbs)
		found = true
	}
	assert.True(t, found, "expected ctld cluster role to include pod resize permissions")
}

func TestReconcileStorageProxyRBACIncludesNodeReadPermission(t *testing.T) {
	scheme := runtime.NewScheme()
	require.NoError(t, corev1.AddToScheme(scheme))
	require.NoError(t, rbacv1.AddToScheme(scheme))
	require.NoError(t, infrav1alpha1.AddToScheme(scheme))

	infra := &infrav1alpha1.Sandbox0Infra{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "demo",
			Namespace: "sandbox0-system",
		},
	}
	client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(infra).Build()
	reconciler := NewReconciler(&common.ResourceManager{Client: client, Scheme: scheme})

	require.NoError(t, reconciler.ReconcileStorageProxyRBAC(context.Background(), infra))

	role := &rbacv1.ClusterRole{}
	require.NoError(t, client.Get(context.Background(), types.NamespacedName{Name: "demo-storage-proxy"}, role))

	found := false
	for _, rule := range role.Rules {
		if !contains(rule.APIGroups, "") {
			continue
		}
		if !contains(rule.Resources, "nodes") {
			continue
		}
		assert.ElementsMatch(t, []string{"get"}, rule.Verbs)
		found = true
	}
	assert.True(t, found, "expected storage-proxy cluster role to include node read permission")
}

func TestReconcileStorageClientRBACAppliesGCSWorkloadIdentity(t *testing.T) {
	scheme := runtime.NewScheme()
	require.NoError(t, corev1.AddToScheme(scheme))
	require.NoError(t, rbacv1.AddToScheme(scheme))
	require.NoError(t, infrav1alpha1.AddToScheme(scheme))

	infra := &infrav1alpha1.Sandbox0Infra{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "demo",
			Namespace: "sandbox0-system",
		},
		Spec: infrav1alpha1.Sandbox0InfraSpec{
			Storage: &infrav1alpha1.StorageConfig{
				Type: infrav1alpha1.StorageTypeGCS,
				GCS: &infrav1alpha1.GCSStorageConfig{
					Bucket:                              "demo-bucket",
					WorkloadIdentityServiceAccountEmail: "storage@example.iam.gserviceaccount.com",
				},
			},
		},
	}
	client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(infra).Build()
	reconciler := NewReconciler(&common.ResourceManager{Client: client, Scheme: scheme})

	require.NoError(t, reconciler.ReconcileStorageProxyRBAC(context.Background(), infra))
	require.NoError(t, reconciler.ReconcileCtldRBAC(context.Background(), infra))

	storageProxySA := &corev1.ServiceAccount{}
	require.NoError(t, client.Get(context.Background(), types.NamespacedName{Name: "demo-storage-proxy", Namespace: "sandbox0-system"}, storageProxySA))
	assert.Equal(t, "storage@example.iam.gserviceaccount.com", storageProxySA.Annotations[gkeWorkloadIdentityAnnotation])

	ctldSA := &corev1.ServiceAccount{}
	require.NoError(t, client.Get(context.Background(), types.NamespacedName{Name: "demo-ctld", Namespace: "sandbox0-system"}, ctldSA))
	assert.Equal(t, "storage@example.iam.gserviceaccount.com", ctldSA.Annotations[gkeWorkloadIdentityAnnotation])
}

func contains(values []string, target string) bool {
	for _, value := range values {
		if value == target {
			return true
		}
	}
	return false
}
