package network

import (
	"context"
	"crypto/md5" // #nosec G501
	"fmt"

	"github.com/pingcap/errors"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	nameutil "github.com/tidbcloud/shared-provider/pkg/utils/name"
	"github.com/tidbcloud/shared-provider/pkg/utils/namespace"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

const (
	externalIdSalt = "infra-api"
)

func (r *Reconciler) ensureAWSImportResource(
	ctx context.Context,
	network *cloudv1.Network,
) error {
	externalId := getAWSExternalId(r.AWSAccountID, network)
	if network.Status.IAM.ExternalId != externalId || network.Status.IAM.AccountID != r.AWSAccountID ||
		network.Status.Import.RoleARN != r.ImportRoleARN {
		network.Status.IAM.ExternalId = externalId
		network.Status.IAM.AccountID = r.AWSAccountID
		network.Status.Import.RoleARN = r.ImportRoleARN
		err := r.InfraClient.Status().Update(ctx, network)
		if err != nil {
			return errors.AddStack(err)
		}
	}

	// create sa
	ns := namespace.GetOwnedNamespaceNameFromNetwork(network)
	sa := &corev1.ServiceAccount{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: ns,
			Name:      nameutil.GetImportServiceAccountName(network.Name),
		},
	}

	_, err := controllerutil.CreateOrUpdate(ctx, r.Client, sa, func() error {
		if sa.Annotations == nil {
			sa.Annotations = make(map[string]string)
		}
		sa.Annotations["eks.amazonaws.com/role-arn"] = r.ImportRoleARN
		return nil
	})
	if err != nil {
		return err
	}

	return nil
}

func (r *Reconciler) cleanUpAWSImportResource(
	ctx context.Context,
	network *cloudv1.Network,
) error {
	ns := namespace.GetOwnedNamespaceNameFromNetwork(network)
	sa := &corev1.ServiceAccount{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: ns,
			Name:      nameutil.GetImportServiceAccountName(network.Name),
		},
	}
	err := r.Client.Delete(ctx, sa)
	if err != nil && !apierrors.IsNotFound(err) {
		return errors.AddStack(err)
	}

	return nil
}

func getAWSIamIdentity(accountID string, roleName string) string {
	identity := fmt.Sprintf("arn:aws:iam::%s:role/%s", accountID, roleName)
	return identity
}

func getAWSExternalId(accountID string, network *cloudv1.Network) string {
	input := fmt.Sprintf("%s-%s-%s-import", accountID, network.GetNamespace(), network.GetName())
	// "weak cryptographic primitive" is OK for this use case
	h := md5.New()
	h.Write([]byte(input)) // #nosec G104
	bs := h.Sum([]byte(externalIdSalt))
	return fmt.Sprintf("%x", bs)
}
