package k8s

import (
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd/api"
)

const (
	authV1Beta1APIVersion = "client.authentication.k8s.io/v1beta1"
)

type ConfigBuilder struct {
	cfg *api.Config
}

func NewConfigBuilder() *ConfigBuilder {
	return NewConfigBuilderFromConfig(api.NewConfig())
}

func NewConfigBuilderFromConfig(cfg *api.Config) *ConfigBuilder {
	return &ConfigBuilder{
		cfg: cfg,
	}
}

func (b *ConfigBuilder) Build() *api.Config {
	return b.cfg.DeepCopy()
}

func (b *ConfigBuilder) AddContext(contextName string, ctx api.Context) *ConfigBuilder {
	b.cfg.Contexts[contextName] = &ctx
	return b
}

func (b *ConfigBuilder) CurrentContext(contextName string) *ConfigBuilder {
	b.cfg.CurrentContext = contextName
	return b
}

func (b *ConfigBuilder) AddCluster(clusterName string, cluster *api.Cluster) *ConfigBuilder {
	b.cfg.Clusters[clusterName] = cluster
	return b
}

// AddInsecureClusterToConfig adds a cluster without checking server CA to the config set.
func (b *ConfigBuilder) AddInsecureCluster(clusterName string, endpoint string) *ConfigBuilder {
	cluster := &api.Cluster{
		Server:                   endpoint,
		InsecureSkipTLSVerify:    true,
		CertificateAuthorityData: nil,
	}
	b.cfg.Clusters[clusterName] = cluster
	return b
}

// AddClusterToConfigFromRest adds a cluster from rest config.
func (b *ConfigBuilder) AddClusterFromRest(clusterName string, restConfig *rest.Config) *ConfigBuilder {
	cluster := &api.Cluster{
		Server:                   restConfig.Host,
		TLSServerName:            restConfig.TLSClientConfig.ServerName,
		InsecureSkipTLSVerify:    restConfig.TLSClientConfig.Insecure,
		CertificateAuthority:     restConfig.TLSClientConfig.CAFile,
		CertificateAuthorityData: restConfig.TLSClientConfig.CAData,
	}
	b.cfg.Clusters[clusterName] = cluster
	return b
}

func (b *ConfigBuilder) AddTokenAuth(authName, token string) *ConfigBuilder {
	auth := &api.AuthInfo{
		Token: token,
	}
	b.cfg.AuthInfos[authName] = auth
	return b
}

type EKSAuth struct {
	Region     string
	EKSName    string
	RoleARN    string
	ExternalID string
}

// AddAuthForEKS adds a eks auth.
//
// It exec the `aws-iam-authenticator` command to auth, so you need install `aws-iam-authenticator`
// in host firstly.
//
// If need to assume role to auth, make sure that the command has the permission to assume the role.
// Maybe you need provide the permission in the host.
func (b *ConfigBuilder) AddEKSAuth(authName string, eksauth EKSAuth) *ConfigBuilder {
	auth := &api.AuthInfo{
		Exec: &api.ExecConfig{
			APIVersion:      authV1Beta1APIVersion,
			InteractiveMode: api.NeverExecInteractiveMode,
			Command:         "aws-iam-authenticator",
			Args: []string{
				"token",
				"--region", eksauth.Region,
				"--cluster-id", eksauth.EKSName,
			},
		},
	}
	if eksauth.RoleARN != "" {
		auth.Exec.Args = append(auth.Exec.Args, "--role", eksauth.RoleARN)
	}
	if eksauth.ExternalID != "" {
		auth.Exec.Args = append(auth.Exec.Args, "--external-id", eksauth.ExternalID)
	}

	b.cfg.AuthInfos[authName] = auth
	return b
}

// AddGKEAuthToConfig adds a gke auth.
//
// It exec the `gke-gcloud-auth-plugin` command to auth, so you need install `gke-gcloud-auth-plugin`
// in host firstly.
func (b *ConfigBuilder) AddGKEAuth(authName string) *ConfigBuilder {
	auth := &api.AuthInfo{
		Exec: &api.ExecConfig{
			APIVersion:      authV1Beta1APIVersion,
			InteractiveMode: api.NeverExecInteractiveMode,
			Command:         "gke-gcloud-auth-plugin",
			Args:            []string{"--use_application_default_credentials"},
		},
	}
	b.cfg.AuthInfos[authName] = auth
	return b
}

type AKSAuth struct {
	TenantID           string // tenant id
	ClientID           string // client id of managed identity
	FederatedTokenFile string // token file path to exchange managed identity token
}

func (b *ConfigBuilder) AddAKSAuth(authName string, aksauth *AKSAuth) *ConfigBuilder {
	auth := &api.AuthInfo{
		Exec: &api.ExecConfig{
			APIVersion:      authV1Beta1APIVersion,
			InteractiveMode: api.NeverExecInteractiveMode,
			Command:         "kubelogin",
			Args: []string{
				"get-token",
				"--environment", "AzurePublicCloud",
				"--login", "workloadidentity", // use managed identity with federated credential
				"--server-id", "6dae42f8-4368-4678-94ff-3960e28e3630", // static value, Ref: https://azure.github.io/kubelogin/concepts/aks.html
				"--authority-host", "https://login.microsoftonline.com/", // static value, Ref: https://azure.github.io/kubelogin/concepts/login-modes/workloadidentity.html
				"--tenant-id", aksauth.TenantID,
				"--client-id", aksauth.ClientID,
				"--federated-token-file", aksauth.FederatedTokenFile,
			},
		},
	}

	b.cfg.AuthInfos[authName] = auth
	return b
}

type CertAuth struct {
	// ClientCertificate is the path to a client cert file for TLS.
	ClientCertificate string `json:"client-certificate,omitempty"`
	// ClientCertificateData contains PEM-encoded data from a client cert file for TLS. Overrides ClientCertificate
	ClientCertificateData []byte `json:"client-certificate-data,omitempty"`
	// ClientKey is the path to a client key file for TLS.
	ClientKey string `json:"client-key,omitempty"`
	// ClientKeyData contains PEM-encoded data from a client key file for TLS. Overrides ClientKey
	ClientKeyData []byte `json:"client-key-data,omitempty" datapolicy:"security-key"`
}

func (b *ConfigBuilder) AddCertAuth(authName string, certAuth *CertAuth) *ConfigBuilder {
	auth := &api.AuthInfo{
		ClientCertificate:     certAuth.ClientCertificate,
		ClientCertificateData: certAuth.ClientCertificateData,
		ClientKey:             certAuth.ClientKey,
		ClientKeyData:         certAuth.ClientKeyData,
	}
	b.cfg.AuthInfos[authName] = auth
	return b
}

// AddClusterToConfigFromRest adds a auth from rest config.
func (b *ConfigBuilder) AddAuthFromRest(authName string, restConfig *rest.Config) *ConfigBuilder {
	auth := &api.AuthInfo{
		ClientCertificate:     restConfig.TLSClientConfig.CertFile,
		ClientCertificateData: restConfig.TLSClientConfig.CertData,
		ClientKey:             restConfig.TLSClientConfig.KeyFile,
		ClientKeyData:         restConfig.TLSClientConfig.KeyData,
		Token:                 restConfig.BearerToken,
		TokenFile:             restConfig.BearerTokenFile,
		Impersonate:           restConfig.Impersonate.UserName,
		ImpersonateGroups:     restConfig.Impersonate.Groups,
		ImpersonateUserExtra:  restConfig.Impersonate.Extra,
		Username:              restConfig.Username,
		Password:              restConfig.Password,
		AuthProvider:          restConfig.AuthProvider,
		Exec:                  restConfig.ExecProvider,
	}

	b.cfg.AuthInfos[authName] = auth
	return b
}

type ACKAuth struct {
	ClusterID   string // The ack cluster id
	ProfileName string // The profile name in alicloud config file
	ProfileFile string // The alicloud config file path, default is ~/.aliyun/config.json
}

// AddACKAuth adds a ack auth.
//
// It exec the `ack-ram-tool` command to auth, so you need install `ack-ram-tool`
// in host firstly.
//
// The ack-ram-tool use local profile to auth, you need config the alicloud profile
// in the host.
func (b *ConfigBuilder) AddACKAuth(authName string, ackAuth *ACKAuth) *ConfigBuilder {
	auth := &api.AuthInfo{
		Exec: &api.ExecConfig{
			APIVersion:      authV1Beta1APIVersion,
			InteractiveMode: api.NeverExecInteractiveMode,
			Command:         "ack-ram-tool",
			Args: []string{
				"credential-plugin",
				"get-token",
				"--cluster-id", ackAuth.ClusterID,
				"--api-version", "v1beta1",
				"--log-level", "error",
			},
		},
	}

	if ackAuth.ProfileName != "" {
		auth.Exec.Args = append(auth.Exec.Args, "--profile-name", ackAuth.ProfileName)
	}

	if ackAuth.ProfileFile != "" {
		auth.Exec.Args = append(auth.Exec.Args, "--profile-file", ackAuth.ProfileFile)
	}

	b.cfg.AuthInfos[authName] = auth
	return b
}
