package gke

import (
	"context"
	"fmt"

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

	"github.com/tidbcloud/infra-provider/pkg/applier"
	"github.com/tidbcloud/infra-provider/pkg/applier/mutatefuncs"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/namer"
	crossplaneutil "github.com/tidbcloud/infra-provider/pkg/util/crossplane"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/appbox/task"
)

// OIDC task create an oidc provider in aws to auth some gke pods.
// e.g cert-manager PCA issuer, sli-reporter, ...
type OIDC struct {
	*task.BaseTask
}

func NewOIDC(ctx *task.Context) *OIDC {
	return &OIDC{
		BaseTask: &task.BaseTask{Context: ctx},
	}
}

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

func (t *OIDC) Create(ctx context.Context) deploy.Result {
	if t.APPBox.Labels[v1alpha1.IsLegacyKey] == "true" {
		return deploy.Done()
	}
	if _, err := applier.NewApplier(t.Local, mutatefuncs.MutateIAMOpenIDConnectProvider).
		Expected(t.NewOIDCProvider(false)).
		Apply(ctx); err != nil {
		return deploy.Error(err)
	}

	return deploy.Done()
}

func (t *OIDC) PostCreate(ctx context.Context) deploy.Result {
	if t.APPBox.Labels[v1alpha1.IsLegacyKey] == "true" {
		return deploy.Done()
	}

	// Pause the OIDC provider CR after it's ready and synced to avoid
	// AWS API throttling caused by continuous reconciliation.
	oidc := t.NewOIDCProvider(true)
	if err := t.Local.Get(ctx, client.ObjectKeyFromObject(oidc), oidc); err != nil {
		return deploy.Error(err)
	}
	if k8s.IsCrossplaneCRPaused(oidc) {
		return deploy.Done()
	}
	if !crossplaneutil.IsReadyAndSynced(oidc.Status.Conditions) {
		return deploy.Error(fmt.Errorf("oidc provider %s is not ready and synced yet", oidc.Name))
	}
	if err := k8s.PauseCrossplaneCR(ctx, t.Local, oidc); err != nil {
		return deploy.Error(err)
	}

	return deploy.Done()
}

func (t *OIDC) Delete(ctx context.Context) deploy.Result {
	if err := k8s.DeleteObjectCompletedWithHooks(ctx, t.Local, t.NewOIDCProvider(true), k8s.HookAutoUnpauseCrossplaneCR); err != nil {
		return deploy.Error(err)
	}
	return deploy.Done()
}

func (t *OIDC) NewOIDCProvider(onlyMeta bool) *v1beta1.OpenIDConnectProvider {
	name := namer.UniqueIdentifier(t.APPBox.ObjectMeta)
	if t.APPBox.Annotations[v1alpha1.AnnotationKeyOwnCrossplaneNameOIDCProvider] != "" {
		name = t.APPBox.Annotations[v1alpha1.AnnotationKeyOwnCrossplaneNameOIDCProvider]
	}
	oidc := &v1beta1.OpenIDConnectProvider{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
	}
	if onlyMeta {
		return oidc
	}
	oidc.Spec = v1beta1.OpenIDConnectProviderSpec{
		ForProvider: v1beta1.OpenIDConnectProviderParameters{
			ClientIDList: []string{
				"sts.amazonaws.com",
			},
			ThumbprintList: []string{
				"0000000000000000000000000000000000000000",
			},
			URL: getURL(t.ProjectID(), t.APPBox.Spec.Region, t.Cluster.GetName()),
		},
		ResourceSpec: xpv1.ResourceSpec{
			ProviderConfigReference: &xpv1.Reference{Name: t.Config.GlobalCrossplaneProviderConfig},
		},
	}
	return oidc

}

func getURL(project, region, cluster string) string {
	return fmt.Sprintf("https://container.googleapis.com/v1/projects/%s/locations/%s/clusters/%s", project, region, cluster)
}
