package integration

import (
	"context"
	"testing"

	"github.com/seaweedfs/seaweedfs/weed/iam/policy"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// TestTrustPolicyAWSUserPrincipal tests that trust policies with specific AWS user
// principals work correctly with ValidateTrustPolicyForPrincipal.
// This is a regression test for the case where setting a specific user principal like:
//
//	"Principal": {"AWS": "arn:aws:iam::000000000000:user/backend"}
//
// would fail with "trust policy denies access to principal" even though the
// caller's principal ARN matched.
func TestTrustPolicyAWSUserPrincipal(t *testing.T) {
	iamManager := setupIntegratedIAMSystem(t)
	ctx := context.Background()

	const (
		accountID   = "000000000000"
		backendUser = "backend"
		backendArn  = "arn:aws:iam::" + accountID + ":user/" + backendUser
		otherUser   = "other"
		otherArn    = "arn:aws:iam::" + accountID + ":user/" + otherUser
		clientRoleN = "ClientRole"
		clientRoleA = "arn:aws:iam::role/" + clientRoleN
	)

	// Create role with trust policy restricted to a specific AWS user principal
	// (the exact scenario from discussion #8588)
	err := iamManager.CreateRole(ctx, "", clientRoleN, &RoleDefinition{
		RoleName: clientRoleN,
		TrustPolicy: &policy.PolicyDocument{
			Version: "2012-10-17",
			Statement: []policy.Statement{
				{
					Effect: "Allow",
					Principal: map[string]interface{}{
						"AWS": backendArn,
					},
					Action: []string{"sts:AssumeRole"},
				},
			},
		},
		AttachedPolicies: []string{"S3ReadOnlyPolicy"},
	})
	require.NoError(t, err)

	tests := []struct {
		name         string
		principalArn string
		expectErr    bool
		description  string
	}{
		{
			name:         "matching user principal should be allowed",
			principalArn: backendArn,
			expectErr:    false,
			description:  "Backend user ARN matches the trust policy principal exactly",
		},
		{
			name:         "non-matching user principal should be denied",
			principalArn: otherArn,
			expectErr:    true,
			description:  "Other user ARN does not match the trust policy principal",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := iamManager.ValidateTrustPolicyForPrincipal(ctx, clientRoleA, tt.principalArn)
			if tt.expectErr {
				assert.Error(t, err, tt.description)
				assert.Contains(t, err.Error(), "trust policy denies access")
			} else {
				assert.NoError(t, err, tt.description)
			}
		})
	}
}

// TestTrustPolicyAWSWildcardPrincipal tests that wildcard AWS principals
// continue to work correctly (this already works, serves as a regression guard).
func TestTrustPolicyAWSWildcardPrincipal(t *testing.T) {
	iamManager := setupIntegratedIAMSystem(t)
	ctx := context.Background()

	const roleName = "WildcardAWSRole"
	const roleArn = "arn:aws:iam::role/" + roleName

	err := iamManager.CreateRole(ctx, "", roleName, &RoleDefinition{
		RoleName: roleName,
		TrustPolicy: &policy.PolicyDocument{
			Version: "2012-10-17",
			Statement: []policy.Statement{
				{
					Effect: "Allow",
					Principal: map[string]interface{}{
						"AWS": "*",
					},
					Action: []string{"sts:AssumeRole"},
				},
			},
		},
		AttachedPolicies: []string{"S3ReadOnlyPolicy"},
	})
	require.NoError(t, err)

	// Any user should be able to assume a role with wildcard AWS principal
	err = iamManager.ValidateTrustPolicyForPrincipal(ctx, roleArn, "arn:aws:iam::000000000000:user/anyuser")
	assert.NoError(t, err, "Wildcard AWS principal should allow any user")
}

// TestTrustPolicyAWSUserArrayPrincipal tests trust policies with an array of
// AWS user principals.
func TestTrustPolicyAWSUserArrayPrincipal(t *testing.T) {
	iamManager := setupIntegratedIAMSystem(t)
	ctx := context.Background()

	const (
		accountID = "000000000000"
		roleName  = "MultiUserRole"
		roleArn   = "arn:aws:iam::role/" + roleName
		user1Arn  = "arn:aws:iam::" + accountID + ":user/user1"
		user2Arn  = "arn:aws:iam::" + accountID + ":user/user2"
		user3Arn  = "arn:aws:iam::" + accountID + ":user/user3"
	)

	// Create role with multiple allowed user principals
	err := iamManager.CreateRole(ctx, "", roleName, &RoleDefinition{
		RoleName: roleName,
		TrustPolicy: &policy.PolicyDocument{
			Version: "2012-10-17",
			Statement: []policy.Statement{
				{
					Effect: "Allow",
					Principal: map[string]interface{}{
						"AWS": []interface{}{user1Arn, user2Arn},
					},
					Action: []string{"sts:AssumeRole"},
				},
			},
		},
		AttachedPolicies: []string{"S3ReadOnlyPolicy"},
	})
	require.NoError(t, err)

	tests := []struct {
		name         string
		principalArn string
		expectErr    bool
	}{
		{
			name:         "first listed user is allowed",
			principalArn: user1Arn,
			expectErr:    false,
		},
		{
			name:         "second listed user is allowed",
			principalArn: user2Arn,
			expectErr:    false,
		},
		{
			name:         "unlisted user is denied",
			principalArn: user3Arn,
			expectErr:    true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := iamManager.ValidateTrustPolicyForPrincipal(ctx, roleArn, tt.principalArn)
			if tt.expectErr {
				if assert.Error(t, err) {
					assert.Contains(t, err.Error(), "trust policy denies access")
				}
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

// TestTrustPolicyPlainStringPrincipal tests trust policies with a plain string
// principal (not wrapped in {"AWS": ...}).
func TestTrustPolicyPlainStringPrincipal(t *testing.T) {
	iamManager := setupIntegratedIAMSystem(t)
	ctx := context.Background()

	const (
		roleName = "PlainStringPrincipalRole"
		roleArn  = "arn:aws:iam::role/" + roleName
		userArn  = "arn:aws:iam::000000000000:user/backend"
	)

	// Plain string wildcard - should work
	err := iamManager.CreateRole(ctx, "", roleName, &RoleDefinition{
		RoleName: roleName,
		TrustPolicy: &policy.PolicyDocument{
			Version: "2012-10-17",
			Statement: []policy.Statement{
				{
					Effect:    "Allow",
					Principal: "*",
					Action:    []string{"sts:AssumeRole"},
				},
			},
		},
		AttachedPolicies: []string{"S3ReadOnlyPolicy"},
	})
	require.NoError(t, err)

	err = iamManager.ValidateTrustPolicyForPrincipal(ctx, roleArn, userArn)
	assert.NoError(t, err, "Plain wildcard principal should allow any user")

	// Plain string with specific ARN
	const roleName2 = "PlainStringSpecificRole"
	const roleArn2 = "arn:aws:iam::role/" + roleName2

	err = iamManager.CreateRole(ctx, "", roleName2, &RoleDefinition{
		RoleName: roleName2,
		TrustPolicy: &policy.PolicyDocument{
			Version: "2012-10-17",
			Statement: []policy.Statement{
				{
					Effect:    "Allow",
					Principal: userArn,
					Action:    []string{"sts:AssumeRole"},
				},
			},
		},
		AttachedPolicies: []string{"S3ReadOnlyPolicy"},
	})
	require.NoError(t, err)

	err = iamManager.ValidateTrustPolicyForPrincipal(ctx, roleArn2, userArn)
	assert.NoError(t, err, "Matching plain string principal should allow user")

	err = iamManager.ValidateTrustPolicyForPrincipal(ctx, roleArn2, "arn:aws:iam::000000000000:user/other")
	if assert.Error(t, err, "Non-matching plain string principal should deny user") {
		assert.Contains(t, err.Error(), "trust policy denies access")
	}
}
