package eks

import (
	"context"
	"crypto/md5"
	"fmt"
	"sort"

	"github.com/crossplane-contrib/provider-aws/apis/iam/v1alpha1"
	"github.com/crossplane-contrib/provider-aws/apis/iam/v1beta1"
	apiv1beta1 "github.com/crossplane-contrib/provider-aws/apis/v1beta1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/pointer"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	eksv1alpha1 "github.com/tidbcloud/eks-provider/api/v1alpha1"
	"github.com/tidbcloud/eks-provider/controllers/network/task"
	"github.com/tidbcloud/eks-provider/controllers/network/task/utils"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/namer"
	"github.com/tidbcloud/infra-provider/pkg/util"
)

const (
	nodeRolePolicyName             = "full-manager-node-role-permission"
	awsLBControllerPolicyName      = "aws-load-balancer-controller-permission"
	RoleRefLabelKey                = "role-reference"
	AmazonEKSVPCResourceController = "arn:aws:iam::aws:policy/AmazonEKSVPCResourceController"
)

var (
	assumeRolePolicyDocument = `{
    "Version":"2012-10-17",
    "Statement":[
        {
            "Effect":"Allow",
            "Principal":{
                "Service":"ec2.amazonaws.com"
            },
            "Action":"sts:AssumeRole"
        }
    ]
}`

	// put all statements in one policy document to reduce the number of policies/attachments (and IAM calls).
	// policy statements for CSI, clusterAutoscaler, import Role, KMS key, PCA and LB controller.
	// See https://github.com/kubernetes-sigs/aws-load-balancer-controller/blob/v2.4.2/docs/install/iam_policy.json

	// According to https://docs.aws.amazon.com/eks/latest/userguide/kubernetes-versions.html#kubernetes-1.24,
	// when we need to scale up from zero node for nodegroup, cluster-autoscaler needs eks:DescribeNodegroup
	// permission to get the label from eks nodegroup. When there's node already in the nodegroup, cluster-autoscaler
	// can refer to the label and taints info in the kubernetes.
	//
	// TODO: refactor the node role policy document, it's hard to add comment
	// NOTE: ec2:DescribePrefixLists is added for network policy controller
	// TODO(liubo02): ec2:DescribePrefixLists will be removed after IAM role is ready in shared vpc
	selfManagedNodeRolePolicyDocument = `{
    "Version":"2012-10-17",
    "Statement":[
        {
            "Effect":"Allow",
            "Action":[
                "ec2:AttachVolume",
                "ec2:DetachVolume",
                "ec2:ModifyVolume",
                "ec2:CreateVolume",
                "ec2:DeleteVolume"
            ],
            "Resource":"*"
        },
        {
            "Effect":"Allow",
            "Action":[
                "ec2:DescribeAvailabilityZones",
                "ec2:DescribeInstances",
                "ec2:DescribeTags",
                "ec2:DescribeVolumes",
                "ec2:DescribeVolumesModifications",
                "ec2:DescribeSnapshots",
                "ec2:RebootInstances"
            ],
            "Resource":"*"
        },
        {
            "Effect":"Allow",
            "Action":[
                "ec2:CreateTags"
            ],
            "Resource":[
                "arn:aws:ec2:*:*:volume/*"
            ],
            "Condition":{
                "StringEquals":{
                    "ec2:CreateAction":[
                        "CreateVolume"
                    ]
                }
            }
        },
        {
            "Effect":"Allow",
            "Action":[
                "ec2:DeleteTags",
                "ec2:CreateSnapshot"
            ],
            "Resource":[
                "arn:aws:ec2:*:*:volume/*"
            ]
        },
        {
            "Action": [
                "autoscaling:DescribeAutoScalingGroups",
                "autoscaling:DescribeAutoScalingInstances",
                "autoscaling:DescribeLaunchConfigurations",
                "autoscaling:DescribeTags",
                "autoscaling:SetDesiredCapacity",
                "autoscaling:TerminateInstanceInAutoScalingGroup",
                "ec2:DescribeLaunchTemplateVersions",
                "eks:DescribeNodegroup"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Effect":"Allow",
            "Action":[
                "sts:AssumeRole"
            ],
            "Resource":[
                "*"
            ]
        },
        {
            "Effect":"Allow",
            "Action":[
                "kms:Decrypt",
                "kms:Encrypt",
                "kms:GenerateDataKey",
                "kms:GenerateDataKeyWithoutPlaintext",
                "kms:CreateGrant",
                "kms:DescribeKey"
            ],
            "Resource":[
                "*"
            ]
        },
        {
            "Sid":"awspcaissuer",
            "Action":[
                "acm-pca:DescribeCertificateAuthority",
                "acm-pca:GetCertificate",
                "acm-pca:IssueCertificate",
                "acm-pca:GetCertificateAuthorityCertificate"
            ],
            "Effect":"Allow",
            "Resource":[
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "sqs:SendMessage"
            ],
            "Resource": [
                "arn:aws:sqs:*:*:ingress-mim.fifo",
                "arn:aws:sqs:*:*:ingress-mim-tmp.fifo"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateTags",
                "ec2:DeleteTags",
                "ec2:CreateSnapshot",
                "ec2:DeleteSnapshot",
                "ebs:ListSnapshotBlocks",
                "ebs:ListChangedBlocks"
            ],
            "Resource": "arn:aws:ec2:*:*:snapshot/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribePrefixLists"
            ],
            "Resource": "*"
        }
    ]
}`

	// See https://github.com/kubernetes-sigs/aws-load-balancer-controller/blob/v2.4.7/docs/install/iam_policy.json
	awsLBControllerPolicyDocument = `{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "iam:CreateServiceLinkedRole"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "iam:AWSServiceName": "elasticloadbalancing.amazonaws.com"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeAccountAttributes",
                "ec2:DescribeAddresses",
                "ec2:DescribeAvailabilityZones",
                "ec2:DescribeInternetGateways",
                "ec2:DescribeVpcs",
                "ec2:DescribeVpcPeeringConnections",
                "ec2:DescribeSubnets",
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeInstances",
                "ec2:DescribeNetworkInterfaces",
                "ec2:DescribeTags",
                "ec2:GetCoipPoolUsage",
                "ec2:DescribeCoipPools",
                "elasticloadbalancing:DescribeLoadBalancers",
                "elasticloadbalancing:DescribeLoadBalancerAttributes",
                "elasticloadbalancing:DescribeListeners",
                "elasticloadbalancing:DescribeListenerCertificates",
                "elasticloadbalancing:DescribeSSLPolicies",
                "elasticloadbalancing:DescribeRules",
                "elasticloadbalancing:DescribeTargetGroups",
                "elasticloadbalancing:DescribeTargetGroupAttributes",
                "elasticloadbalancing:DescribeTargetHealth",
                "elasticloadbalancing:DescribeTags"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "cognito-idp:DescribeUserPoolClient",
                "acm:ListCertificates",
                "acm:DescribeCertificate",
                "iam:ListServerCertificates",
                "iam:GetServerCertificate",
                "waf-regional:GetWebACL",
                "waf-regional:GetWebACLForResource",
                "waf-regional:AssociateWebACL",
                "waf-regional:DisassociateWebACL",
                "wafv2:GetWebACL",
                "wafv2:GetWebACLForResource",
                "wafv2:AssociateWebACL",
                "wafv2:DisassociateWebACL",
                "shield:GetSubscriptionState",
                "shield:DescribeProtection",
                "shield:CreateProtection",
                "shield:DeleteProtection"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:AuthorizeSecurityGroupIngress",
                "ec2:RevokeSecurityGroupIngress"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateSecurityGroup"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateTags"
            ],
            "Resource": "arn:aws:ec2:*:*:security-group/*",
            "Condition": {
                "StringEquals": {
                    "ec2:CreateAction": "CreateSecurityGroup"
                },
                "Null": {
                    "aws:RequestTag/elbv2.k8s.aws/cluster": "false"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateTags",
                "ec2:DeleteTags"
            ],
            "Resource": "arn:aws:ec2:*:*:security-group/*",
            "Condition": {
                "Null": {
                    "aws:RequestTag/elbv2.k8s.aws/cluster": "true",
                    "aws:ResourceTag/elbv2.k8s.aws/cluster": "false"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:AuthorizeSecurityGroupIngress",
                "ec2:RevokeSecurityGroupIngress",
                "ec2:DeleteSecurityGroup"
            ],
            "Resource": "*",
            "Condition": {
                "Null": {
                    "aws:ResourceTag/elbv2.k8s.aws/cluster": "false"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:CreateLoadBalancer",
                "elasticloadbalancing:CreateTargetGroup"
            ],
            "Resource": "*",
            "Condition": {
                "Null": {
                    "aws:RequestTag/elbv2.k8s.aws/cluster": "false"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:CreateListener",
                "elasticloadbalancing:DeleteListener",
                "elasticloadbalancing:CreateRule",
                "elasticloadbalancing:DeleteRule"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:AddTags",
                "elasticloadbalancing:RemoveTags"
            ],
            "Resource": [
                "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*",
                "arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*",
                "arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*"
            ],
            "Condition": {
                "Null": {
                    "aws:RequestTag/elbv2.k8s.aws/cluster": "true",
                    "aws:ResourceTag/elbv2.k8s.aws/cluster": "false"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:AddTags",
                "elasticloadbalancing:RemoveTags"
            ],
            "Resource": [
                "arn:aws:elasticloadbalancing:*:*:listener/net/*/*/*",
                "arn:aws:elasticloadbalancing:*:*:listener/app/*/*/*",
                "arn:aws:elasticloadbalancing:*:*:listener-rule/net/*/*/*",
                "arn:aws:elasticloadbalancing:*:*:listener-rule/app/*/*/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:ModifyLoadBalancerAttributes",
                "elasticloadbalancing:SetIpAddressType",
                "elasticloadbalancing:SetSecurityGroups",
                "elasticloadbalancing:SetSubnets",
                "elasticloadbalancing:DeleteLoadBalancer",
                "elasticloadbalancing:ModifyTargetGroup",
                "elasticloadbalancing:ModifyTargetGroupAttributes",
                "elasticloadbalancing:DeleteTargetGroup"
            ],
            "Resource": "*",
            "Condition": {
                "Null": {
                    "aws:ResourceTag/elbv2.k8s.aws/cluster": "false"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:AddTags"
            ],
            "Resource": [
                "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*",
                "arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*",
                "arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*"
            ],
            "Condition": {
                "StringEquals": {
                    "elasticloadbalancing:CreateAction": [
                        "CreateTargetGroup",
                        "CreateLoadBalancer"
                    ]
                },
                "Null": {
                    "aws:RequestTag/elbv2.k8s.aws/cluster": "false"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:RegisterTargets",
                "elasticloadbalancing:DeregisterTargets"
            ],
            "Resource": "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:SetWebAcl",
                "elasticloadbalancing:ModifyListener",
                "elasticloadbalancing:AddListenerCertificates",
                "elasticloadbalancing:RemoveListenerCertificates",
                "elasticloadbalancing:ModifyRule"
            ],
            "Resource": "*"
        }
    ]
}`
)

var (
	AWSManagedPolicy = []string{
		"arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy",
		"arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly",
		"arn:aws:iam::aws:policy/AWSDirectConnectFullAccess",
		"arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy",
		"arn:aws:iam::aws:policy/AmazonRoute53ResolverFullAccess",
		// See https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-getting-started.html for more detail.
		"arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
	}
)

// NodeRole task will only be executed on the full manager network controller.
// It will create a role for the node and attach the policy to the role.
type NodeRole struct {
	*task.BaseTask
}

func NewNodeRole(ctx *task.Context) deploy.Task {
	return &NodeRole{BaseTask: &task.BaseTask{Context: ctx}}
}

func (t *NodeRole) Name() string {
	return "NodeRole"
}

func (t *NodeRole) Create(ctx context.Context) deploy.Result {
	network := t.Network

	spec, err := eksv1alpha1.EKSNetworkSpecForNetwork(network)
	if err != nil {
		return deploy.Panic(err)
	}

	nodeRoleARN, err := prepareNodeRole(ctx, t.Local, network)
	if err != nil {
		return deploy.Panic(err)
	}

	needUpdate, err := utils.MutateEKSNetworkSpec(network, func(spec *eksv1alpha1.EKSNetworkSpec) error {
		spec.NodeRoleArn = nodeRoleARN
		return nil
	})
	if err != nil {
		return deploy.Panic(err)
	}
	if needUpdate {
		return deploy.Panic(nil).WithRequeuAfter(0)
	}

	if t.Network.Status.IAM.AccountID == "" {
		if err := utils.SetIAMAccountID(spec.NodeRoleArn, network); err != nil {
			return deploy.Panic(err)
		}
		return deploy.Panic(nil).WithRequeuAfter(0)
	}

	return deploy.Done()
}

func (t *NodeRole) PostCreate(ctx context.Context) deploy.Result {
	return deploy.Done()
}

// Delete will delete all role policy attachments and the role itself.
// Must delete the role attachments before deleting the role.
func (t *NodeRole) Delete(ctx context.Context) deploy.Result {
	name := namer.UniqueIdentifier(t.Network.ObjectMeta)
	if err := client.IgnoreNotFound(t.Local.DeleteAllOf(context.Background(), &v1beta1.RolePolicyAttachment{}, client.MatchingLabels{RoleRefLabelKey: name})); err != nil {
		return deploy.Panic(err)
	}

	if err := client.IgnoreNotFound(t.Local.DeleteAllOf(context.Background(), &v1beta1.Policy{}, client.MatchingLabels{RoleRefLabelKey: name})); err != nil {
		return deploy.Panic(err)
	}

	role := &v1beta1.Role{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
	}
	if err := client.IgnoreNotFound(t.Local.Delete(context.Background(), role)); err != nil {
		return deploy.Panic(err)
	}

	instanceProfile := &v1alpha1.InstanceProfile{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
	}
	if err := client.IgnoreNotFound(t.Local.Delete(context.Background(), instanceProfile)); err != nil {
		return deploy.Panic(err)
	}

	return deploy.Done()
}

func (t *NodeRole) PostDelete(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func prepareNodeRole(ctx context.Context, cli client.Client, network *cloudv1.Network) (string, error) {
	config := &apiv1beta1.ProviderConfig{
		ObjectMeta: metav1.ObjectMeta{
			Name: namer.UniqueIdentifier(network.ObjectMeta),
		},
	}
	err := cli.Get(ctx, client.ObjectKey{Name: config.Name}, config)
	if err != nil {
		return "", err
	}

	if config.Spec.AssumeRoleARN == nil {
		return "", fmt.Errorf("assume role arn is not set")
	}

	// create node role per network
	role, roleReady, err := createNodeRole(ctx, cli, network)
	if err != nil {
		return "", err
	}

	// create node role policy per network
	policyARN, policyReady, err := createNodeRolePolicy(ctx, cli, network)
	if err != nil {
		return "", err
	}

	awsLBCPolicyARN, awsLBCPolicyReady, err := createPolicyForALBController(ctx, cli, network)
	if err != nil {
		return "", fmt.Errorf("failed to create policy for AWS LB Controller: %v", err)
	}

	var nodeRoleARN string
	if roleReady && policyReady && awsLBCPolicyReady {
		err := createRoleAttachment(ctx, cli, network, []string{policyARN, awsLBCPolicyARN}, role)
		if err != nil {
			return "", err
		}

		// Create an instance profile per network for AutoScalingGroups.
		// Must wait for the role to be ready before creating the instance profile,
		// since https://github.com/crossplane-contrib/provider-aws/issues/1493
		if err = createInstanceProfile(ctx, cli, network); err != nil {
			return "", err
		}

		nodeRoleARN = role.Status.AtProvider.ARN
	} else {
		return "", fmt.Errorf("node role or policy is not ready")
	}

	return nodeRoleARN, nil
}

func createNodeRole(ctx context.Context, cli client.Client, network *cloudv1.Network) (role *v1beta1.Role, ready bool, err error) {
	tags := cloudv1.TagsFromLabels(network.Labels)
	name := namer.UniqueIdentifier(network.ObjectMeta)
	tags = util.AddResourceName(tags, name)

	role = &v1beta1.Role{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "iam.aws.crossplane.io/v1beta1",
			Kind:       "Role",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		Spec: v1beta1.RoleSpec{
			ForProvider: v1beta1.RoleParameters{
				Description:              pointer.String(fmt.Sprintf("Node role for %s", network.Name)),
				AssumeRolePolicyDocument: assumeRolePolicyDocument,
				Tags:                     iamTagFromLabels(tags),
			},
			ResourceSpec: xpv1.ResourceSpec{
				DeletionPolicy:          xpv1.DeletionDelete,
				ProviderConfigReference: &xpv1.Reference{Name: namer.UniqueIdentifier(network.ObjectMeta)},
			},
		},
	}

	err = k8s.Apply(ctx, cli, role)
	if err != nil {
		return role, false, err
	}
	if role.Status.AtProvider.ARN != "" {
		ready = true
	}

	return role, ready, nil
}

func createInstanceProfile(ctx context.Context, cli client.Client, network *cloudv1.Network) error {
	name := namer.UniqueIdentifier(network.ObjectMeta)

	instanceProfile := &v1alpha1.InstanceProfile{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		Spec: v1alpha1.InstanceProfileSpec{
			ForProvider: v1alpha1.InstanceProfileParameters{
				CustomInstanceProfileParameters: v1alpha1.CustomInstanceProfileParameters{
					RoleRef: &xpv1.Reference{
						Name: name,
					},
				},
			},
			ResourceSpec: xpv1.ResourceSpec{
				DeletionPolicy:          xpv1.DeletionDelete,
				ProviderConfigReference: &xpv1.Reference{Name: namer.UniqueIdentifier(network.ObjectMeta)},
			},
		},
	}

	return k8s.Apply(ctx, cli, instanceProfile)
}

func policyName(name, policyName string) string {
	return fmt.Sprintf("%s-%s", name, policyName)
}

func policyAttachmentName(name, policyARN string) string {
	hash := fmt.Sprintf("%x", md5.Sum([]byte(policyARN)))[:8]
	return fmt.Sprintf("%s-%s", name, hash)
}

func createRoleAttachment(ctx context.Context, cli client.Client, network *cloudv1.Network, selfManagedPolicyARN []string, role *v1beta1.Role) (err error) {
	name := namer.UniqueIdentifier(network.ObjectMeta)
	policyARNs := append(AWSManagedPolicy, selfManagedPolicyARN...)

	for _, policyARN := range policyARNs {
		attachment := &v1beta1.RolePolicyAttachment{
			ObjectMeta: metav1.ObjectMeta{
				Name:   policyAttachmentName(name, policyARN),
				Labels: map[string]string{RoleRefLabelKey: name},
			},
			Spec: v1beta1.RolePolicyAttachmentSpec{
				ForProvider: v1beta1.RolePolicyAttachmentParameters{
					RoleNameRef: &xpv1.Reference{
						Name: namer.UniqueIdentifier(network.ObjectMeta),
					},
					PolicyARN: policyARN,
				},
				ResourceSpec: xpv1.ResourceSpec{
					DeletionPolicy:          xpv1.DeletionDelete,
					ProviderConfigReference: &xpv1.Reference{Name: namer.UniqueIdentifier(network.ObjectMeta)},
				},
			},
		}

		err = controllerutil.SetOwnerReference(role, attachment, cli.Scheme())
		if err != nil {
			return err
		}

		_, err = controllerutil.CreateOrUpdate(ctx, cli, attachment, func() error {
			return nil
		})
		if err != nil {
			return err
		}
	}

	return nil
}

func createNodeRolePolicy(ctx context.Context, cli client.Client, network *cloudv1.Network) (policyARN string, ready bool, err error) {
	name := namer.UniqueIdentifier(network.ObjectMeta)
	policy := v1beta1.Policy{
		ObjectMeta: metav1.ObjectMeta{
			Name:   policyName(name, nodeRolePolicyName),
			Labels: map[string]string{RoleRefLabelKey: name},
		},
		Spec: v1beta1.PolicySpec{
			ForProvider: v1beta1.PolicyParameters{
				Description: pointer.String("Node role permissions"),
				Name:        policyName(name, nodeRolePolicyName),
			},
			ResourceSpec: xpv1.ResourceSpec{
				DeletionPolicy:          xpv1.DeletionDelete,
				ProviderConfigReference: &xpv1.Reference{Name: name},
			},
		},
	}

	_, err = controllerutil.CreateOrUpdate(ctx, cli, &policy, func() error {
		policy.Spec.ForProvider.Document = *pointer.String(selfManagedNodeRolePolicyDocument)
		return nil
	})
	if err != nil {
		return
	}
	if policy.Status.AtProvider.ARN == "" {
		ready = false
		return
	}

	ready = true
	return policy.Status.AtProvider.ARN, ready, nil
}

func createPolicyForALBController(ctx context.Context, cli client.Client, network *cloudv1.Network) (policyARN string, ready bool, err error) {
	name := namer.UniqueIdentifier(network.ObjectMeta)
	policy := v1beta1.Policy{
		ObjectMeta: metav1.ObjectMeta{
			Name:   policyName(name, awsLBControllerPolicyName),
			Labels: map[string]string{RoleRefLabelKey: name},
		},
		Spec: v1beta1.PolicySpec{
			ForProvider: v1beta1.PolicyParameters{
				Description: pointer.String("AWS LB Controller permissions"),
				Name:        policyName(name, awsLBControllerPolicyName),
			},
			ResourceSpec: xpv1.ResourceSpec{
				DeletionPolicy:          xpv1.DeletionDelete,
				ProviderConfigReference: &xpv1.Reference{Name: name},
			},
		},
	}

	_, err = controllerutil.CreateOrUpdate(ctx, cli, &policy, func() error {
		policy.Spec.ForProvider.Document = *pointer.String(awsLBControllerPolicyDocument)
		return nil
	})
	if err != nil {
		return
	}
	if policy.Status.AtProvider.ARN == "" {
		ready = false
		return
	}

	ready = true
	return policy.Status.AtProvider.ARN, ready, nil
}

func iamTagFromLabels(kv map[string]string) []v1beta1.Tag {
	var tags []v1beta1.Tag
	for k, v := range kv {
		tags = append(tags, v1beta1.Tag{Key: k, Value: v})
	}
	sort.Slice(tags, func(i, j int) bool {
		return tags[i].Key < tags[j].Key
	})
	return tags
}
