package eks

import (
	"context"
	"encoding/json"
	"fmt"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/bucket/task"
	"github.com/tidbcloud/infra-provider/regional-server/test/kubemock"
	"github.com/tidbcloud/infra-provider/regional-server/test/mockrepository"
	"go.uber.org/mock/gomock"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/types"
)

func TestBucketPolicy_makeS3BucketPolicy(t *testing.T) {
	bucketName := "test-bucket"
	tests := []struct {
		name                 string
		currentDataPlaneCR   *v1alpha1.DataPlane
		bucketSpec           v1alpha1.BucketSpec
		setupMocks           func(*kubemock.MockClient)
		expectedStatements   int
		expectedError        bool
		expectedSids         []string
		expectedPrincipals   []string
		validatePolicyFields func(*testing.T, string)
	}{
		{
			name: "default dataplane with no granted dataplanes - creates single DefaultAuth statement",
			currentDataPlaneCR: &v1alpha1.DataPlane{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-dataplane",
					Namespace: "test-namespace",
				},
			},
			bucketSpec: v1alpha1.BucketSpec{
				NodeRoleARN:         "arn:aws:iam::123456789012:role/default-node-role",
				GrantedDataplaneIds: []string{},
			},
			setupMocks:         func(mockClient *kubemock.MockClient) {},
			expectedStatements: 1,
			expectedError:      false,
			expectedSids:       []string{"DefaultAuth"},
			expectedPrincipals: []string{"arn:aws:iam::123456789012:role/default-node-role"},
			validatePolicyFields: func(t *testing.T, policyJSON string) {
				var policy map[string]interface{}
				err := json.Unmarshal([]byte(policyJSON), &policy)
				require.NoError(t, err)

				// Verify policy version
				assert.Equal(t, "2012-10-17", policy["Version"])

				// Verify statement structure
				statements := policy["Statement"].([]interface{})
				assert.Equal(t, 1, len(statements))

				stmt := statements[0].(map[string]interface{})

				// Verify statement fields
				assert.Equal(t, "DefaultAuth", stmt["Sid"])
				assert.Equal(t, "Allow", stmt["Effect"])
				assert.Equal(t, "s3:*", stmt["Action"])

				// Verify principal structure
				principal := stmt["Principal"].(map[string]interface{})
				assert.Equal(t, "arn:aws:iam::123456789012:role/default-node-role", principal["AWS"])

				// Verify resource structure
				resources := stmt["Resource"].([]interface{})
				assert.Equal(t, 2, len(resources))
				assert.Contains(t, resources, "arn:aws:s3:::test-bucket")
				assert.Contains(t, resources, "arn:aws:s3:::test-bucket/*")

				// Verify resource order (bucket first, then bucket/*)
				assert.Equal(t, "arn:aws:s3:::test-bucket", resources[0])
				assert.Equal(t, "arn:aws:s3:::test-bucket/*", resources[1])
			},
		},
		{
			name:               "nil currentDataPlaneCR with single granted dataplane - creates statement for granted dataplane only",
			currentDataPlaneCR: nil,
			bucketSpec: v1alpha1.BucketSpec{
				NodeRoleARN:         "arn:aws:iam::123456789012:role/default-node-role",
				GrantedDataplaneIds: []string{"dp-1"},
			},
			setupMocks: func(mockClient *kubemock.MockClient) {
				// Mock DataPlane Get call for granted dataplane
				mockClient.EXPECT().Get(
					gomock.Any(),
					types.NamespacedName{Namespace: "test-namespace", Name: "dp-1"},
					gomock.Any(),
				).DoAndReturn(func(ctx context.Context, key types.NamespacedName, obj interface{}, opts ...interface{}) error {
					if dataPlane, ok := obj.(*v1alpha1.DataPlane); ok {
						dataPlane.Name = key.Name
						dataPlane.Namespace = key.Namespace
						dataPlane.Status.AppBox = &v1alpha1.Reference{
							Name: "appbox-1",
						}
					}
					return nil
				})

				// Mock APPBox Get call for granted dataplane
				mockClient.EXPECT().Get(
					gomock.Any(),
					types.NamespacedName{Namespace: "test-namespace", Name: "appbox-1"},
					gomock.Any(),
				).DoAndReturn(func(ctx context.Context, key types.NamespacedName, obj interface{}, opts ...interface{}) error {
					if appBox, ok := obj.(*v1alpha1.APPBox); ok {
						appBox.Name = key.Name
						appBox.Namespace = key.Namespace
						appBox.Status.IAM.NodeRoleARN = "arn:aws:iam::123456789012:role/granted-node-role"
					}
					return nil
				})
			},
			expectedStatements: 1,
			expectedError:      false,
			expectedSids:       []string{"dp-1"},
			expectedPrincipals: []string{"arn:aws:iam::123456789012:role/granted-node-role"},
			validatePolicyFields: func(t *testing.T, policyJSON string) {
				var policy map[string]interface{}
				err := json.Unmarshal([]byte(policyJSON), &policy)
				require.NoError(t, err)

				// Verify policy structure
				assert.Equal(t, "2012-10-17", policy["Version"])
				statements := policy["Statement"].([]interface{})
				assert.Equal(t, 1, len(statements))

				stmt := statements[0].(map[string]interface{})

				// Verify statement uses dataplane ID as Sid
				assert.Equal(t, "dp-1", stmt["Sid"])
				assert.Equal(t, "Allow", stmt["Effect"])
				assert.Equal(t, "s3:*", stmt["Action"])

				// Verify principal from granted dataplane
				principal := stmt["Principal"].(map[string]interface{})
				assert.Equal(t, "arn:aws:iam::123456789012:role/granted-node-role", principal["AWS"])

				// Verify resources
				resources := stmt["Resource"].([]interface{})
				assert.Equal(t, 2, len(resources))
				assert.Contains(t, resources, "arn:aws:s3:::test-bucket")
				assert.Contains(t, resources, "arn:aws:s3:::test-bucket/*")
			},
		},
		{
			name: "deleting currentDataPlaneCR with granted dataplane - skips default statement, includes granted only",
			currentDataPlaneCR: &v1alpha1.DataPlane{
				ObjectMeta: metav1.ObjectMeta{
					DeletionTimestamp: &metav1.Time{Time: time.Now()},
				},
			},
			bucketSpec: v1alpha1.BucketSpec{
				NodeRoleARN:         "arn:aws:iam::123456789012:role/default-node-role",
				GrantedDataplaneIds: []string{"dp-1"},
			},
			setupMocks: func(mockClient *kubemock.MockClient) {
				// Mock DataPlane Get call for granted dataplane (default dataplane is being deleted, so no mock for it)
				mockClient.EXPECT().Get(
					gomock.Any(),
					types.NamespacedName{Namespace: "test-namespace", Name: "dp-1"},
					gomock.Any(),
				).DoAndReturn(func(ctx context.Context, key types.NamespacedName, obj interface{}, opts ...interface{}) error {
					if dataPlane, ok := obj.(*v1alpha1.DataPlane); ok {
						dataPlane.Name = key.Name
						dataPlane.Namespace = key.Namespace
						dataPlane.Status.AppBox = &v1alpha1.Reference{
							Name: "appbox-1",
						}
					}
					return nil
				})

				// Mock APPBox Get call for granted dataplane
				mockClient.EXPECT().Get(
					gomock.Any(),
					types.NamespacedName{Namespace: "test-namespace", Name: "appbox-1"},
					gomock.Any(),
				).DoAndReturn(func(ctx context.Context, key types.NamespacedName, obj interface{}, opts ...interface{}) error {
					if appBox, ok := obj.(*v1alpha1.APPBox); ok {
						appBox.Name = key.Name
						appBox.Namespace = key.Namespace
						appBox.Status.IAM.NodeRoleARN = "arn:aws:iam::123456789012:role/granted-node-role"
					}
					return nil
				})
			},
			expectedStatements: 1,
			expectedError:      false,
			expectedSids:       []string{"dp-1"},
			expectedPrincipals: []string{"arn:aws:iam::123456789012:role/granted-node-role"},
			validatePolicyFields: func(t *testing.T, policyJSON string) {
				var policy map[string]interface{}
				err := json.Unmarshal([]byte(policyJSON), &policy)
				require.NoError(t, err)

				// Verify only granted dataplane statement is created (no DefaultAuth)
				assert.Equal(t, "2012-10-17", policy["Version"])
				statements := policy["Statement"].([]interface{})
				assert.Equal(t, 1, len(statements))

				stmt := statements[0].(map[string]interface{})

				// Verify statement uses granted dataplane ID as Sid
				assert.Equal(t, "dp-1", stmt["Sid"])
				assert.Equal(t, "Allow", stmt["Effect"])
				assert.Equal(t, "s3:*", stmt["Action"])

				// Verify principal from granted dataplane
				principal := stmt["Principal"].(map[string]interface{})
				assert.Equal(t, "arn:aws:iam::123456789012:role/granted-node-role", principal["AWS"])

				// Verify resources
				resources := stmt["Resource"].([]interface{})
				assert.Equal(t, 2, len(resources))
				assert.Contains(t, resources, "arn:aws:s3:::test-bucket")
				assert.Contains(t, resources, "arn:aws:s3:::test-bucket/*")
			},
		},
		{
			name: "currentDataPlaneCR with multiple granted dataplanes - creates statements for default + all granted dataplanes",
			currentDataPlaneCR: &v1alpha1.DataPlane{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-dataplane",
					Namespace: "test-namespace",
				},
			},
			bucketSpec: v1alpha1.BucketSpec{
				NodeRoleARN:         "arn:aws:iam::123456789012:role/default-node-role",
				GrantedDataplaneIds: []string{"dp-1", "dp-2"},
			},
			setupMocks: func(mockClient *kubemock.MockClient) {
				// Mock first granted dataplane
				mockClient.EXPECT().Get(
					gomock.Any(),
					types.NamespacedName{Namespace: "test-namespace", Name: "dp-1"},
					gomock.Any(),
				).DoAndReturn(func(ctx context.Context, key types.NamespacedName, obj interface{}, opts ...interface{}) error {
					if dataPlane, ok := obj.(*v1alpha1.DataPlane); ok {
						dataPlane.Name = key.Name
						dataPlane.Namespace = key.Namespace
						dataPlane.Status.AppBox = &v1alpha1.Reference{
							Name: "appbox-1",
						}
					}
					return nil
				})

				// Mock first appbox
				mockClient.EXPECT().Get(
					gomock.Any(),
					types.NamespacedName{Namespace: "test-namespace", Name: "appbox-1"},
					gomock.Any(),
				).DoAndReturn(func(ctx context.Context, key types.NamespacedName, obj interface{}, opts ...interface{}) error {
					if appBox, ok := obj.(*v1alpha1.APPBox); ok {
						appBox.Name = key.Name
						appBox.Namespace = key.Namespace
						appBox.Status.IAM.NodeRoleARN = "arn:aws:iam::123456789012:role/granted-node-role-1"
					}
					return nil
				})

				// Mock second granted dataplane
				mockClient.EXPECT().Get(
					gomock.Any(),
					types.NamespacedName{Namespace: "test-namespace", Name: "dp-2"},
					gomock.Any(),
				).DoAndReturn(func(ctx context.Context, key types.NamespacedName, obj interface{}, opts ...interface{}) error {
					if dataPlane, ok := obj.(*v1alpha1.DataPlane); ok {
						dataPlane.Name = key.Name
						dataPlane.Namespace = key.Namespace
						dataPlane.Status.AppBox = &v1alpha1.Reference{
							Name: "appbox-2",
						}
					}
					return nil
				})

				// Mock second appbox
				mockClient.EXPECT().Get(
					gomock.Any(),
					types.NamespacedName{Namespace: "test-namespace", Name: "appbox-2"},
					gomock.Any(),
				).DoAndReturn(func(ctx context.Context, key types.NamespacedName, obj interface{}, opts ...interface{}) error {
					if appBox, ok := obj.(*v1alpha1.APPBox); ok {
						appBox.Name = key.Name
						appBox.Namespace = key.Namespace
						appBox.Status.IAM.NodeRoleARN = "arn:aws:iam::123456789012:role/granted-node-role-2"
					}
					return nil
				})
			},
			expectedStatements: 3, // 1 default + 2 granted
			expectedError:      false,
			expectedSids:       []string{"DefaultAuth", "dp-1", "dp-2"},
			expectedPrincipals: []string{
				"arn:aws:iam::123456789012:role/default-node-role",
				"arn:aws:iam::123456789012:role/granted-node-role-1",
				"arn:aws:iam::123456789012:role/granted-node-role-2",
			},
			validatePolicyFields: func(t *testing.T, policyJSON string) {
				var policy map[string]interface{}
				err := json.Unmarshal([]byte(policyJSON), &policy)
				require.NoError(t, err)

				// Verify policy structure
				assert.Equal(t, "2012-10-17", policy["Version"])
				statements := policy["Statement"].([]interface{})
				assert.Equal(t, 3, len(statements))

				// Track found SIDs and principals to ensure all are present
				foundSids := make(map[string]bool)
				foundPrincipals := make(map[string]bool)

				// Validate all statements have correct structure
				for i, stmt := range statements {
					s := stmt.(map[string]interface{})

					// Verify basic statement structure
					assert.Equal(t, "Allow", s["Effect"])
					assert.Equal(t, "s3:*", s["Action"])

					// Track Sid
					sid := s["Sid"].(string)
					foundSids[sid] = true

					// Verify resources
					resources := s["Resource"].([]interface{})
					assert.Equal(t, 2, len(resources))
					assert.Contains(t, resources, "arn:aws:s3:::test-bucket")
					assert.Contains(t, resources, "arn:aws:s3:::test-bucket/*")

					// Track principal
					principal := s["Principal"].(map[string]interface{})
					principalARN := principal["AWS"].(string)
					foundPrincipals[principalARN] = true

					t.Logf("Statement %d: Sid=%s, Principal=%s", i, sid, principalARN)
				}

				// Verify all expected SIDs are present
				expectedSids := []string{"DefaultAuth", "dp-1", "dp-2"}
				for _, expectedSid := range expectedSids {
					assert.True(t, foundSids[expectedSid], "Expected Sid %s not found", expectedSid)
				}

				// Verify all expected principals are present
				expectedPrincipals := []string{
					"arn:aws:iam::123456789012:role/default-node-role",
					"arn:aws:iam::123456789012:role/granted-node-role-1",
					"arn:aws:iam::123456789012:role/granted-node-role-2",
				}
				for _, expectedPrincipal := range expectedPrincipals {
					assert.True(t, foundPrincipals[expectedPrincipal], "Expected Principal %s not found", expectedPrincipal)
				}
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctrl := gomock.NewController(t)
			defer ctrl.Finish()

			mockClient := kubemock.NewMockClient(ctrl)

			// Setup mocks
			tt.setupMocks(mockClient)

			// Create BucketPolicy instance
			bucket := &v1alpha1.Bucket{
				Spec: tt.bucketSpec,
				Status: v1alpha1.BucketStatus{
					Bucket: &v1alpha1.Reference{
						Name: bucketName,
					},
				},
			}

			taskContext := &task.Context{
				Bucket: bucket,
				Config: config.RootConfig{
					Namespace: "test-namespace",
				},
				Local: mockClient,
			}

			bucketPolicy := &BucketPolicy{
				BaseTask: &task.BaseTask{
					Context: taskContext,
				},
			}

			// Call the method under test
			ctx := context.Background()
			result, err := bucketPolicy.makeS3BucketPolicy(ctx, tt.currentDataPlaneCR)

			// Validate error expectation
			if tt.expectedError {
				assert.Error(t, err)
				assert.Nil(t, result)
				return
			}

			require.NoError(t, err)
			require.NotNil(t, result)

			// Parse the JSON policy
			var policy map[string]interface{}
			err = json.Unmarshal([]byte(*result), &policy)
			require.NoError(t, err)

			// Validate basic policy structure
			assert.Equal(t, "2012-10-17", policy["Version"])

			statements := policy["Statement"].([]interface{})
			assert.Equal(t, tt.expectedStatements, len(statements))

			// Validate SIDs
			actualSids := make([]string, len(statements))
			actualPrincipals := make([]string, len(statements))
			for i, stmt := range statements {
				s := stmt.(map[string]interface{})
				actualSids[i] = s["Sid"].(string)
				principal := s["Principal"].(map[string]interface{})
				actualPrincipals[i] = principal["AWS"].(string)
			}

			assert.ElementsMatch(t, tt.expectedSids, actualSids)
			assert.ElementsMatch(t, tt.expectedPrincipals, actualPrincipals)

			// Run custom validation if provided
			if tt.validatePolicyFields != nil {
				tt.validatePolicyFields(t, *result)
			}
		})
	}
}

func TestBucketPolicy_makeS3BucketPolicy_SkipsMissingGrantedDataplane(t *testing.T) {
	ctrl := gomock.NewController(t)
	mockClient := kubemock.NewMockClient(ctrl)

	mockClient.EXPECT().Get(
		gomock.Any(),
		types.NamespacedName{Namespace: "test-namespace", Name: "deleted-dataplane"},
		gomock.Any(),
	).Return(apierrors.NewNotFound(schema.GroupResource{Resource: "dataplanes"}, "deleted-dataplane"))
	mockClient.EXPECT().Get(
		gomock.Any(),
		types.NamespacedName{Namespace: "test-namespace", Name: "active-dataplane"},
		gomock.Any(),
	).DoAndReturn(func(_ context.Context, _ types.NamespacedName, obj interface{}, _ ...interface{}) error {
		dataPlane := obj.(*v1alpha1.DataPlane)
		dataPlane.Status.AppBox = &v1alpha1.Reference{Name: "active-appbox"}
		return nil
	})
	mockClient.EXPECT().Get(
		gomock.Any(),
		types.NamespacedName{Namespace: "test-namespace", Name: "active-appbox"},
		gomock.Any(),
	).DoAndReturn(func(_ context.Context, _ types.NamespacedName, obj interface{}, _ ...interface{}) error {
		appBox := obj.(*v1alpha1.APPBox)
		appBox.Status.IAM.NodeRoleARN = "arn:aws:iam::123456789012:role/active-node-role"
		return nil
	})

	bucket := &v1alpha1.Bucket{
		Spec: v1alpha1.BucketSpec{
			NodeRoleARN: "arn:aws:iam::123456789012:role/default-node-role",
			GrantedDataplaneIds: []string{
				"deleted-dataplane",
				"active-dataplane",
			},
		},
		Status: v1alpha1.BucketStatus{
			Bucket: &v1alpha1.Reference{Name: "test-bucket"},
		},
	}
	bucketPolicy := NewBucketPolicy(&task.Context{
		Bucket: bucket,
		Config: config.RootConfig{Namespace: "test-namespace"},
		Local:  mockClient,
	})
	currentDataPlane := &v1alpha1.DataPlane{}

	policyJSON, err := bucketPolicy.makeS3BucketPolicy(context.Background(), currentDataPlane)
	require.NoError(t, err)
	require.NotNil(t, policyJSON)

	var policy struct {
		Statement []struct {
			Sid       string `json:"Sid"`
			Principal struct {
				AWS string `json:"AWS"`
			} `json:"Principal"`
		} `json:"Statement"`
	}
	require.NoError(t, json.Unmarshal([]byte(*policyJSON), &policy))
	require.Len(t, policy.Statement, 2)
	assert.Equal(t, "DefaultAuth", policy.Statement[0].Sid)
	assert.Equal(t, "active-dataplane", policy.Statement[1].Sid)
	assert.Equal(t, "arn:aws:iam::123456789012:role/active-node-role", policy.Statement[1].Principal.AWS)
	assert.Equal(t, []string{"deleted-dataplane", "active-dataplane"}, bucket.Spec.GrantedDataplaneIds)
}

func TestBucketPolicy_Create_DeletesPolicyWhenAllStatementsAreStale(t *testing.T) {
	ctrl := gomock.NewController(t)
	mockClient := kubemock.NewMockClient(ctrl)
	mockDataPlaneReader := mockrepository.NewMockDataPlaneReader(ctrl)

	mockDataPlaneReader.EXPECT().Get(
		gomock.Any(),
		types.NamespacedName{Namespace: "test-namespace", Name: "owner-dataplane"},
		gomock.Any(),
	).Return(apierrors.NewNotFound(schema.GroupResource{Resource: "dataplanes"}, "owner-dataplane"))
	mockClient.EXPECT().Get(
		gomock.Any(),
		types.NamespacedName{Namespace: "test-namespace", Name: "deleted-dataplane"},
		gomock.Any(),
	).Return(apierrors.NewNotFound(schema.GroupResource{Resource: "dataplanes"}, "deleted-dataplane"))
	mockClient.EXPECT().Get(
		gomock.Any(),
		types.NamespacedName{Name: "test-bucket"},
		gomock.Any(),
	).Return(nil)
	mockClient.EXPECT().Patch(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
	mockClient.EXPECT().Delete(gomock.Any(), gomock.Any()).Return(nil)

	bucketPolicy := NewBucketPolicy(&task.Context{
		Bucket: &v1alpha1.Bucket{
			Spec: v1alpha1.BucketSpec{
				DataplaneId:         "owner-dataplane",
				GrantedDataplaneIds: []string{"deleted-dataplane"},
				AccountRef:          &v1alpha1.AccountReference{},
			},
			Status: v1alpha1.BucketStatus{
				Bucket: &v1alpha1.Reference{Name: "test-bucket"},
			},
		},
		Config:     config.RootConfig{Namespace: "test-namespace"},
		Local:      mockClient,
		DataPlaneR: mockDataPlaneReader,
	})

	result := bucketPolicy.Create(context.Background())
	require.NoError(t, result.Error())
	postCreateResult := bucketPolicy.PostCreate(context.Background())
	require.NoError(t, postCreateResult.Error())
}

func TestBucketPolicy_makeS3BucketPolicy_ErrorCases(t *testing.T) {
	bucketName := "test-bucket"
	tests := []struct {
		name               string
		currentDataPlaneCR *v1alpha1.DataPlane
		bucketSpec         v1alpha1.BucketSpec
		setupMocks         func(*kubemock.MockClient)
		expectedError      string
		errorContext       string
	}{
		{
			name:               "dataplane not found error - should return error when granted dataplane doesn't exist",
			currentDataPlaneCR: nil,
			bucketSpec: v1alpha1.BucketSpec{
				NodeRoleARN:         "arn:aws:iam::123456789012:role/default-node-role",
				GrantedDataplaneIds: []string{"non-existent-dataplane"},
			},
			setupMocks: func(mockClient *kubemock.MockClient) {
				// Mock dataplane Get to return error
				mockClient.EXPECT().Get(
					gomock.Any(),
					types.NamespacedName{Namespace: "test-namespace", Name: "non-existent-dataplane"},
					gomock.Any(),
				).Return(fmt.Errorf("dataplane not found"))
			},
			expectedError: "make bucket policy statement for granted dataplane 'non-existent-dataplane' error: get dataplane 'non-existent-dataplane' error: dataplane not found",
			errorContext:  "When a granted dataplane ID doesn't exist in the cluster",
		},
		{
			name:               "appbox not found error - should return error when appbox for granted dataplane doesn't exist",
			currentDataPlaneCR: nil,
			bucketSpec: v1alpha1.BucketSpec{
				NodeRoleARN:         "arn:aws:iam::123456789012:role/default-node-role",
				GrantedDataplaneIds: []string{"dp-1"},
			},
			setupMocks: func(mockClient *kubemock.MockClient) {
				// Mock dataplane Get to succeed but return appbox reference
				mockClient.EXPECT().Get(
					gomock.Any(),
					types.NamespacedName{Namespace: "test-namespace", Name: "dp-1"},
					gomock.Any(),
				).DoAndReturn(func(ctx context.Context, key types.NamespacedName, obj interface{}, opts ...interface{}) error {
					if dataPlane, ok := obj.(*v1alpha1.DataPlane); ok {
						dataPlane.Name = key.Name
						dataPlane.Namespace = key.Namespace
						dataPlane.Status.AppBox = &v1alpha1.Reference{
							Name: "non-existent-appbox",
						}
					}
					return nil
				})

				// Mock appbox Get to return a Kubernetes NotFound error
				mockClient.EXPECT().Get(
					gomock.Any(),
					types.NamespacedName{Namespace: "test-namespace", Name: "non-existent-appbox"},
					gomock.Any(),
				).Return(apierrors.NewNotFound(schema.GroupResource{Resource: "appboxes"}, "non-existent-appbox"))
			},
			expectedError: "make bucket policy statement for granted dataplane 'dp-1' error: get appbox 'non-existent-appbox' for dataplane 'dp-1' error: appboxes \"non-existent-appbox\" not found",
			errorContext:  "When the appbox referenced by a granted dataplane doesn't exist",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctrl := gomock.NewController(t)
			defer ctrl.Finish()

			mockClient := kubemock.NewMockClient(ctrl)

			// Setup mocks
			tt.setupMocks(mockClient)

			// Create BucketPolicy instance
			bucket := &v1alpha1.Bucket{
				Spec: tt.bucketSpec,
				Status: v1alpha1.BucketStatus{
					Bucket: &v1alpha1.Reference{
						Name: bucketName,
					},
				},
			}

			taskContext := &task.Context{
				Bucket: bucket,
				Config: config.RootConfig{
					Namespace: "test-namespace",
				},
				Local: mockClient,
			}

			bucketPolicy := &BucketPolicy{
				BaseTask: &task.BaseTask{
					Context: taskContext,
				},
			}

			// Call the method under test
			ctx := context.Background()
			result, err := bucketPolicy.makeS3BucketPolicy(ctx, tt.currentDataPlaneCR)

			// Validate error occurred
			require.Error(t, err, "Expected error for test case: %s", tt.name)

			// Validate error message contains expected text
			assert.Contains(t, err.Error(), tt.expectedError,
				"Error message should contain expected text. Context: %s", tt.errorContext)

			// Validate no result returned on error
			assert.Nil(t, result, "Result should be nil when error occurs")

			// Log the actual error for debugging
			t.Logf("Test case: %s", tt.name)
			t.Logf("Context: %s", tt.errorContext)
			t.Logf("Expected error contains: %s", tt.expectedError)
			t.Logf("Actual error: %s", err.Error())
		})
	}
}
