package k8scliv2

import (
	"context"

	eksv1beta1 "github.com/upbound/provider-aws/apis/eks/v1beta1"
	cpv1beta1 "github.com/upbound/provider-aws/apis/v1beta1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
	resourceclient "k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1beta1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/observability/agent-control-server/pkg/consts"
	"github.com/tidbcloud/observability/agent-control-server/pkg/util"
)

const (
	v1execConfigAPIVersionV1alpha1 = "client.authentication.k8s.io/v1alpha1" //[v1alpha1 removed]https://github.com/kubernetes/kubernetes/pull/108616
	v1execConfigAPIVersionV1beta1  = "client.authentication.k8s.io/v1beta1"
)

type eksTenantClientManager struct {
	cli    client.Client
	option client.Options
}

func NewEKSTenantClientManager(cli client.Client, opt client.Options) ClientManager {
	return &eksTenantClientManager{
		cli:    cli,
		option: opt,
	}
}

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

func (mgr *eksTenantClientManager) GetMetricsClient(ctx context.Context, cluster client.ObjectKey) (resourceclient.MetricsV1beta1Interface, error) {
	restCfg, err := mgr.GetConfig(ctx, cluster)
	if err != nil {
		return nil, err
	}
	return resourceclient.NewForConfig(restCfg)
}

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

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

	if eks.Spec.ProviderConfigReference != nil && eks.Spec.ProviderConfigReference.Name != "" {
		providerCfg := &cpv1beta1.ProviderConfig{}
		err := mgr.cli.Get(ctx, client.ObjectKey{Name: eks.Spec.ProviderConfigReference.Name}, providerCfg)
		if err != nil {
			return nil, err
		}
		if len(providerCfg.Spec.AssumeRoleChain) > 0 {
			var roleARN string
			for _, c := range providerCfg.Spec.AssumeRoleChain {
				if c.RoleARN != nil && *c.RoleARN != "" {
					roleARN = *c.RoleARN
					break
				}
			}
			if roleARN != "" {
				args = append(args, "--role", roleARN)
			}
			var externalID string
			for _, c := range providerCfg.Spec.AssumeRoleChain {
				if c.ExternalID != nil && *c.ExternalID != "" {
					externalID = *c.ExternalID
					break
				}
			}
			if externalID != "" {
				args = append(args, "--external-id", externalID)
			}
		}
	}

	cfg := clientcmdapi.NewConfig()

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

	cluster := clientcmdapi.NewCluster()

	// Temporarily skip verification for all.
	// CA: eks.Status.AtProvider.CertificateAuthorityData
	cluster.InsecureSkipTLSVerify = true

	var endpointPublicAccess *bool
	for _, c := range eks.Spec.ForProvider.VPCConfig {
		if c.EndpointPublicAccess != nil {
			endpointPublicAccess = c.EndpointPublicAccess
			break
		}
	}

	// 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 {
		if eks.Status.AtProvider.Endpoint != nil {
			cluster.Server = *eks.Status.AtProvider.Endpoint
		}
	}

	apiVersion := v1execConfigAPIVersionV1alpha1
	// 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 := util.CompareVersions(*eks.Spec.ForProvider.Version, ">=", "1.24")
		if err != nil {
			return nil, err
		}
		if k8sVersionGreaterThanEqual124 {
			apiVersion = v1execConfigAPIVersionV1beta1
		}
	}

	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()
}

type eksLocalClientManager struct {
	restCfg *rest.Config
	option  client.Options
}

func NewEKSLocalClientManager(restCfg *rest.Config, opt client.Options) ClientManager {
	return &eksLocalClientManager{
		restCfg: restCfg,
		option:  opt,
	}
}

func (mgr *eksLocalClientManager) GetConfig(ctx context.Context, _ client.ObjectKey) (*rest.Config, error) {
	return mgr.restCfg, nil
}

func (mgr *eksLocalClientManager) GetClient(ctx context.Context, _ client.ObjectKey) (client.Client, error) {
	return client.New(mgr.restCfg, mgr.option)
}

func (mgr *eksLocalClientManager) GetMetricsClient(ctx context.Context, _ client.ObjectKey) (resourceclient.MetricsV1beta1Interface, error) {
	return resourceclient.NewForConfig(mgr.restCfg)
}
