package eks

import (
	"context"
	"fmt"
	"sort"

	awskmsv1alpha1 "github.com/crossplane-contrib/provider-aws/apis/kms/v1alpha1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/crossplane/crossplane-runtime/pkg/meta"
	"github.com/pingcap/errors"
	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"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/namer"
)

// You can get key id by key.Status.AtProvider.KeyID
// We do not grants in kms
// So the node role need have kms permission to the kms created.
// etc:
//
//	{
//	    "Effect": "Allow",
//	    "Action": [
//	        "kms:Decrypt",
//	        "kms:Encrypt",
//	        "kms:GenerateDataKey",
//	        "kms:GenerateDataKeyWithoutPlaintext"
//	    ],
//	    "Resource": [
//	        "arn:aws:kms:<region>:<account>:key/*"
//	    ]
//	},
func ensureEncryptionKey(
	ctx context.Context,
	client client.Client,
	region string,
	cluster *cloudv1.Cluster,
	providerConfigName string,
) (keyArn string, err error) {
	name := namer.UniqueIdentifier(cluster.ObjectMeta)

	k := &awskmsv1alpha1.Key{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
	}
	tags := cloudv1.TagsFromLabels(cluster.Labels)

	_, err = controllerutil.CreateOrUpdate(ctx, client, k, func() error {
		k.Spec = awskmsv1alpha1.KeySpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: providerConfigName,
				},
			},
			ForProvider: awskmsv1alpha1.KeyParameters{
				Region: region,
				Tags:   toKMSTag(tags),
			},
		}

		if k.CreationTimestamp.IsZero() {
			if v, ok := cluster.Annotations[eksv1alpha1.ClusterKMSKeyIDAnnKey]; ok {
				if k.Annotations == nil {
					k.Annotations = make(map[string]string)
				}
				setExternalName(k, v)
			}
		}
		return nil
	})
	if err != nil {
		return "", errors.Annotate(err, "failed to create or update kms key")
	}

	if k.Status.AtProvider.ARN == nil || *k.Status.AtProvider.ARN == "" {
		return "", fmt.Errorf("wait for kms key ready")
	}

	return *k.Status.AtProvider.ARN, nil
}

func deleteEncrptionKey(
	ctx context.Context,
	cli client.Client,
	cluster *cloudv1.Cluster,
) error {
	name := namer.UniqueIdentifier(cluster.ObjectMeta)
	k := &awskmsv1alpha1.Key{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
	}

	err := cli.Delete(ctx, k)
	return client.IgnoreNotFound(err)
}

func toKMSTag(mp map[string]string) (tags []*awskmsv1alpha1.Tag) {
	for k, v := range mp {
		tags = append(tags, &awskmsv1alpha1.Tag{
			TagKey:   pointer.String(k),
			TagValue: pointer.String(v),
		})
	}

	sort.Slice(tags, func(i, j int) bool {
		return *tags[i].TagKey < *tags[j].TagKey
	})

	return
}

func setExternalName(obj metav1.Object, name string) {
	if obj.GetAnnotations() == nil {
		obj.SetAnnotations(map[string]string{})
	}
	if name == "" {
		panic("empty name")
	}
	obj.GetAnnotations()[meta.AnnotationKeyExternalName] = name
}
