package task

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"

	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
)

const testPolicyJSON = `
{
  "Version": "1",
  "Statement": [
    {
      "Sid": "Enable Permissions",
      "Effect": "Allow",
      "Principal": {
        "RAM": ["acs:ram::119285303511:*"],
        "AWS": "arn:aws:iam::123456789012:root"
      },
      "Action": ["kms:*"],
      "Resource": ["*"]
    },
    {
      "Sid": "Allow node roles",
      "Effect": "Allow",
      "Principal": {
        "RAM": [
          "acs:ram::119285303511:role/node-role-abc123",
          "acs:ram::119285303511:role/node-role-def456"
        ],
        "AWS": [
          "arn:aws:iam::123456789012:role/node-role-abc123"
        ]
      },
      "Action": ["kms:Encrypt", "kms:Decrypt"],
      "Resource": ["*"]
    }
  ]
}
`

func TestContainsPrincipal(t *testing.T) {
	tests := []struct {
		name         string
		target       string
		policyPtr    *string
		principalKey string
		want         bool
	}{
		{
			name:         "RAM wildcard match",
			target:       "acs:ram::119285303511:*",
			policyPtr:    ptr.To(testPolicyJSON),
			principalKey: "RAM",
			want:         true,
		},
		{
			name:         "RAM role match",
			target:       "acs:ram::119285303511:role/node-role-abc123",
			policyPtr:    ptr.To(testPolicyJSON),
			principalKey: "RAM",
			want:         true,
		},
		{
			name:         "AWS root match (string principal)",
			target:       "arn:aws:iam::123456789012:root",
			policyPtr:    ptr.To(testPolicyJSON),
			principalKey: "AWS",
			want:         true,
		},
		{
			name:         "AWS role match (array principal)",
			target:       "arn:aws:iam::123456789012:role/node-role-abc123",
			policyPtr:    ptr.To(testPolicyJSON),
			principalKey: "AWS",
			want:         true,
		},
		{
			name:         "not found",
			target:       "acs:ram::119285303511:role/does-not-exist",
			policyPtr:    ptr.To(testPolicyJSON),
			principalKey: "RAM",
			want:         false,
		},
		{
			name:         "nil policy",
			target:       "anything",
			policyPtr:    nil,
			principalKey: "RAM",
			want:         false,
		},
		{
			name:         "empty policy",
			target:       "anything",
			policyPtr:    ptr.To(""),
			principalKey: "RAM",
			want:         false,
		},
		{
			name:         "invalid json",
			target:       "anything",
			policyPtr:    ptr.To("not json"),
			principalKey: "RAM",
			want:         false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := ContainsPrincipal(tt.target, tt.policyPtr, tt.principalKey)
			assert.Equal(t, tt.want, got)
		})
	}
}

func TestLookupNodeRoleARN(t *testing.T) {
	s := runtime.NewScheme()
	v1alpha1.AddToScheme(s)

	key := &v1alpha1.Key{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test-key",
			Namespace: "default",
		},
	}

	tests := []struct {
		name      string
		objects   []runtime.Object
		dpID      string
		wantARN   string
		wantError bool
	}{
		{
			name:    "dataplane not found",
			dpID:    "dp-missing",
			wantARN: "",
		},
		{
			name: "appbox ref is nil",
			objects: []runtime.Object{
				&v1alpha1.DataPlane{
					ObjectMeta: metav1.ObjectMeta{Name: "dp-1", Namespace: "default"},
					Status:     v1alpha1.DataPlaneStatus{},
				},
			},
			dpID:    "dp-1",
			wantARN: "",
		},
		{
			name: "appbox name is empty",
			objects: []runtime.Object{
				&v1alpha1.DataPlane{
					ObjectMeta: metav1.ObjectMeta{Name: "dp-2", Namespace: "default"},
					Status: v1alpha1.DataPlaneStatus{
						AppBox: &v1alpha1.Reference{Name: ""},
					},
				},
			},
			dpID:    "dp-2",
			wantARN: "",
		},
		{
			name: "appbox CR not found",
			objects: []runtime.Object{
				&v1alpha1.DataPlane{
					ObjectMeta: metav1.ObjectMeta{Name: "dp-3", Namespace: "default"},
					Status: v1alpha1.DataPlaneStatus{
						AppBox: &v1alpha1.Reference{Name: "appbox-missing"},
					},
				},
			},
			dpID:    "dp-3",
			wantARN: "",
		},
		{
			name: "node role ARN is empty",
			objects: []runtime.Object{
				&v1alpha1.DataPlane{
					ObjectMeta: metav1.ObjectMeta{Name: "dp-4", Namespace: "default"},
					Status: v1alpha1.DataPlaneStatus{
						AppBox: &v1alpha1.Reference{Name: "appbox-4"},
					},
				},
				&v1alpha1.APPBox{
					ObjectMeta: metav1.ObjectMeta{Name: "appbox-4", Namespace: "default"},
					Status:     v1alpha1.APPBoxStatus{},
				},
			},
			dpID:    "dp-4",
			wantARN: "",
		},
		{
			name: "node role ARN found",
			objects: []runtime.Object{
				&v1alpha1.DataPlane{
					ObjectMeta: metav1.ObjectMeta{Name: "dp-5", Namespace: "default"},
					Status: v1alpha1.DataPlaneStatus{
						AppBox: &v1alpha1.Reference{Name: "appbox-5"},
					},
				},
				&v1alpha1.APPBox{
					ObjectMeta: metav1.ObjectMeta{Name: "appbox-5", Namespace: "default"},
					Status: v1alpha1.APPBoxStatus{
						IAM: v1alpha1.IAMStatus{
							NodeRoleARN: "arn:aws:iam::123456789012:role/node-role-abc123",
						},
					},
				},
			},
			dpID:    "dp-5",
			wantARN: "arn:aws:iam::123456789012:role/node-role-abc123",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			builder := fake.NewClientBuilder().WithScheme(s)
			for _, obj := range tt.objects {
				builder = builder.WithRuntimeObjects(obj)
			}
			cli := builder.Build()

			arn, err := LookupNodeRoleARN(context.Background(), cli, key, tt.dpID)
			if tt.wantError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
			assert.Equal(t, tt.wantARN, arn)
		})
	}
}
