package gke

import (
	"context"
	"time"

	"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/util"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/appbox/task"
	rbacv1 "k8s.io/api/rbac/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
	ClusterRoleBindingName = "regional-server:cluster-controller-admin"
	ClusterAdminRoleName   = "cluster-admin"
)

// create RBAC to grant permissions to a gcp service account.
// https://cloud.google.com/kubernetes-engine/docs/how-to/role-based-access-control
type RBAC struct {
	*task.BaseTask
}

func NewRBAC(ctx *task.Context) *RBAC {
	return &RBAC{BaseTask: &task.BaseTask{Context: ctx}}
}

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

// It's safe to run this task on migrated appbox because it only creates new binding and never changes existed bindings.
// regional-server:cluster-controller-admin is a new name for existed gke cluster.
func (t *RBAC) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())
	if len(t.Config.APPBoxConfig.AuthorizedSubjects) == 0 {
		return deploy.Done()
	}
	cli, err := t.Remote.GetClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}

	subjects := []rbacv1.Subject{}
	for _, subject := range t.Config.APPBoxConfig.AuthorizedSubjects {
		subjects = append(subjects, rbacv1.Subject{
			APIGroup: "rbac.authorization.k8s.io",
			Kind:     "User",
			Name:     subject, // supposed to be a gcp service account email.
		})
	}

	// create clusterrolebinding
	binding := &rbacv1.ClusterRoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name: ClusterRoleBindingName,
		},
		Subjects: subjects,
		RoleRef: rbacv1.RoleRef{
			APIGroup: "rbac.authorization.k8s.io",
			Kind:     "ClusterRole",
			Name:     ClusterAdminRoleName,
		},
	}

	if _, err := applier.NewApplier(cli, mutatefuncs.MutateClusterRoleBinding).
		Expected(binding).
		Apply(ctx); err != nil {
		return deploy.Error(err)
	}
	return deploy.Done()
}

func (t *RBAC) Delete(ctx context.Context) deploy.Result {
	return deploy.Done()
}
