package iam

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"testing"

	ramclient "github.com/alibabacloud-go/ram-20150501/v2/client"
	"github.com/alibabacloud-go/tea/tea"
	iam0 "github.com/aws/aws-sdk-go-v2/service/iam"
	awsiamtypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
	"github.com/stretchr/testify/assert"
	"go.uber.org/mock/gomock"

	v1 "github.com/tidbcloud/observability/api/regionalserver/resourceworker/v1"
	mock_prodiders "github.com/tidbcloud/observability/test/mockresourceworker/providers"
)

// Helper function to create base64 encoded policy document
func createBase64Policy(policy AssumeRolePolicy) string {
	policyBytes, _ := json.Marshal(policy)
	return base64.StdEncoding.EncodeToString(policyBytes)
}

// Helper function to create test policy
func createTestPolicy(version string, statements []Statement) AssumeRolePolicy {
	return AssumeRolePolicy{
		Version:   version,
		Statement: statements,
	}
}

func TestUpdateAssumeEntityAliCloud(t *testing.T) {
	tests := []struct {
		name           string
		roleName       string
		role           *v1.IAM
		setupMocks     func(*mock_prodiders.MockIAMClientAliCloud)
		expectedError  string
		expectedResult bool
	}{
		{
			name:     "successful update with last policy document",
			roleName: "test-role",
			role: &v1.IAM{
				Spec: v1.IAMSpec{
					AliCloud: v1.AliCloudIAM{
						AssumeRolePolicyDocument: createBase64Policy(createTestPolicy("1", []Statement{
							{
								Effect: "Allow",
								Principal: map[string]interface{}{
									"Service": "ecs.aliyuncs.com",
								},
								Action: "sts:AssumeRole",
							},
						})),
						LastAssumeRolePolicyDocument: createBase64Policy(createTestPolicy("1", []Statement{
							{
								Effect: "Allow",
								Principal: map[string]interface{}{
									"Service": "ecs.aliyuncs.com",
								},
								Action: "sts:AssumeRole",
							},
						})),
					},
				},
			},
			setupMocks: func(mockClient *mock_prodiders.MockIAMClientAliCloud) {
				// Mock GetRole call
				mockClient.EXPECT().GetRole(gomock.Any(), "test-role").Return(
					&ramclient.GetRoleResponse{
						Body: &ramclient.GetRoleResponseBody{
							Role: &ramclient.GetRoleResponseBodyRole{
								AssumeRolePolicyDocument: tea.String(`{"Version":"1","Statement":[{"Effect":"Allow","Principal":{"Service":"ecs.aliyuncs.com"},"Action":"sts:AssumeRole"}]}`),
							},
						},
					}, true, nil)

				// Mock UpdateAssumeRolePolicy call
				mockClient.EXPECT().UpdateAssumeRolePolicy(gomock.Any(), "test-role", gomock.Any()).Return(nil)
			},
			expectedError:  "",
			expectedResult: true,
		},
		{
			name:     "successful update without last policy document",
			roleName: "test-role",
			role: &v1.IAM{
				Spec: v1.IAMSpec{
					AliCloud: v1.AliCloudIAM{
						AssumeRolePolicyDocument: createBase64Policy(createTestPolicy("1", []Statement{
							{
								Effect: "Allow",
								Principal: map[string]interface{}{
									"Service": "ecs.aliyuncs.com",
								},
								Action: "sts:AssumeRole",
							},
							{
								Effect: "Allow",
								Principal: map[string]interface{}{
									"Service": "rds.aliyuncs.com",
								},
								Action: "sts:AssumeRole",
							},
						})),
						LastAssumeRolePolicyDocument: "", // Empty last policy
					},
				},
			},
			setupMocks: func(mockClient *mock_prodiders.MockIAMClientAliCloud) {
				// Mock GetRole call - return policy with one statement
				mockClient.EXPECT().GetRole(gomock.Any(), "test-role").Return(
					&ramclient.GetRoleResponse{
						Body: &ramclient.GetRoleResponseBody{
							Role: &ramclient.GetRoleResponseBodyRole{
								AssumeRolePolicyDocument: tea.String(`{"Version":"1","Statement":[{"Effect":"Allow","Principal":{"Service":"ecs.aliyuncs.com"},"Action":"sts:AssumeRole"}]}`),
							},
						},
					}, true, nil)

				// Mock UpdateAssumeRolePolicy call
				mockClient.EXPECT().UpdateAssumeRolePolicy(gomock.Any(), "test-role", gomock.Any()).Return(nil)
			},
			expectedError:  "",
			expectedResult: true,
		},
		{
			name:     "error decoding CRD policy document",
			roleName: "test-role",
			role: &v1.IAM{
				Spec: v1.IAMSpec{
					AliCloud: v1.AliCloudIAM{
						AssumeRolePolicyDocument: "invalid-base64",
					},
				},
			},
			setupMocks: func(mockClient *mock_prodiders.MockIAMClientAliCloud) {
				// No mocks needed as error occurs before any calls
			},
			expectedError:  "illegal base64 data",
			expectedResult: false,
		},
		{
			name:     "error getting role from AliCloud",
			roleName: "test-role",
			role: &v1.IAM{
				Spec: v1.IAMSpec{
					AliCloud: v1.AliCloudIAM{
						AssumeRolePolicyDocument: createBase64Policy(createTestPolicy("1", []Statement{
							{
								Effect: "Allow",
								Principal: map[string]interface{}{
									"Service": "ecs.aliyuncs.com",
								},
								Action: "sts:AssumeRole",
							},
						})),
					},
				},
			},
			setupMocks: func(mockClient *mock_prodiders.MockIAMClientAliCloud) {
				// Mock GetRole call to return error
				mockClient.EXPECT().GetRole(gomock.Any(), "test-role").Return(
					(*ramclient.GetRoleResponse)(nil), false, errors.New("AliCloud API error"))
			},
			expectedError:  "AliCloud API error",
			expectedResult: false,
		},
		{
			name:     "error decoding last policy document",
			roleName: "test-role",
			role: &v1.IAM{
				Spec: v1.IAMSpec{
					AliCloud: v1.AliCloudIAM{
						AssumeRolePolicyDocument:     createBase64Policy(createTestPolicy("1", []Statement{})),
						LastAssumeRolePolicyDocument: "invalid-base64",
					},
				},
			},
			setupMocks: func(mockClient *mock_prodiders.MockIAMClientAliCloud) {
				// Mock GetRole call
				mockClient.EXPECT().GetRole(gomock.Any(), "test-role").Return(
					&ramclient.GetRoleResponse{
						Body: &ramclient.GetRoleResponseBody{
							Role: &ramclient.GetRoleResponseBodyRole{
								AssumeRolePolicyDocument: tea.String(`{"Version":"1","Statement":[]}`),
							},
						},
					}, true, nil)
			},
			expectedError:  "illegal base64 data",
			expectedResult: false,
		},
		{
			name:     "error updating assume role policy",
			roleName: "test-role",
			role: &v1.IAM{
				Spec: v1.IAMSpec{
					AliCloud: v1.AliCloudIAM{
						AssumeRolePolicyDocument: createBase64Policy(createTestPolicy("1", []Statement{
							{
								Effect: "Allow",
								Principal: map[string]interface{}{
									"Service": "ecs.aliyuncs.com",
								},
								Action: "sts:AssumeRole",
							},
						})),
						LastAssumeRolePolicyDocument: createBase64Policy(createTestPolicy("1", []Statement{
							{
								Effect: "Allow",
								Principal: map[string]interface{}{
									"Service": "ecs.aliyuncs.com",
								},
								Action: "sts:AssumeRole",
							},
						})),
					},
				},
			},
			setupMocks: func(mockClient *mock_prodiders.MockIAMClientAliCloud) {
				// Mock GetRole call
				mockClient.EXPECT().GetRole(gomock.Any(), "test-role").Return(
					&ramclient.GetRoleResponse{
						Body: &ramclient.GetRoleResponseBody{
							Role: &ramclient.GetRoleResponseBodyRole{
								AssumeRolePolicyDocument: tea.String(`{"Version":"1","Statement":[{"Effect":"Allow","Principal":{"Service":"ecs.aliyuncs.com"},"Action":"sts:AssumeRole"}]}`),
							},
						},
					}, true, nil)

				// Mock UpdateAssumeRolePolicy call to return error
				mockClient.EXPECT().UpdateAssumeRolePolicy(gomock.Any(), "test-role", gomock.Any()).Return(errors.New("update failed"))
			},
			expectedError:  "update assume role policy failed: update failed",
			expectedResult: false,
		},
		{
			name:     "empty policy document from AliCloud",
			roleName: "test-role",
			role: &v1.IAM{
				Spec: v1.IAMSpec{
					AliCloud: v1.AliCloudIAM{
						AssumeRolePolicyDocument: createBase64Policy(createTestPolicy("1", []Statement{
							{
								Effect: "Allow",
								Principal: map[string]interface{}{
									"Service": "ecs.aliyuncs.com",
								},
								Action: "sts:AssumeRole",
							},
						})),
					},
				},
			},
			setupMocks: func(mockClient *mock_prodiders.MockIAMClientAliCloud) {
				// Mock GetRole call with empty policy document
				mockClient.EXPECT().GetRole(gomock.Any(), "test-role").Return(
					&ramclient.GetRoleResponse{
						Body: &ramclient.GetRoleResponseBody{
							Role: &ramclient.GetRoleResponseBodyRole{
								AssumeRolePolicyDocument: tea.String(""),
							},
						},
					}, true, nil)

				// Mock UpdateAssumeRolePolicy call
				mockClient.EXPECT().UpdateAssumeRolePolicy(gomock.Any(), "test-role", gomock.Any()).Return(nil)
			},
			expectedError:  "",
			expectedResult: true,
		},
	}

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

			// Create mock client
			mockClient := mock_prodiders.NewMockIAMClientAliCloud(ctrl)
			tt.setupMocks(mockClient)

			// Create IAM instance with mock client
			iam := &IAM{
				aliCli: mockClient,
			}

			// Call the function under test
			result, err := iam.updateAssumeEntityAliCloud(context.Background(), tt.roleName, tt.role)

			// Verify expectations
			if tt.expectedError != "" {
				assert.Error(t, err)
				assert.Contains(t, err.Error(), tt.expectedError)
				assert.Nil(t, result)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, result)

				// Verify the returned policy document is valid JSON
				var policy AssumeRolePolicy
				err = json.Unmarshal(result, &policy)
				assert.NoError(t, err)
				assert.Equal(t, "1", policy.Version) // AliCloud uses version "1"
			}
		})
	}
}

func TestGetAliCloudAssumeEntityFromCRD(t *testing.T) {
	tests := []struct {
		name           string
		policyDocument string
		expectedError  string
		expectedPolicy AssumeRolePolicy
	}{
		{
			name: "valid policy document",
			policyDocument: createBase64Policy(AssumeRolePolicy{
				Version: "1",
				Statement: []Statement{
					{
						Effect: "Allow",
						Principal: map[string]interface{}{
							"Service": "ecs.aliyuncs.com",
						},
						Action: "sts:AssumeRole",
					},
				},
			}),
			expectedError: "",
			expectedPolicy: AssumeRolePolicy{
				Version: "1",
				Statement: []Statement{
					{
						Effect: "Allow",
						Principal: map[string]interface{}{
							"Service": "ecs.aliyuncs.com",
						},
						Action: "sts:AssumeRole",
					},
				},
			},
		},
		{
			name:           "invalid base64",
			policyDocument: "invalid-base64",
			expectedError:  "illegal base64 data",
		},
		{
			name:           "invalid JSON",
			policyDocument: base64.StdEncoding.EncodeToString([]byte("invalid-json")),
			expectedError:  "invalid character",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			policy, err := getAliCloudAssumeEntityFromCRD(tt.policyDocument)

			if tt.expectedError != "" {
				assert.Error(t, err)
				assert.Contains(t, err.Error(), tt.expectedError)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tt.expectedPolicy, policy)
			}
		})
	}
}

func TestGetAWSAssumeEntityFromCRD(t *testing.T) {
	tests := []struct {
		name           string
		policyDocument string
		expectedError  string
		expectedPolicy AssumeRolePolicy
	}{
		{
			name: "valid AWS policy document",
			policyDocument: createBase64Policy(AssumeRolePolicy{
				Version: "2012-10-17",
				Statement: []Statement{
					{
						Effect: "Allow",
						Principal: map[string]any{
							"AWS": "arn:aws:iam::123456789012:root",
						},
						Action: "sts:AssumeRole",
					},
				},
			}),
			expectedError: "",
			expectedPolicy: AssumeRolePolicy{
				Version: "2012-10-17",
				Statement: []Statement{
					{
						Effect: "Allow",
						Principal: map[string]any{
							"AWS": "arn:aws:iam::123456789012:root",
						},
						Action: "sts:AssumeRole",
					},
				},
			},
		},
		{
			name:           "invalid base64",
			policyDocument: "invalid-base64!!!",
			expectedError:  "illegal base64 data",
		},
		{
			name:           "invalid JSON",
			policyDocument: base64.StdEncoding.EncodeToString([]byte("{invalid-json")),
			expectedError:  "invalid character",
		},
		{
			name: "policy with condition",
			policyDocument: createBase64Policy(AssumeRolePolicy{
				Version: "2012-10-17",
				Statement: []Statement{
					{
						Effect: "Allow",
						Principal: map[string]any{
							"AWS": "arn:aws:iam::123456789012:root",
						},
						Action: "sts:AssumeRole",
						Condition: map[string]any{
							"StringEquals": map[string]any{
								"sts:ExternalId": "unique-external-id",
							},
						},
					},
				},
			}),
			expectedError: "",
			expectedPolicy: AssumeRolePolicy{
				Version: "2012-10-17",
				Statement: []Statement{
					{
						Effect: "Allow",
						Principal: map[string]any{
							"AWS": "arn:aws:iam::123456789012:root",
						},
						Action: "sts:AssumeRole",
						Condition: map[string]any{
							"StringEquals": map[string]any{
								"sts:ExternalId": "unique-external-id",
							},
						},
					},
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			policy, err := getAWSAssumeEntityFromCRD(tt.policyDocument)

			if tt.expectedError != "" {
				assert.Error(t, err)
				assert.Contains(t, err.Error(), tt.expectedError)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tt.expectedPolicy.Version, policy.Version)
				assert.Equal(t, len(tt.expectedPolicy.Statement), len(policy.Statement))
			}
		})
	}
}

func TestGetAWSPolicyStatementsWithUniqueAWSPrincipals(t *testing.T) {
	tests := []struct {
		name               string
		statements         []Statement
		expectedStatements int
	}{
		{
			name: "unique principals",
			statements: []Statement{
				{
					Effect: "Allow",
					Principal: map[string]any{
						"AWS": "arn:aws:iam::111111111111:root",
					},
					Action: "sts:AssumeRole",
				},
				{
					Effect: "Allow",
					Principal: map[string]any{
						"AWS": "arn:aws:iam::222222222222:root",
					},
					Action: "sts:AssumeRole",
				},
			},
			expectedStatements: 2,
		},
		{
			name: "duplicate principals",
			statements: []Statement{
				{
					Effect: "Allow",
					Principal: map[string]any{
						"AWS": "arn:aws:iam::111111111111:root",
					},
					Action: "sts:AssumeRole",
				},
				{
					Effect: "Allow",
					Principal: map[string]any{
						"AWS": "arn:aws:iam::111111111111:root",
					},
					Action: "sts:AssumeRole",
				},
			},
			expectedStatements: 1,
		},
		{
			name: "non-AWS principals",
			statements: []Statement{
				{
					Effect: "Allow",
					Principal: map[string]any{
						"Service": "ec2.amazonaws.com",
					},
					Action: "sts:AssumeRole",
				},
				{
					Effect: "Allow",
					Principal: map[string]any{
						"AWS": "arn:aws:iam::111111111111:root",
					},
					Action: "sts:AssumeRole",
				},
			},
			expectedStatements: 2,
		},
		{
			name:               "empty statements",
			statements:         []Statement{},
			expectedStatements: 0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := getAWSPolicyStatementsWithUniqueAWSPrincipals(tt.statements)
			assert.Equal(t, tt.expectedStatements, len(result))
		})
	}
}

func TestGetAWSPrincipals(t *testing.T) {
	tests := []struct {
		name                    string
		statementsFromCRD       []Statement
		awsPrincipalsFromAWS    map[string]struct{}
		expectedPrincipalsCount int
		expectedValidPrincipals int
		expectedUndeletedCount  int
		expectedDeletedNotEmpty int
	}{
		{
			name: "all principals exist in AWS",
			statementsFromCRD: []Statement{
				{
					Effect: "Allow",
					Principal: map[string]any{
						"AWS": "arn:aws:iam::111111111111:root",
					},
					Action: "sts:AssumeRole",
				},
			},
			awsPrincipalsFromAWS: map[string]struct{}{
				"arn:aws:iam::111111111111:root": {},
			},
			expectedPrincipalsCount: 1,
			expectedValidPrincipals: 1,
			expectedUndeletedCount:  1,
		},
		{
			name: "principal not in AWS",
			statementsFromCRD: []Statement{
				{
					Effect: "Allow",
					Principal: map[string]any{
						"AWS": "arn:aws:iam::111111111111:root",
					},
					Action: "sts:AssumeRole",
				},
			},
			awsPrincipalsFromAWS:    map[string]struct{}{},
			expectedPrincipalsCount: 1,
			expectedValidPrincipals: 0,
		},
		{
			name: "principal deleted but has other principals",
			statementsFromCRD: []Statement{
				{
					Effect: "Allow",
					Principal: map[string]any{
						"AWS":     "arn:aws:iam::111111111111:root",
						"Service": "ec2.amazonaws.com",
					},
					Action: "sts:AssumeRole",
				},
			},
			awsPrincipalsFromAWS:    map[string]struct{}{},
			expectedPrincipalsCount: 1,
			expectedValidPrincipals: 1,
			expectedDeletedNotEmpty: 1,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			principals, validPrincipals := getAWSPrincipals(tt.statementsFromCRD, tt.awsPrincipalsFromAWS)

			assert.Equal(t, tt.expectedPrincipalsCount, len(principals))
			assert.Equal(t, tt.expectedValidPrincipals, len(validPrincipals))

			if tt.expectedUndeletedCount > 0 {
				undeletedCount := 0
				for _, status := range validPrincipals {
					if status == undeleted {
						undeletedCount++
					}
				}
				assert.Equal(t, tt.expectedUndeletedCount, undeletedCount)
			}

			if tt.expectedDeletedNotEmpty > 0 {
				deletedCount := 0
				for _, status := range validPrincipals {
					if status == deletedAndPrincipalsNotEmpty {
						deletedCount++
					}
				}
				assert.Equal(t, tt.expectedDeletedNotEmpty, deletedCount)
			}
		})
	}
}

func TestCalcAssumeRolePolicy(t *testing.T) {
	tests := []struct {
		name              string
		policyFromCRD     AssumeRolePolicy
		lastPolicyFromCRD AssumeRolePolicy
		policyFromAWS     AssumeRolePolicy
		expectedCount     int
	}{
		{
			name: "new principal added",
			policyFromCRD: AssumeRolePolicy{
				Version: "2012-10-17",
				Statement: []Statement{
					{
						Effect: "Allow",
						Principal: map[string]any{
							"AWS": "arn:aws:iam::111111111111:root",
						},
						Action: "sts:AssumeRole",
					},
					{
						Effect: "Allow",
						Principal: map[string]any{
							"AWS": "arn:aws:iam::222222222222:root",
						},
						Action: "sts:AssumeRole",
					},
				},
			},
			lastPolicyFromCRD: AssumeRolePolicy{
				Version: "2012-10-17",
				Statement: []Statement{
					{
						Effect: "Allow",
						Principal: map[string]any{
							"AWS": "arn:aws:iam::111111111111:root",
						},
						Action: "sts:AssumeRole",
					},
				},
			},
			policyFromAWS: AssumeRolePolicy{
				Version: "2012-10-17",
				Statement: []Statement{
					{
						Effect: "Allow",
						Principal: map[string]any{
							"AWS": "arn:aws:iam::111111111111:root",
						},
						Action: "sts:AssumeRole",
					},
				},
			},
			expectedCount: 2,
		},
		{
			name: "principal removed from AWS",
			policyFromCRD: AssumeRolePolicy{
				Version: "2012-10-17",
				Statement: []Statement{
					{
						Effect: "Allow",
						Principal: map[string]any{
							"AWS": "arn:aws:iam::111111111111:root",
						},
						Action: "sts:AssumeRole",
					},
				},
			},
			lastPolicyFromCRD: AssumeRolePolicy{
				Version: "2012-10-17",
				Statement: []Statement{
					{
						Effect: "Allow",
						Principal: map[string]any{
							"AWS": "arn:aws:iam::111111111111:root",
						},
						Action: "sts:AssumeRole",
					},
				},
			},
			policyFromAWS: AssumeRolePolicy{
				Version:   "2012-10-17",
				Statement: []Statement{},
			},
			expectedCount: 0,
		},
		{
			name: "non-AWS principal preserved",
			policyFromCRD: AssumeRolePolicy{
				Version: "2012-10-17",
				Statement: []Statement{
					{
						Effect: "Allow",
						Principal: map[string]any{
							"Service": "ec2.amazonaws.com",
						},
						Action: "sts:AssumeRole",
					},
				},
			},
			lastPolicyFromCRD: AssumeRolePolicy{
				Version:   "2012-10-17",
				Statement: []Statement{},
			},
			policyFromAWS: AssumeRolePolicy{
				Version:   "2012-10-17",
				Statement: []Statement{},
			},
			expectedCount: 1,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := calcAssumeRolePolicy(tt.policyFromCRD, tt.lastPolicyFromCRD, tt.policyFromAWS)
			assert.Equal(t, tt.expectedCount, len(result.Statement))
			assert.Equal(t, tt.policyFromCRD.Version, result.Version)
		})
	}
}

func TestCalcAssumeRoleAccountGCP(t *testing.T) {
	tests := []struct {
		name                string
		accountsFromCRD     []string
		lastAccountsFromCRD []string
		accountsFromGCP     []string
		expectedAccounts    []string
	}{
		{
			name:                "new account added",
			accountsFromCRD:     []string{"account1", "account2"},
			lastAccountsFromCRD: []string{"account1"},
			accountsFromGCP:     []string{"account1"},
			expectedAccounts:    []string{"account1", "account2"},
		},
		{
			name:                "account removed from GCP",
			accountsFromCRD:     []string{"account1"},
			lastAccountsFromCRD: []string{"account1"},
			accountsFromGCP:     []string{},
			expectedAccounts:    []string{},
		},
		{
			name:                "all accounts valid",
			accountsFromCRD:     []string{"account1", "account2"},
			lastAccountsFromCRD: []string{"account1", "account2"},
			accountsFromGCP:     []string{"account1", "account2"},
			expectedAccounts:    []string{"account1", "account2"},
		},
		{
			name:                "empty accounts",
			accountsFromCRD:     []string{},
			lastAccountsFromCRD: []string{},
			accountsFromGCP:     []string{},
			expectedAccounts:    []string{},
		},
		{
			name:                "account with empty string",
			accountsFromCRD:     []string{"account1", "", "account2"},
			lastAccountsFromCRD: []string{"account1"},
			accountsFromGCP:     []string{"account1"},
			expectedAccounts:    []string{"account1", "account2"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := calcAssumeRoleAccountGCP(tt.accountsFromCRD, tt.lastAccountsFromCRD, tt.accountsFromGCP)
			assert.Equal(t, tt.expectedAccounts, result)
		})
	}
}

func TestGetAliCloudAssumeEntityFromAliCloud(t *testing.T) {
	tests := []struct {
		name           string
		roleName       string
		setupMocks     func(*mock_prodiders.MockIAMClientAliCloud)
		expectedError  string
		expectedPolicy AssumeRolePolicy
	}{
		{
			name:     "successful retrieval",
			roleName: "test-role",
			setupMocks: func(mockClient *mock_prodiders.MockIAMClientAliCloud) {
				mockClient.EXPECT().GetRole(gomock.Any(), "test-role").Return(
					&ramclient.GetRoleResponse{
						Body: &ramclient.GetRoleResponseBody{
							Role: &ramclient.GetRoleResponseBodyRole{
								AssumeRolePolicyDocument: tea.String(`{"Version":"1","Statement":[{"Effect":"Allow","Principal":{"Service":"ecs.aliyuncs.com"},"Action":"sts:AssumeRole"}]}`),
							},
						},
					}, true, nil)
			},
			expectedError: "",
			expectedPolicy: AssumeRolePolicy{
				Version: "1",
				Statement: []Statement{
					{
						Effect: "Allow",
						Principal: map[string]interface{}{
							"Service": "ecs.aliyuncs.com",
						},
						Action: "sts:AssumeRole",
					},
				},
			},
		},
		{
			name:     "role not found",
			roleName: "nonexistent-role",
			setupMocks: func(mockClient *mock_prodiders.MockIAMClientAliCloud) {
				mockClient.EXPECT().GetRole(gomock.Any(), "nonexistent-role").Return(
					(*ramclient.GetRoleResponse)(nil), false, nil)
			},
			expectedError: "role not found in aliyun",
		},
		{
			name:     "API error",
			roleName: "test-role",
			setupMocks: func(mockClient *mock_prodiders.MockIAMClientAliCloud) {
				mockClient.EXPECT().GetRole(gomock.Any(), "test-role").Return(
					(*ramclient.GetRoleResponse)(nil), false, errors.New("API error"))
			},
			expectedError: "get ram role failed",
		},
		{
			name:     "empty policy document",
			roleName: "test-role",
			setupMocks: func(mockClient *mock_prodiders.MockIAMClientAliCloud) {
				mockClient.EXPECT().GetRole(gomock.Any(), "test-role").Return(
					&ramclient.GetRoleResponse{
						Body: &ramclient.GetRoleResponseBody{
							Role: &ramclient.GetRoleResponseBodyRole{
								AssumeRolePolicyDocument: tea.String(""),
							},
						},
					}, true, nil)
			},
			expectedError: "",
			expectedPolicy: AssumeRolePolicy{
				Version:   "",
				Statement: nil,
			},
		},
	}

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

			// Create mock client
			mockClient := mock_prodiders.NewMockIAMClientAliCloud(ctrl)
			tt.setupMocks(mockClient)

			// Create IAM instance with mock client
			iam := &IAM{
				aliCli: mockClient,
			}

			// Call the function under test
			policy, err := iam.getAliCloudAssumeEntityFromAliCloud(context.Background(), tt.roleName)

			// Verify expectations
			if tt.expectedError != "" {
				assert.Error(t, err)
				assert.Contains(t, err.Error(), tt.expectedError)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tt.expectedPolicy, policy)
			}
		})
	}
}

// Integration-task external roles store the AWS principal as a list
// (createInitialAssumeRolePolicy writes "AWS": []string{arn}). After a JSON
// round-trip through the IAM CRD it decodes to []interface{}, which must never
// be used directly as a map key or the IAM reconcile panics with
// "hash of unhashable type: []interface {}".
func TestGetAWSPolicyStatementsWithUniqueAWSPrincipals_ListPrincipal(t *testing.T) {
	listPrincipal := func() map[string]any {
		return map[string]any{"AWS": []interface{}{
			"arn:aws:iam::111111111111:role/a",
			"arn:aws:iam::222222222222:role/b",
		}}
	}
	statements := []Statement{
		{Effect: "Allow", Principal: listPrincipal(), Action: "sts:AssumeRole"},
		{Effect: "Allow", Principal: listPrincipal(), Action: "sts:AssumeRole"},
	}

	var result []Statement
	assert.NotPanics(t, func() {
		result = getAWSPolicyStatementsWithUniqueAWSPrincipals(statements)
	})
	// Identical list principals de-duplicate to a single statement.
	assert.Len(t, result, 1)
}

func TestCalcAssumeRolePolicy_ListPrincipal(t *testing.T) {
	policy := AssumeRolePolicy{
		Version: "2012-10-17",
		Statement: []Statement{
			{
				Effect:    "Allow",
				Principal: map[string]any{"AWS": []interface{}{"arn:aws:iam::111111111111:role/a"}},
				Action:    "sts:AssumeRole",
			},
		},
	}

	var result AssumeRolePolicy
	assert.NotPanics(t, func() {
		result = calcAssumeRolePolicy(policy, AssumeRolePolicy{}, policy)
	})
	assert.Len(t, result.Statement, 1)
}

// When the three-way merge yields an empty trust policy (every desired principal
// was removed from AWS externally), the caller must NOT push an empty policy to
// AWS — AWS rejects it with "MalformedPolicyDocument: Statement is empty",
// crashing the reconcile. The update should be skipped instead.
func TestUpdateAssumeEntityAWS_SkipsEmptyPolicy(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockClient := mock_prodiders.NewMockIAMClient(ctrl)

	awsDoc := `{"Version":"2012-10-17","Statement":[]}`
	mockClient.EXPECT().GetRole(gomock.Any(), "test-role").Return(
		&iam0.GetRoleOutput{Role: &awsiamtypes.Role{AssumeRolePolicyDocument: &awsDoc}}, true, nil)
	// No EXPECT for UpdateAssumeRolePolicy: any call is an unexpected-call failure.

	desired := createBase64Policy(AssumeRolePolicy{
		Version: "2012-10-17",
		Statement: []Statement{
			{
				Effect:    "Allow",
				Principal: map[string]any{"AWS": "arn:aws:iam::111111111111:role/customer"},
				Action:    "sts:AssumeRole",
			},
		},
	})
	role := &v1.IAM{
		Spec: v1.IAMSpec{
			Aws: v1.AWSIAM{
				AssumeRolePolicyDocument:     desired,
				LastAssumeRolePolicyDocument: desired,
			},
		},
	}

	iamInst := &IAM{iamcli: mockClient}
	result, err := iamInst.updateAssumeEntityAWS(context.Background(), "test-role", role)
	assert.NoError(t, err)
	assert.Nil(t, result)
}
