package k8s

import (
	"context"
	"encoding/base64"

	eksv1beta1 "github.com/crossplane-contrib/provider-aws/apis/eks/v1beta1"
	cpv1beta1 "github.com/crossplane-contrib/provider-aws/apis/v1beta1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/util"
	"github.com/tidbcloud/infra-provider/pkg/version"
)

const (
	execConfigAPIVersionV1alpha1 = "client.authentication.k8s.io/v1alpha1"
	execConfigAPIVersionV1beta1  = "client.authentication.k8s.io/v1beta1"
)

type eksTenantClientMngr struct {
	cli                   client.Client
	option                client.Options
	awsAssumeRoleOptional *AssumeRole
}

type EksTenantClientMngrOption func(*eksTenantClientMngr)

func withAWSAssumeRole(role *AssumeRole) EksTenantClientMngrOption {
	return func(mngr *eksTenantClientMngr) {
		mngr.awsAssumeRoleOptional = role
	}
}

func NewEKSClientManager(cli client.Client, k8sCliOpts client.Options, opts ...EksTenantClientMngrOption) ClientManager {
	mngr := &eksTenantClientMngr{
		cli:    cli,
		option: k8sCliOpts,
	}
	for _, opt := range opts {
		opt(mngr)
	}
	return mngr
}

func (t *eksTenantClientMngr) GetClient(ctx context.Context, cluster client.ObjectKey) (client.Client, error) {
	restCfg, err := t.GetConfig(ctx, cluster)
	if err != nil {
		return nil, err
	}
	cli, err := client.New(restCfg, t.option)
	if err != nil {
		return nil, err
	}
	return cli, nil
}

func (t *eksTenantClientMngr) GetConfig(ctx context.Context, eksKey client.ObjectKey) (*rest.Config, error) {
	eks := &eksv1beta1.Cluster{}
	err := t.cli.Get(ctx, eksKey, eks)
	if err != nil {
		return nil, err
	}

	args := []string{
		"token",
		"--region", *eks.Spec.ForProvider.Region,
		"--cluster-id", eks.Name,
	}

	if t.awsAssumeRoleOptional != nil {
		args = append(args, "--role", t.awsAssumeRoleOptional.RoleARN)
		if t.awsAssumeRoleOptional.ExternalID != "" {
			args = append(args, "--external-id", t.awsAssumeRoleOptional.ExternalID)
		}
	} else if eks.Spec.ProviderConfigReference != nil && eks.Spec.ProviderConfigReference.Name != "" {
		providerCfg := &cpv1beta1.ProviderConfig{}
		err := t.cli.Get(ctx, client.ObjectKey{Name: eks.Spec.ProviderConfigReference.Name}, providerCfg)
		if err != nil {
			return nil, err
		}
		if providerCfg.Spec.AssumeRoleARN != nil {
			args = append(args, "--role", *providerCfg.Spec.AssumeRoleARN)
			if providerCfg.Spec.ExternalID != nil {
				args = append(args, "--external-id", *providerCfg.Spec.ExternalID)
			}
		}
	}

	cfg := clientcmdapi.NewConfig()

	cfgctx := clientcmdapi.NewContext()
	cfgctx.Cluster = eks.Name
	cfgctx.AuthInfo = eks.Name

	cluster := clientcmdapi.NewCluster()
	if eks.Status.AtProvider.CertificateAuthorityData != "" {
		ca, err := base64.StdEncoding.DecodeString(eks.Status.AtProvider.CertificateAuthorityData)
		if err != nil {
			return nil, err
		}
		cluster.CertificateAuthorityData = ca
	} else {
		cluster.InsecureSkipTLSVerify = true
	}

	endpointPublicAccess := eks.Spec.ForProvider.ResourcesVpcConfig.EndpointPublicAccess
	// If EKS enable public access, use the public kubeconfig.
	if util.GetAnnotation(eks, consts.AnnotationKeyEKSEndpoint) != "" && (endpointPublicAccess == nil || !*endpointPublicAccess) {
		cluster.Server = eks.Annotations[consts.AnnotationKeyEKSEndpoint]
	} else {
		cluster.Server = eks.Status.AtProvider.Endpoint
	}

	apiVersion := execConfigAPIVersionV1alpha1
	// With the release of Kubernetes v1.24.0
	// client.authentication.k8s.io/v1alpha1 has been [removed](https://github.com/kubernetes/kubernetes/pull/108616).
	if eks.Spec.ForProvider.Version != nil {
		k8sVersionGreaterThanEqual124, err := version.CompareVersions(*eks.Spec.ForProvider.Version, ">=", "1.24")
		if err != nil {
			return nil, err
		}
		if k8sVersionGreaterThanEqual124 {
			apiVersion = execConfigAPIVersionV1beta1
		}
	}

	auth := clientcmdapi.NewAuthInfo()
	auth.Exec = &clientcmdapi.ExecConfig{
		APIVersion:      apiVersion,
		InteractiveMode: clientcmdapi.NeverExecInteractiveMode,
		Command:         "aws-iam-authenticator",
		Args:            args,
	}

	cfg.Contexts[eks.Name] = cfgctx
	cfg.Clusters[eks.Name] = cluster
	cfg.AuthInfos[eks.Name] = auth
	cfg.CurrentContext = eks.Name

	return clientcmd.NewDefaultClientConfig(*cfg, nil).ClientConfig()
}

var _ ClientManager = &eksTenantClientMngr{}
