//go:generate ${GOBIN}/dummygen -d=infra_api_cli_dummy.go -package=dedicatedcli ${GO_MODULE}/pkg/infra/clients/dedicatedcli InfraAPIClient
package dedicatedcli

import (
	"context"
	"fmt"

	cpautoscalingv1bet1 "github.com/crossplane-contrib/provider-aws/apis/autoscaling/v1beta1"
	cpmanualv1alpha1 "github.com/crossplane-contrib/provider-aws/apis/eks/manualv1alpha1"
	cpeksv1beta1 "github.com/crossplane-contrib/provider-aws/apis/eks/v1beta1"
	cpawsv1beta1 "github.com/crossplane-contrib/provider-aws/apis/v1beta1"
	cpgkev1beta2 "github.com/crossplane-contrib/provider-gcp/apis/container/v1beta2"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"k8s.io/apimachinery/pkg/runtime"
	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/devops/pkg/infra/config"
	"github.com/tidbcloud/devops/pkg/infra/secretsource"
)

var (
	infraSchemes = []func(s *runtime.Scheme) error{
		clientgoscheme.AddToScheme,
		cloudv1.SchemeBuilder.AddToScheme,
		// crossplane-aws
		cpautoscalingv1bet1.SchemeBuilder.AddToScheme,
		cpmanualv1alpha1.SchemeBuilder.AddToScheme,
		cpeksv1beta1.SchemeBuilder.AddToScheme,
		cpawsv1beta1.SchemeBuilder.AddToScheme,
		// crossplane-gcp
		cpgkev1beta2.SchemeBuilder.AddToScheme,
	}
)

// InfraAPIClient is a wrapper of k8s client for infra apiserver and add some helper methods
type InfraAPIClient interface {
	GetClient() client.Client
	GetRestConfig() *rest.Config
}

type infraClient struct {
	restConfig *rest.Config
	ctrl       client.Client
	scheme     *runtime.Scheme
}

func NewInfraAPIClientFromSecret(rootConf *config.RootConfig, secretsource secretsource.SecretSource) (InfraAPIClient, error) {
	conf := rootConf.Clients.InfraAPIServer

	if conf.Dummy {
		return &DummyInfraAPIClient{}, nil
	}

	kubeconfigData, err := secretsource.GetSecretValue(context.Background(), conf.KubeConfigSecretName)
	if err != nil {
		return nil, fmt.Errorf("failed to get kubeconfig data from secret source: %w", err)
	}

	return NewInfraAPIClient([]byte(kubeconfigData))
}

func NewInfraAPIClient(kubeConfigData []byte) (InfraAPIClient, error) {
	scheme := runtime.NewScheme()
	for _, fn := range infraSchemes {
		if err := fn(scheme); err != nil {
			return nil, fmt.Errorf("add scheme failed: %w", err)
		}
	}

	restConfig, err := clientcmd.RESTConfigFromKubeConfig(kubeConfigData)
	if err != nil {
		return nil, fmt.Errorf("build rest config: %w", err)
	}
	// TODO: add client option
	cli, err := client.New(restConfig, client.Options{
		Scheme: scheme,
	})
	if err != nil {
		return nil, fmt.Errorf("build client: %w", err)
	}

	return &infraClient{
		restConfig: restConfig,
		ctrl:       cli,
		scheme:     scheme,
	}, nil
}

func (c *infraClient) GetClient() client.Client {
	return c.ctrl
}

func (c *infraClient) GetRestConfig() *rest.Config {
	return c.restConfig
}
