package network

import (
	"context"
	"fmt"

	cciamv1beta1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/iam/v1beta1"
	cck8sv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/k8s/v1alpha1"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	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/client"
	"sigs.k8s.io/controller-runtime/pkg/log"

	"github.com/tidbcloud/shared-provider/pkg/types"
	"github.com/tidbcloud/shared-provider/pkg/utils"
	"github.com/tidbcloud/shared-provider/pkg/utils/labels"
	nameutil "github.com/tidbcloud/shared-provider/pkg/utils/name"
	"github.com/tidbcloud/shared-provider/pkg/utils/namespace"
)

func (r *Reconciler) ensureGCPImportResource(
	ctx context.Context,
	network *cloudv1.Network,
) error {
	// NOTE: we use a static Google Service Account Key as dedicated tier did now.
	projectID := labels.GetProjectFromLabels(network.Labels)
	ns := namespace.GetOwnedNamespaceNameFromNetwork(network)
	name := nameutil.GetImportGCPServiceAccountName(r.Env, projectID)
	objectKey := client.ObjectKey{Namespace: ns, Name: name}
	displayName := fmt.Sprintf("shared-tier-%s", name)
	pKeyType := types.CnrmSAPrivateKeyType

	gsa := &cciamv1beta1.IAMServiceAccount{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: ns,
			Name:      name,
			Annotations: map[string]string{
				types.AnnoCnrmProjectID: r.GCPProjectID,
			},
		},
		Spec: cciamv1beta1.IAMServiceAccountSpec{
			DisplayName: &displayName,
		},
	}
	gsak := &cciamv1beta1.IAMServiceAccountKey{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: ns,
			Name:      name,
			Annotations: map[string]string{
				types.AnnoCnrmProjectID: r.GCPProjectID,
			},
		},
		Spec: cciamv1beta1.IAMServiceAccountKeySpec{
			ServiceAccountRef: cck8sv1alpha1.ResourceRef{
				Namespace: ns,
				Name:      name,
			},
			PrivateKeyType: &pKeyType,
		},
	}

	if err := r.Cache.Get(ctx, objectKey, gsa); err != nil {
		if !apierrors.IsNotFound(err) {
			return fmt.Errorf("can't get GCP service account %s: %w", objectKey, err)
		}
		if err := r.Client.Create(ctx, gsa); err != nil {
			return fmt.Errorf("can't create GCP service account %s: %w", objectKey, err)
		}
	}
	if err := r.Cache.Get(ctx, objectKey, gsak); err != nil {
		if !apierrors.IsNotFound(err) {
			return fmt.Errorf("can't get GCP service account key %s: %w", objectKey, err)
		}
		if err := r.Client.Create(ctx, gsak); err != nil {
			return fmt.Errorf("can't create GCP service account key %s: %w", objectKey, err)
		}
	}

	// a same name secret will be create by the config-connector controller.
	var secret corev1.Secret
	if err := r.Client.Get(ctx, objectKey, &secret); err != nil {
		if !apierrors.IsNotFound(err) {
			return fmt.Errorf("can't get GCP service account key secret %s: %w", objectKey, err)
		}
		logger := log.FromContext(ctx)
		logger.Info("waiting for GCP service account key secret to be created", "secret", objectKey)
		return &utils.RequeueAfterError{RequeueAfter: utils.DefaultWaitInterval}
	}

	gsaEmail := gsa.Status.Email
	if gsaEmail == "" {
		gsaEmail = getGCPServiceAccountEmail(name, r.GCPProjectID)
	}
	if network.Status.IAM.ServiceAccountEmail != gsaEmail {
		network.Status.IAM.ServiceAccountEmail = gsaEmail
		if err := r.InfraClient.Status().Update(ctx, network); err != nil {
			return err
		}
	}

	return nil
}

func (r *Reconciler) cleanUpGCPImportResource(
	ctx context.Context,
	network *cloudv1.Network,
) error {
	projectID := labels.GetProjectFromLabels(network.Labels)
	ns := namespace.GetOwnedNamespaceNameFromNetwork(network)
	name := nameutil.GetImportGCPServiceAccountName(r.Env, projectID)
	objectKey := client.ObjectKey{Namespace: ns, Name: name}
	gsa := &cciamv1beta1.IAMServiceAccount{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: ns,
			Name:      name,
		},
	}
	gsak := &cciamv1beta1.IAMServiceAccountKey{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: ns,
			Name:      name,
		},
	}

	if err := r.Client.Delete(ctx, gsak); client.IgnoreNotFound(err) != nil {
		return fmt.Errorf("can't delete GCP service account key %s: %w", objectKey, err)
	}
	if err := r.Client.Delete(ctx, gsa); client.IgnoreNotFound(err) != nil {
		return fmt.Errorf("can't delete GCP service account %s: %w", objectKey, err)
	}

	// the secret will be deleted by the config-connector controller.
	return nil
}

func getGCPServiceAccountEmail(saName, gcpProjectID string) string {
	return fmt.Sprintf("%s@%s.iam.gserviceaccount.com", saName, gcpProjectID)
}
