package k8s

import (
	"context"

	"k8s.io/cli-runtime/pkg/genericclioptions"
	restclient "k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/tools/clientcmd/api"
	"k8s.io/utils/pointer"
	"sigs.k8s.io/controller-runtime/pkg/client"

	_ "github.com/crossplane/provider-aws/apis/eks/v1beta1"
	eksv1beta1 "github.com/crossplane/provider-aws/apis/eks/v1beta1"
)

// EKSClientManager manages the EKS client.
type EKSClientManager interface {
	GetClient(ctx context.Context, opt EKSClientOpt) (client.Client, error)
	GetConfig(ctx context.Context, opt EKSClientOpt) (*genericclioptions.ConfigFlags, error)
}

// EKSTarget identifies the target EKS cluster to be accessed.
type EKSTarget struct {
	// AssumeRole allows user to retrieve token on behave of the assumed role.
	// If AssumeRole is empty, will retrieve bearer token using the current
	// AWS identity.
	AssumeRole string
	// ExternalID is the external ID of the assumed role.
	ExternalID string
	// Region specifies the AWS region of the EKS cluster.
	Region string
	// Cluster specifies the name of the EKS cluster.
	Cluster string
	// Namespace specifies the namespace for storing the cached credentials.
	Namespace string
}

// EKSClientOpt specifiest the options for retrieving the EKS k8s client.
type EKSClientOpt struct {
	EKSTarget
	// EKSObject references the crossplane EKS cluster CR.
	EKSObject client.ObjectKey
}

// eksClientMngr manages the k8s client for EKS cluster.
type eksClientMngr struct {
	client.Client
	tokMngr EKSTokenManager
	option  client.Options
}

func NewEKSClientManager(cli client.Client, tokMngr EKSTokenManager, opt client.Options) EKSClientManager {
	return &eksClientMngr{Client: cli, tokMngr: tokMngr, option: opt}
}

// GetClient returns a k8s client for the given option.
func (e *eksClientMngr) GetClient(ctx context.Context, opt EKSClientOpt) (client.Client, error) {
	tokenStr, err := e.tokMngr.RetrieveToken(ctx, EKSBearerTokenOpt{EKSTarget: opt.EKSTarget})
	if err != nil {
		return nil, err
	}
	eks := &eksv1beta1.Cluster{}
	err = e.Get(ctx, opt.EKSObject, eks)
	if err != nil {
		return nil, err
	}
	restCfg, err := constructClientFromEKSObj(eks, tokenStr)
	if err != nil {
		return nil, err
	}
	return client.New(restCfg, e.option)
}

func (e *eksClientMngr) GetConfig(ctx context.Context, opt EKSClientOpt) (*genericclioptions.ConfigFlags, error) {
	kubeconfig := genericclioptions.NewConfigFlags(false)
	eks := &eksv1beta1.Cluster{}
	err := e.Get(ctx, opt.EKSObject, eks)
	if err != nil {
		return nil, err
	}
	ep := eks.Status.AtProvider.Endpoint
	kubeconfig.APIServer = pointer.String(ep)
	token, err := e.tokMngr.RetrieveToken(ctx, EKSBearerTokenOpt{EKSTarget: opt.EKSTarget})
	if err != nil {
		return nil, err
	}
	kubeconfig.BearerToken = pointer.String(token)
	// TODO: Make it secure once we can get the CA data of the cluster.
	kubeconfig.Insecure = pointer.Bool(true)
	return kubeconfig, nil
}

func constructClientFromEKSObj(eks *eksv1beta1.Cluster, tokenStr string) (*restclient.Config, error) {
	eksARN := eks.Status.AtProvider.Arn
	cfg := api.NewConfig()
	cfg.APIVersion, cfg.Kind, cfg.CurrentContext = "v1", "Config", eksARN

	cluster, auth, cfgContext := api.NewCluster(), api.NewAuthInfo(), api.NewContext()
	cfg.Clusters[eksARN] = cluster
	cfg.AuthInfos[eksARN] = auth
	cfg.Contexts[eksARN] = cfgContext

	// config cluster
	cluster.Server = eks.Status.AtProvider.Endpoint
	// TODO(hanlins):
	// Turn off insecure connection and use CACert once
	// https://github.com/crossplane/provider-aws/issues/955 is resolved.
	cluster.InsecureSkipTLSVerify = true

	// config authinfo
	auth.Token = tokenStr

	// config context
	cfgContext.Cluster = eksARN
	cfgContext.AuthInfo = eksARN

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