package eks

import (
	"context"
	"fmt"
	"time"

	rbacv1 "k8s.io/api/rbac/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/infra-provider/pkg/awsauth"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/util"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/utils"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/appbox/task"
)

const (
	K8sGroupMaster = "system:masters"
	UserNamePrefix = "cluster-controller-admin"
)

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"
}

// we also want to change the aws-auth for migrated cluster
// because the cluster-service use {env}-{region}-cluster-controller-assumed-role to access eks
// which is not existed in migrated eks.
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)
	}
	authRW := awsauth.NewAwsAuthReaderWriter(cli)
	authSubjects := t.Config.APPBoxConfig.AuthorizedSubjects
	if t.Config.ClusterControllerAssumeRoleName != "" {
		assumeRole := utils.GenAWSRoleArn(
			t.VPC.Spec.AccountRef.AccountID, t.Config.ClusterControllerAssumeRoleName)
		authSubjects = append(authSubjects, assumeRole)
	}

	if t.Config.DPControllerAssumeRoleName != "" {
		assumeRole := utils.GenAWSRoleArn(
			t.VPC.Spec.AccountRef.AccountID, t.Config.DPControllerAssumeRoleName)
		authSubjects = append(authSubjects, assumeRole)
	}
	if t.Config.TiCDCControllerAssumeRoleName != "" {
		assumeRole := utils.GenAWSRoleArn(
			t.VPC.Spec.AccountRef.AccountID, t.Config.TiCDCControllerAssumeRoleName)
		authSubjects = append(authSubjects, assumeRole)
	}

	authData := new(awsauth.AwsAuthData)
	for i, subject := range authSubjects {
		// attach authorizedSubject to master group.
		roleMap := awsauth.RolesAuthMap{
			RoleARN:  subject,
			Username: fmt.Sprintf("%s-%d", UserNamePrefix, i),
			Groups:   []string{K8sGroupMaster},
		}
		authData.AppendMapRoles(&roleMap)
	}

	if err := t.bindReadonlyIAMRole2K8sGroup(ctx, cli, authData); err != nil {
		return deploy.Error(err)
	}
	return deploy.ErrorOrDone(authRW.Update(ctx, authData))
}

func (t *RBAC) bindReadonlyIAMRole2K8sGroup(ctx context.Context, cli client.Client, authData *awsauth.AwsAuthData) error {
	// skip if the readonly assume role name is not set
	if t.Config.ReadonlyClusterControllerAssumeRoleName == "" {
		return nil
	}

	// Bind the ClusterRole `cluster-viewer` to k8s Group `readonly-cluster-controller`
	clusterViewerRoleBinding := &rbacv1.ClusterRoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name: consts.ReadonlyClusterController,
		},
		Subjects: []rbacv1.Subject{
			{
				Kind:     rbacv1.GroupKind,
				APIGroup: rbacv1.GroupName,
				Name:     consts.ReadonlyClusterController,
			},
		},
		RoleRef: rbacv1.RoleRef{
			APIGroup: rbacv1.GroupName,
			Kind:     "ClusterRole",
			Name:     consts.ClusterRoleViewerName,
		},
	}
	if err := k8s.Apply(ctx, cli, clusterViewerRoleBinding); err != nil {
		return fmt.Errorf("create cluster-viewer clusterRoleBinding: %w", err)
	}

	// bind the readonly assume IAM role k8s Group `readonly-cluster-controller`
	readonlyClusterControllerAssumedRoleARN := util.GetRoleARN(t.VPC.Spec.AccountRef.AccountID, t.Config.ReadonlyClusterControllerAssumeRoleName)
	authData.AppendMapRoles(&awsauth.RolesAuthMap{
		RoleARN:  readonlyClusterControllerAssumedRoleARN,
		Username: consts.ReadonlyClusterController,
		Groups:   []string{consts.ReadonlyClusterController},
	})
	return nil
}

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