package dataplane

import (
	"context"
	"fmt"
	"log"
	"time"

	"helm.sh/helm/v3/pkg/action"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"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/cache"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
	execConfigAPIVersion = "client.authentication.k8s.io/v1beta1"
	helmNamespace        = "helm"
)

func NewRESTConfigForEKS(
	clusterName,
	endpoint,
	region,
	roleARN,
	externalID string,
	ca []byte,
) (*rest.Config, error) {
	args := []string{
		"token",
		"--cluster-id", clusterName,
	}

	if region != "" {
		args = append(args, "--region", region)
	}

	if roleARN != "" {
		args = append(args, "--role", roleARN)
	}

	if externalID != "" {
		args = append(args, "--external-id", externalID)
	}

	cfg := clientcmdapi.NewConfig()

	cfgctx := clientcmdapi.NewContext()
	cfgctx.Cluster = clusterName
	cfgctx.AuthInfo = clusterName

	cluster := clientcmdapi.NewCluster()
	if len(ca) != 0 {
		cluster.CertificateAuthorityData = ca
	} else {
		cluster.InsecureSkipTLSVerify = true
	}

	cluster.Server = endpoint

	auth := clientcmdapi.NewAuthInfo()
	// TODO: use aws cli to replace aws-iam-authenticator?
	auth.Exec = &clientcmdapi.ExecConfig{
		APIVersion:      execConfigAPIVersion,
		InteractiveMode: clientcmdapi.NeverExecInteractiveMode,
		Command:         "aws-iam-authenticator",
		Args:            args,
	}

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

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

}

func NewRESTConfigForGKE(
	clusterName,
	endpoint string,
	ca []byte,
) (*rest.Config, error) {
	cfg := clientcmdapi.NewConfig()

	cfgctx := clientcmdapi.NewContext()
	cfgctx.Cluster = clusterName
	cfgctx.AuthInfo = clusterName

	cluster := clientcmdapi.NewCluster()
	if len(ca) != 0 {
		cluster.CertificateAuthorityData = ca
	} else {
		cluster.InsecureSkipTLSVerify = true
	}
	cluster.Server = endpoint

	auth := clientcmdapi.NewAuthInfo()
	auth.Exec = &clientcmdapi.ExecConfig{
		APIVersion:      execConfigAPIVersion,
		InteractiveMode: clientcmdapi.NeverExecInteractiveMode,
		Command:         "gke-gcloud-auth-plugin",
		Args:            []string{"--use_application_default_credentials"},
	}

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

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

func NewRESTConfigForKubeConfig(
	kubeconfigData []byte,
) (*rest.Config, error) {
	config, err := clientcmd.Load(kubeconfigData)
	if err != nil {
		return nil, err
	}

	return clientcmd.NewDefaultClientConfig(*config, &clientcmd.ConfigOverrides{}).ClientConfig()
}

func newClient(ctx context.Context, cfg *rest.Config, s *runtime.Scheme) (client.Client, error) {
	hc, err := rest.HTTPClientFor(cfg)
	if err != nil {
		return nil, err
	}

	reader, err := cache.New(cfg, cache.Options{
		Scheme:     s,
		HTTPClient: hc,
	})
	if err != nil {
		return nil, err
	}

	// it will not return any errors
	// nolint: errcheck
	go reader.Start(ctx)

	nctx, cancel := context.WithTimeout(ctx, time.Second*30)
	defer cancel()
	if !reader.WaitForCacheSync(nctx) {
		return nil, fmt.Errorf("cache is not synced: %w", nctx.Err())
	}

	c, err := client.New(cfg, client.Options{
		Scheme:     s,
		HTTPClient: hc,
		Cache: &client.CacheOptions{
			Reader: reader,
		},
	})
	if err != nil {
		return nil, err
	}

	return c, nil
}

func newHelmConfig(cfg *rest.Config) (*action.Configuration, error) {
	configFlags := genericclioptions.NewConfigFlags(true)

	// https://github.com/helm/helm/issues/11047
	// helm client can't use rest.Config directly
	// The Manager is constructed from rest.Config and rest.Config is created from configFlags
	// So here we just reverse the process to construct configFlags from rest.Config
	configFlags.APIServer = &cfg.Host
	configFlags.WrapConfigFn = func(_ *rest.Config) *rest.Config {
		return cfg
	}
	var c action.Configuration
	if err := c.Init(
		configFlags,
		helmNamespace,
		"configmap", // use configmap as the driver
		log.Printf,
	); err != nil {
		return nil, err
	}

	return &c, nil
}
