package iam

import (
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/iam"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// TestIAMUserManagement tests user management operations
func TestIAMUserManagement(t *testing.T) {
	framework := NewS3IAMTestFramework(t)
	defer framework.Cleanup()

	// Create IAM client with admin privileges
	iamClient, err := framework.CreateIAMClientWithJWT("admin-user", "TestAdminRole")
	require.NoError(t, err)

	t.Run("create_and_get_user", func(t *testing.T) {
		userName := "test-user-mgm"

		// Create user
		createResp, err := iamClient.CreateUser(&iam.CreateUserInput{
			UserName: aws.String(userName),
		})
		require.NoError(t, err)
		assert.Equal(t, userName, *createResp.User.UserName)

		// Get user
		getResp, err := iamClient.GetUser(&iam.GetUserInput{
			UserName: aws.String(userName),
		})
		require.NoError(t, err)
		assert.Equal(t, userName, *getResp.User.UserName)

		// List users to verify existence
		listResp, err := iamClient.ListUsers(&iam.ListUsersInput{})
		require.NoError(t, err)
		found := false
		for _, user := range listResp.Users {
			if *user.UserName == userName {
				found = true
				break
			}
		}
		assert.True(t, found, "Created user should be listed")

		// Clean up
		_, err = iamClient.DeleteUser(&iam.DeleteUserInput{
			UserName: aws.String(userName),
		})
		require.NoError(t, err)
	})

	t.Run("update_user", func(t *testing.T) {
		userName := "user-to-update"
		newUserName := "user-updated"

		// Create user
		_, err := iamClient.CreateUser(&iam.CreateUserInput{
			UserName: aws.String(userName),
		})
		require.NoError(t, err)
		defer func() {
			// Try to delete both just in case
			iamClient.DeleteUser(&iam.DeleteUserInput{UserName: aws.String(userName)})
			iamClient.DeleteUser(&iam.DeleteUserInput{UserName: aws.String(newUserName)})
		}()

		// Update user name
		_, err = iamClient.UpdateUser(&iam.UpdateUserInput{
			UserName:    aws.String(userName),
			NewUserName: aws.String(newUserName),
		})
		require.NoError(t, err)

		// Verify update (GetUser with NEW name should work)
		getResp, err := iamClient.GetUser(&iam.GetUserInput{
			UserName: aws.String(newUserName),
		})
		require.NoError(t, err)
		assert.Equal(t, newUserName, *getResp.User.UserName)

		// GetUser with OLD name should fail
		_, err = iamClient.GetUser(&iam.GetUserInput{
			UserName: aws.String(userName),
		})
		require.Error(t, err)
	})
}

// TestIAMAccessKeyManagement tests access key operations
func TestIAMAccessKeyManagement(t *testing.T) {
	framework := NewS3IAMTestFramework(t)
	defer framework.Cleanup()

	iamClient, err := framework.CreateIAMClientWithJWT("admin-user", "TestAdminRole")
	require.NoError(t, err)

	userName := "test-user-keys"
	_, err = iamClient.CreateUser(&iam.CreateUserInput{
		UserName: aws.String(userName),
	})
	require.NoError(t, err)
	defer iamClient.DeleteUser(&iam.DeleteUserInput{UserName: aws.String(userName)})

	t.Run("create_list_delete_access_key", func(t *testing.T) {
		// Create access key
		createResp, err := iamClient.CreateAccessKey(&iam.CreateAccessKeyInput{
			UserName: aws.String(userName),
		})
		require.NoError(t, err)
		assert.NotEmpty(t, *createResp.AccessKey.AccessKeyId)
		assert.NotEmpty(t, *createResp.AccessKey.SecretAccessKey)
		assert.Equal(t, "Active", *createResp.AccessKey.Status)

		// List access keys
		listResp, err := iamClient.ListAccessKeys(&iam.ListAccessKeysInput{
			UserName: aws.String(userName),
		})
		require.NoError(t, err)
		assert.Equal(t, 1, len(listResp.AccessKeyMetadata))
		assert.Equal(t, *createResp.AccessKey.AccessKeyId, *listResp.AccessKeyMetadata[0].AccessKeyId)

		// Delete access key
		_, err = iamClient.DeleteAccessKey(&iam.DeleteAccessKeyInput{
			UserName:    aws.String(userName),
			AccessKeyId: createResp.AccessKey.AccessKeyId,
		})
		require.NoError(t, err)

		// Verify deletion
		listResp, err = iamClient.ListAccessKeys(&iam.ListAccessKeysInput{
			UserName: aws.String(userName),
		})
		require.NoError(t, err)
		assert.Equal(t, 0, len(listResp.AccessKeyMetadata))
	})

	t.Run("update_access_key_status", func(t *testing.T) {
		// Create access key
		createResp, err := iamClient.CreateAccessKey(&iam.CreateAccessKeyInput{
			UserName: aws.String(userName),
		})
		require.NoError(t, err)
		defer iamClient.DeleteAccessKey(&iam.DeleteAccessKeyInput{
			UserName:    aws.String(userName),
			AccessKeyId: createResp.AccessKey.AccessKeyId,
		})

		// Update to Inactive
		_, err = iamClient.UpdateAccessKey(&iam.UpdateAccessKeyInput{
			UserName:    aws.String(userName),
			AccessKeyId: createResp.AccessKey.AccessKeyId,
			Status:      aws.String("Inactive"),
		})
		require.NoError(t, err)

		// Verify update in ListAccessKeys
		listResp, err := iamClient.ListAccessKeys(&iam.ListAccessKeysInput{
			UserName: aws.String(userName),
		})
		require.NoError(t, err)
		found := false
		for _, key := range listResp.AccessKeyMetadata {
			if *key.AccessKeyId == *createResp.AccessKey.AccessKeyId {
				assert.Equal(t, "Inactive", *key.Status)
				found = true
				break
			}
		}
		assert.True(t, found)
	})
}

// TestIAMPolicyManagement tests policy operations
func TestIAMPolicyManagement(t *testing.T) {
	framework := NewS3IAMTestFramework(t)
	defer framework.Cleanup()

	iamClient, err := framework.CreateIAMClientWithJWT("admin-user", "TestAdminRole")
	require.NoError(t, err)

	t.Run("create_managed_policy", func(t *testing.T) {
		policyName := "test-managed-policy"
		policyDoc := `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:ListBucket","Resource":"*"}]}`

		createResp, err := iamClient.CreatePolicy(&iam.CreatePolicyInput{
			PolicyName:     aws.String(policyName),
			PolicyDocument: aws.String(policyDoc),
		})
		require.NoError(t, err)
		assert.Equal(t, policyName, *createResp.Policy.PolicyName)
		assert.NotEmpty(t, *createResp.Policy.Arn)

		t.Cleanup(func() {
			_, _ = iamClient.DeletePolicy(&iam.DeletePolicyInput{
				PolicyArn: createResp.Policy.Arn,
			})
		})
	})

	t.Run("managed_policy_crud_lifecycle", func(t *testing.T) {
		policyDoc := `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:GetObject","Resource":"arn:aws:s3:::*"}]}`

		policyNames := []string{"test-managed-policy-lifecycle-a", "test-managed-policy-lifecycle-b"}
		policyArns := make([]*string, 0, len(policyNames))
		for _, policyName := range policyNames {
			createResp, err := iamClient.CreatePolicy(&iam.CreatePolicyInput{
				PolicyName:     aws.String(policyName),
				PolicyDocument: aws.String(policyDoc),
			})
			require.NoError(t, err)
			policyArns = append(policyArns, createResp.Policy.Arn)
		}

		t.Cleanup(func() {
			for _, policyArn := range policyArns {
				_, _ = iamClient.DeletePolicy(&iam.DeletePolicyInput{PolicyArn: policyArn})
			}
		})

		listResp, err := iamClient.ListPolicies(&iam.ListPoliciesInput{})
		require.NoError(t, err)

		foundByName := map[string]bool{}
		for _, policy := range listResp.Policies {
			if policy.PolicyName != nil {
				foundByName[*policy.PolicyName] = true
			}
		}
		for _, policyName := range policyNames {
			assert.True(t, foundByName[policyName], "policy %s should be listed", policyName)
		}

		getResp, err := iamClient.GetPolicy(&iam.GetPolicyInput{PolicyArn: policyArns[0]})
		require.NoError(t, err)
		require.NotNil(t, getResp.Policy)
		assert.Equal(t, policyNames[0], aws.StringValue(getResp.Policy.PolicyName))
		assert.Equal(t, aws.StringValue(policyArns[0]), aws.StringValue(getResp.Policy.Arn))

		_, err = iamClient.DeletePolicy(&iam.DeletePolicyInput{PolicyArn: policyArns[0]})
		require.NoError(t, err)

		_, err = iamClient.GetPolicy(&iam.GetPolicyInput{PolicyArn: policyArns[0]})
		require.Error(t, err)
		awsErr, ok := err.(awserr.Error)
		require.True(t, ok)
		assert.Equal(t, iam.ErrCodeNoSuchEntityException, awsErr.Code())

		listAfterDeleteResp, err := iamClient.ListPolicies(&iam.ListPoliciesInput{})
		require.NoError(t, err)
		deletedPolicyFound := false
		remainingPolicyFound := false
		for _, policy := range listAfterDeleteResp.Policies {
			if policy.PolicyName == nil {
				continue
			}
			if *policy.PolicyName == policyNames[0] {
				deletedPolicyFound = true
			}
			if *policy.PolicyName == policyNames[1] {
				remainingPolicyFound = true
			}
		}
		assert.False(t, deletedPolicyFound, "deleted policy should no longer be listed")
		assert.True(t, remainingPolicyFound, "remaining policy should still be listed")

		policyArns[0] = nil
	})

	t.Run("managed_policy_versions", func(t *testing.T) {
		policyName := "test-managed-policy-version"
		policyDoc := `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:ListBucket","Resource":"*"}]}`

		createResp, err := iamClient.CreatePolicy(&iam.CreatePolicyInput{
			PolicyName:     aws.String(policyName),
			PolicyDocument: aws.String(policyDoc),
		})
		require.NoError(t, err)

		t.Cleanup(func() {
			_, _ = iamClient.DeletePolicy(&iam.DeletePolicyInput{PolicyArn: createResp.Policy.Arn})
		})

		listVersionsResp, err := iamClient.ListPolicyVersions(&iam.ListPolicyVersionsInput{
			PolicyArn: createResp.Policy.Arn,
		})
		require.NoError(t, err)
		require.NotEmpty(t, listVersionsResp.Versions)
		assert.Equal(t, "v1", aws.StringValue(listVersionsResp.Versions[0].VersionId))
		assert.Equal(t, true, aws.BoolValue(listVersionsResp.Versions[0].IsDefaultVersion))

		getVersionResp, err := iamClient.GetPolicyVersion(&iam.GetPolicyVersionInput{
			PolicyArn:  createResp.Policy.Arn,
			VersionId:  aws.String("v1"),
		})
		require.NoError(t, err)
		require.NotNil(t, getVersionResp.PolicyVersion)
		assert.Equal(t, "v1", aws.StringValue(getVersionResp.PolicyVersion.VersionId))
		assert.Contains(t, aws.StringValue(getVersionResp.PolicyVersion.Document), "s3:ListBucket")

		_, err = iamClient.GetPolicyVersion(&iam.GetPolicyVersionInput{
			PolicyArn: createResp.Policy.Arn,
			VersionId: aws.String("v2"),
		})
		require.Error(t, err)
		awsErr, ok := err.(awserr.Error)
		require.True(t, ok)
		assert.Equal(t, iam.ErrCodeNoSuchEntityException, awsErr.Code())
	})

	t.Run("user_inline_policy", func(t *testing.T) {
		userName := "test-user-policy"
		_, err := iamClient.CreateUser(&iam.CreateUserInput{
			UserName: aws.String(userName),
		})
		require.NoError(t, err)
		defer iamClient.DeleteUser(&iam.DeleteUserInput{UserName: aws.String(userName)})

		// List user policies before any inline policy is attached
		listResp, err := iamClient.ListUserPolicies(&iam.ListUserPoliciesInput{
			UserName: aws.String(userName),
		})
		require.NoError(t, err)
		assert.Empty(t, listResp.PolicyNames, "New user should have no inline policies")

		policyName := "test-inline-policy"
		policyDoc := `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:GetObject","Resource":"arn:aws:s3:::*"}]}`

		// Put user policy
		_, err = iamClient.PutUserPolicy(&iam.PutUserPolicyInput{
			UserName:       aws.String(userName),
			PolicyName:     aws.String(policyName),
			PolicyDocument: aws.String(policyDoc),
		})
		require.NoError(t, err)

		// List user policies after attaching inline policy
		listResp, err = iamClient.ListUserPolicies(&iam.ListUserPoliciesInput{
			UserName: aws.String(userName),
		})
		require.NoError(t, err)
		assert.NotEmpty(t, listResp.PolicyNames, "User should have inline policies after PutUserPolicy")
		assert.False(t, *listResp.IsTruncated)

		// Get user policy
		getResp, err := iamClient.GetUserPolicy(&iam.GetUserPolicyInput{
			UserName:   aws.String(userName),
			PolicyName: aws.String(policyName),
		})
		require.NoError(t, err)
		assert.Equal(t, userName, *getResp.UserName)
		assert.Equal(t, policyName, *getResp.PolicyName)
		assert.Contains(t, *getResp.PolicyDocument, "s3:Get")

		// Delete user policy
		_, err = iamClient.DeleteUserPolicy(&iam.DeleteUserPolicyInput{
			UserName:   aws.String(userName),
			PolicyName: aws.String(policyName),
		})
		require.NoError(t, err)

		// List user policies after deletion
		listResp, err = iamClient.ListUserPolicies(&iam.ListUserPoliciesInput{
			UserName: aws.String(userName),
		})
		require.NoError(t, err)
		assert.Empty(t, listResp.PolicyNames, "User should have no inline policies after DeleteUserPolicy")
	})

	t.Run("list_user_policies_nonexistent_user", func(t *testing.T) {
		_, err := iamClient.ListUserPolicies(&iam.ListUserPoliciesInput{
			UserName: aws.String("nonexistent-user-for-list-policies"),
		})
		require.Error(t, err)
		awsErr, ok := err.(awserr.Error)
		require.True(t, ok, "Expected AWS error type")
		assert.Equal(t, iam.ErrCodeNoSuchEntityException, awsErr.Code())
	})
}
