package integration

import (
	"context"
	"crypto/tls"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net"
	"path/filepath"
	"reflect"
	"runtime"
	"testing"
	"time"

	pcaissuerv1beta1 "github.com/cert-manager/aws-privateca-issuer/pkg/api/v1beta1"
	ec2v1alpha1 "github.com/crossplane/provider-aws/apis/ec2/v1alpha1"
	awskmsv1alpha1 "github.com/crossplane/provider-aws/apis/kms/v1alpha1"
	awsdnsv1alpha1 "github.com/crossplane/provider-aws/apis/route53/v1alpha1"
	awsvpcpeeringv1alpha1 "github.com/crossplane/provider-aws/apis/vpcpeering/v1alpha1"
	gcpdnsv1alpha1 "github.com/crossplane/provider-gcp/apis/dns/v1alpha1"
	iamv1alpha1 "github.com/crossplane/provider-gcp/apis/iam/v1alpha1"
	gcpvpcpeeringv1beta1 "github.com/crossplane/provider-gcp/apis/vpcpeering/v1beta1"
	certmanagerv1alpha2 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	admissionv1beta1 "k8s.io/api/admission/v1beta1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	apiruntime "k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/yaml"
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
	"k8s.io/utils/pointer"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/envtest"
	logf "sigs.k8s.io/controller-runtime/pkg/log"
	"sigs.k8s.io/controller-runtime/pkg/log/zap"
	"sigs.k8s.io/controller-runtime/pkg/predicate"
	"sigs.k8s.io/controller-runtime/pkg/webhook"

	operatorv1alpha1 "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	"github.com/tidbcloud/config-service/sdk/api"
	"github.com/tidbcloud/config-service/sdk/mock"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-api/cmd/config-manager/webhooks"
	"github.com/tidbcloud/infra-api/controllers"
	"github.com/tidbcloud/infra-api/controllers/backup"
	"github.com/tidbcloud/infra-api/controllers/cluster"
	"github.com/tidbcloud/infra-api/controllers/clusterconfig"
	"github.com/tidbcloud/infra-api/controllers/imports"
	"github.com/tidbcloud/infra-api/controllers/networkpeer"
	"github.com/tidbcloud/infra-api/controllers/providertype"
	"github.com/tidbcloud/infra-api/controllers/ticdc"
	"github.com/tidbcloud/infra-api/pkg/consts"
	"github.com/tidbcloud/infra-api/pkg/gardener"
	gardencorev1beta1 "github.com/tidbcloud/infra-api/pkg/gardener/core/apis/v1beta1"
	"github.com/tidbcloud/infra-api/pkg/gardener/hibernation"
	"github.com/tidbcloud/infra-api/pkg/gardener/node_controller"
	"github.com/tidbcloud/infra-api/pkg/gitops"
	asts "github.com/tidbcloud/infra-api/pkg/pingcap/apis/v1"
	pingcaptest "github.com/tidbcloud/infra-api/pkg/pingcap/test"
	"github.com/tidbcloud/infra-api/pkg/provider"
	"github.com/tidbcloud/infra-api/pkg/providers/aws"
	awsfake "github.com/tidbcloud/infra-api/pkg/providers/aws/fake"
	"github.com/tidbcloud/infra-api/pkg/providers/gcp"
)

const (
	kubeApiQPS            = 65536
	CustomReconciler      = "my-reconciler"
	HibernationReconciler = "hibernation-reconciler"
)

// only on node 1 (synchronized)
var createdEntity map[string]struct{}
var deletedEntity map[string]struct{}
var testEnv *envtest.Environment

// on all nodes
var k8sClient client.Client
var k8sConfig *rest.Config
var gardenerEnv *gardener.GardEnvironment

// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
func TestAPIs(t *testing.T) {
	RegisterFailHandler(Fail)
	rand.Seed(time.Now().UnixNano())
	RunSpecs(t, "Controllers Integration Suite")
}

var awsClient *awsfake.MockClient

var _ = SynchronizedBeforeSuite(func() []byte {
	// runs in node 1 only (synchronized)
	// setup a singleton api-server and all necessary business controllers and mock controllers
	logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))

	By("bootstrapping test environment")

	testEnv = &envtest.Environment{
		CRDDirectoryPaths: []string{
			filepath.Join("..", "..", "config", "crd", "bases"),
			filepath.Join("..", "..", "config", "crossplane"),
			filepath.Join("..", "..", "pkg", "gardener", "crd"),
			filepath.Join("..", "..", "pkg", "pingcap", "crd"),
		},
		ErrorIfCRDPathMissing: true,
		WebhookInstallOptions: envtest.WebhookInstallOptions{
			Paths: []string{filepath.Join("..", "..", "config", "webhook")},
		},
		ControlPlaneStopTimeout:  time.Minute * 5,
		ControlPlaneStartTimeout: time.Minute * 5,
	}

	cfg, err := testEnv.Start()
	Expect(err).NotTo(HaveOccurred())
	Expect(cfg).NotTo(BeNil())
	cfg.QPS = kubeApiQPS
	cfg.Burst = kubeApiQPS

	data, err := GenerateKubeConfiguration("", cfg)
	Expect(err).NotTo(HaveOccurred())
	fname, err := filepath.Abs("./kubeconfig.tmp")
	Expect(err).NotTo(HaveOccurred())
	err = ioutil.WriteFile(fname, data, 0644) // #nosec G306
	Expect(err).NotTo(HaveOccurred())
	logf.Log.Info("write test kubeconfig", "filename", fname)

	k8sClient = newClient(cfg)
	Expect(k8sClient).NotTo(BeNil())
	k8sConfig = cfg

	// ref https://onsi.github.io/ginkgo/#integrating-with-gomock
	// currently it will panic if the mock call is not expected
	awsClient = awsfake.NewMockClient(GinkgoT())
	gcpAPI := gcp.NewNoOpGCPAPI()
	createdEntity, deletedEntity = setupExpect(awsClient)

	webhookInstallOptions := &testEnv.WebhookInstallOptions
	k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
		Scheme:             scheme.Scheme,
		Host:               webhookInstallOptions.LocalServingHost,
		Port:               webhookInstallOptions.LocalServingPort,
		CertDir:            webhookInstallOptions.LocalServingCertDir,
		LeaderElection:     false,
		MetricsBindAddress: "0",
	})
	Expect(err).ToNot(HaveOccurred())

	ctrlConfig := &cloudv1.InfraAPIConfig{
		Gardener: cloudv1.Gardener{
			GardenerProjectName: "test",
		},
		Server: cloudv1.Server{
			ManifestDirectoryPrefix: getManifestPrefix(),
		},
		DNS: cloudv1.DNSConfig{
			PublicHostedZoneID:     "test",
			PublicHostedZoneDomain: "tidb-cloud.com",
		},
	}
	ctrlConfig.Default()
	gardenerEnv = newGardnerEnv(cfg, &ctrlConfig.Gardener, k8sClient, awsClient, gcpAPI)

	p, err := predicate.LabelSelectorPredicate(ctrlConfig.Predicate)
	Expect(err).ToNot(HaveOccurred())

	ns := &corev1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: gardenerEnv.GetShootNamespace(),
		},
	}
	configService := &mock.MockClient{
		ClusterConfigOutput: &api.DescribeClusterConfigOutput{
			Version: "v5.0.2",
			ConfigMeta: &api.ConfigMeta{
				ID:            "base",
				GitCommitID:   "TEST",
				ConfigVersion: 1,
			},
			Components: []api.ComponentOutput{{
				Component: api.ComponentPD,
				Image:     api.Image{BaseImage: "gcr.io/pingcap-public/dbaas/pd"},
				Config:    api.NewTomlConfig(map[string]interface{}{}),
			}, {
				Component: api.ComponentTiDB,
				Image:     api.Image{BaseImage: "gcr.io/pingcap-public/dbaas/tidb"},
				Config:    api.NewTomlConfig(map[string]interface{}{}),
			}, {
				Component: api.ComponentTiKV,
				Image:     api.Image{BaseImage: "gcr.io/pingcap-public/dbaas/tikv"},
				Config:    api.NewTomlConfig(map[string]interface{}{}),
			}, {
				Component: api.ComponentTiFlash,
				Image:     api.Image{BaseImage: "gcr.io/pingcap-public/dbaas/tiflash"},
				Config:    api.NewTomlConfig(map[string]interface{}{}),
			}, {
				Component: api.ComponentTiFlashLearner,
				Image:     api.Image{BaseImage: "gcr.io/pingcap-public/dbaas/tiflash"},
				Config:    api.NewTomlConfig(map[string]interface{}{}),
			}},
		},
		BackupConfigOutput: &api.DescribeBRConfigOutput{
			Version: "v4.0.9",
			Config:  map[string]interface{}{"concurrency": float64(2)},
			Image: api.Image{
				BaseImage: "gcr.io/pingcap-public/dbaas/br",
				Tag:       pointer.StringPtr("v4.0.9-hotfix"),
			},
		},
		RestoreConfigOutput: &api.DescribeBRConfigOutput{
			Version: "v4.0.9",
			Config:  map[string]interface{}{"concurrency": float64(2)},
			Image: api.Image{
				BaseImage: "gcr.io/pingcap-public/dbaas/br",
				Tag:       pointer.StringPtr("v4.0.9-hotfix"),
			},
		},
	}
	err = k8sClient.Create(context.Background(), ns)
	Expect(err).ToNot(HaveOccurred())
	err = providertype.NewReconciler(k8sManager, consts.ProviderTypeAWS).SetupWithManager(k8sManager, p)
	Expect(err).ToNot(HaveOccurred())
	err = providertype.NewReconciler(k8sManager, consts.ProviderTypeGCP).SetupWithManager(k8sManager, p)
	Expect(err).ToNot(HaveOccurred())
	err = cluster.NewReconciler(k8sManager, gardenerEnv, consts.ProviderTypeAWS, awsClient, gcpAPI, configService, ctrlConfig, false, getManifestPrefix(), nil, nil).SetupWithManager(k8sManager, p, 1)
	Expect(err).ToNot(HaveOccurred())
	err = cluster.NewReconciler(k8sManager, gardenerEnv, consts.ProviderTypeGCP, awsClient, gcpAPI, configService, ctrlConfig, false, getManifestPrefix(), nil, nil).SetupWithManager(k8sManager, p, 1)
	Expect(err).ToNot(HaveOccurred())

	err = ApplyObjectFromDir(k8sClient, filepath.Join("..", "..", "pkg", "gitops-v2", "testdata"))
	Expect(err).ToNot(HaveOccurred())
	// Create an independent cluster controller used for testing gitops-v2
	enableGitopsV2, err := predicate.LabelSelectorPredicate(metav1.LabelSelector{MatchLabels: map[string]string{
		cloudv1.ReconcilerLabelKey: "enable-gitops",
	}})
	Expect(err).ToNot(HaveOccurred())
	infraNs := &corev1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: "infra",
		},
	}
	err = k8sClient.Create(context.TODO(), infraNs)
	Expect(err).ToNot(HaveOccurred())
	// we will crete the configmap in infra-cd project
	err = ApplyObjectFromDir(k8sClient, filepath.Join("..", "..", "manifests", "prom"))
	Expect(err).ToNot(HaveOccurred())

	err = cluster.NewReconciler(k8sManager, gardenerEnv, consts.ProviderTypeAWS, awsClient, gcpAPI, configService, ctrlConfig, false, getManifestPrefix(), nil, nil).SetUpGitopsV2(true).SetupWithManager(k8sManager, enableGitopsV2, 1)
	Expect(err).ToNot(HaveOccurred())
	err = (&controllers.NetworkReconciler{
		Client:      k8sManager.GetClient(),
		Log:         ctrl.Log.WithName("controllers").WithName("Network"),
		Scheme:      k8sManager.GetScheme(),
		GardenerEnv: gardenerEnv,
		Recorder:    k8sManager.GetEventRecorderFor("network-controller"),
		Config:      ctrlConfig,
		AWSClient:   awsClient,
		GCPAPI:      gcpAPI,
	}).SetupWithManager(k8sManager, false, getManifestPrefix(), nil, nil, 1, p)
	Expect(err).ToNot(HaveOccurred())

	// Create an independent network controller with specified predicate
	customReconciler, err := predicate.LabelSelectorPredicate(metav1.LabelSelector{MatchLabels: map[string]string{
		cloudv1.ReconcilerLabelKey: CustomReconciler,
	}})
	Expect(err).ToNot(HaveOccurred())
	err = (&controllers.NetworkReconciler{
		Client:      k8sManager.GetClient(),
		Log:         ctrl.Log.WithName("controllers").WithName("Network"),
		Scheme:      k8sManager.GetScheme(),
		GardenerEnv: gardenerEnv,
		Recorder:    k8sManager.GetEventRecorderFor("custom-network-controller"),
		Config:      ctrlConfig,
		AWSClient:   awsClient,
		GCPAPI:      gcpAPI,
	}).SetupWithManager(k8sManager, false, getManifestPrefix(), nil, nil, 1, customReconciler)
	Expect(err).ToNot(HaveOccurred())

	err = hibernation.NewHibernationController(
		gardenerEnv.Manager().GetClient(),
		k8sManager.GetClient(),
		gardenerEnv,
		k8sManager.GetEventRecorderFor("hibernation-controller"),
		// Only hibernation network object with hibernation label
		metav1.LabelSelector{
			MatchLabels: map[string]string{
				HibernationReconciler: "true",
			},
		},
		cloudv1.Hibernation{
			CheckIdleInterval: metav1.Duration{Duration: 1 * time.Second},
			IdleTimeThreshold: metav1.Duration{Duration: 10 * time.Second},
		}).SetupWithManager(gardenerEnv.Manager())

	Expect(err).ToNot(HaveOccurred())

	err = ticdc.NewTiCDCReconciler(k8sManager, ctrl.Log.WithName("controllers").WithName("TiCDC"), gardenerEnv, nil, configService, k8sManager.GetEventRecorderFor("cdc-controller"), provider.NewGardenerCDCProvider()).SetupWithManager(k8sManager, p)
	Expect(err).ToNot(HaveOccurred())
	err = backup.NewBackupReconciler(k8sManager, ctrl.Log.WithName("controllers").WithName("Backup"), gardenerEnv, nil, configService, provider.NewUnimplemented()).SetupWithManager(k8sManager, p)
	Expect(err).ToNot(HaveOccurred())
	err = backup.NewRestoreReconciler(k8sManager, ctrl.Log.WithName("controllers").WithName("Restore"), gardenerEnv, nil, configService, awsClient, gcpAPI, provider.NewUnimplemented()).SetupWithManager(k8sManager, p)
	Expect(err).ToNot(HaveOccurred())
	err = networkpeer.NewNetworkPeerReconciler(k8sManager, ctrl.Log.WithName("controllers").WithName("Networkpeer"), ctrlConfig, awsClient, gcpAPI).SetupWithManager(k8sManager, p)
	Expect(err).ToNot(HaveOccurred())

	err = (&imports.ImportReconciler{
		Client:     k8sManager.GetClient(),
		Log:        ctrl.Log.WithName("controllers").WithName("Import"),
		Scheme:     k8sManager.GetScheme(),
		EnvBuilder: imports.NewGardenerShootEnvBuilder(k8sManager.GetClient(), gardenerEnv),
	}).SetUpGitopsV2().SetupWithManager(k8sManager, p)
	Expect(err).ToNot(HaveOccurred())

	err = clusterconfig.NewClusterConfigReconciler(k8sManager, ctrl.Log.WithName("controllers").WithName("ClusterConfig")).SetupWithManager(k8sManager)
	Expect(err).ToNot(HaveOccurred())
	err = clusterconfig.NewGarbageCollector(k8sManager, ctrl.Log.WithName("controllers").WithName("ConfigRevisionGCer"), 30*time.Second, time.Minute).SetupWithManager(k8sManager)
	Expect(err).ToNot(HaveOccurred())

	// setup mock controllers
	// MockTCReconcile mock all tidbcluster objects to Ready
	err = pingcaptest.NewMockTCReconciler(k8sManager).SetupWithManager(k8sManager)
	Expect(err).ToNot(HaveOccurred())

	err = pingcaptest.NewMockTIReconciler(k8sManager).SetupWithManager(k8sManager)
	Expect(err).ToNot(HaveOccurred())

	// MockKMSKeyReconciler set key-id to the status of all key objects
	err = pingcaptest.NewMockKMSKeyReconciler(k8sManager).SetupWithManager(k8sManager)
	Expect(err).ToNot(HaveOccurred())

	err = pingcaptest.NewMockAwsVpcPeerReconciler(k8sManager).SetupWithManager(k8sManager)
	Expect(err).ToNot(HaveOccurred())
	err = pingcaptest.NewServiceLBReconciler(k8sManager).SetupWithManager(k8sManager)
	Expect(err).ToNot(HaveOccurred())
	err = pingcaptest.NewMockDeployReconciler(k8sManager).SetupWithManager(k8sManager)
	Expect(err).ToNot(HaveOccurred())

	// register webhooks
	k8sManager.GetWebhookServer().Register("/mutate-cloud-pingcap-com-v1-cluster-revision", &webhook.Admission{Handler: webhooks.NewClusterRevisionMutator(k8sManager)})
	err = cloudv1.RegisterWebhooks(k8sManager, &noopPeerCreator{})
	Expect(err).ToNot(HaveOccurred())

	ctx := ctrl.SetupSignalHandler()
	go func() {
		defer GinkgoRecover()
		err = k8sManager.Start(ctx)
		Expect(err).ToNot(HaveOccurred())
	}()
	go func() {
		defer GinkgoRecover()
		<-k8sManager.Elected()
		err = gardenerEnv.Start(ctx)
		Expect(err).ToNot(HaveOccurred())
	}()

	// wait for the webhook server to get ready
	dialer := &net.Dialer{Timeout: time.Second}
	addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort)
	Eventually(func() error {
		conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) // #nosec G402
		if err != nil {
			return err
		}
		conn.Close()
		return nil
	}).Should(Succeed())

	err = initProvider(k8sClient)
	Expect(err).NotTo(HaveOccurred())

	return data
}, func(kubeconfigData []byte) {
	// runs on all node
	// setup the k8s and gardener clients for each node
	clientCfg, err := clientcmd.NewClientConfigFromBytes(kubeconfigData)
	Expect(err).To(Succeed())
	cfg, err := clientCfg.ClientConfig()
	Expect(err).To(Succeed())
	cfg.QPS = kubeApiQPS
	cfg.Burst = kubeApiQPS
	if k8sConfig == nil {
		k8sConfig = cfg
	}
	if k8sClient == nil {
		k8sClient = newClient(k8sConfig)
	}
	if gardenerEnv == nil {
		gardenerEnv = newGardnerEnv(k8sConfig, &cloudv1.Gardener{
			GardenerProjectName: "test",
		}, k8sClient, awsClient, gcp.NewNoOpGCPAPI())
		go func() {
			defer GinkgoRecover()
			ctx := ctrl.SetupSignalHandler()
			err = gardenerEnv.Start(ctx)
			Expect(err).ToNot(HaveOccurred())
		}()
	}
})

func newClient(cfg *rest.Config) client.Client {
	err := cloudv1.AddToScheme(scheme.Scheme)
	Expect(err).NotTo(HaveOccurred())
	err = admissionv1beta1.AddToScheme(scheme.Scheme)
	Expect(err).NotTo(HaveOccurred())
	err = gardencorev1beta1.AddToScheme(scheme.Scheme)
	Expect(err).NotTo(HaveOccurred())
	err = awskmsv1alpha1.AddToScheme(scheme.Scheme)
	Expect(err).NotTo(HaveOccurred())
	err = certmanagerv1alpha2.AddToScheme(scheme.Scheme)
	Expect(err).NotTo(HaveOccurred())
	err = pcaissuerv1beta1.AddToScheme(scheme.Scheme)
	Expect(err).NotTo(HaveOccurred())
	err = awsvpcpeeringv1alpha1.SchemeBuilder.AddToScheme(scheme.Scheme)
	Expect(err).NotTo(HaveOccurred())
	err = gcpvpcpeeringv1beta1.SchemeBuilder.AddToScheme(scheme.Scheme)
	Expect(err).NotTo(HaveOccurred())
	err = awsdnsv1alpha1.SchemeBuilder.AddToScheme(scheme.Scheme)
	Expect(err).NotTo(HaveOccurred())
	err = gcpdnsv1alpha1.SchemeBuilder.AddToScheme(scheme.Scheme)
	Expect(err).NotTo(HaveOccurred())
	err = operatorv1alpha1.AddToScheme(scheme.Scheme)
	Expect(err).NotTo(HaveOccurred())
	err = iamv1alpha1.SchemeBuilder.AddToScheme(scheme.Scheme)
	Expect(err).NotTo(HaveOccurred())
	err = asts.SchemeBuilder.AddToScheme(scheme.Scheme)
	Expect(err).NotTo(HaveOccurred())
	err = ec2v1alpha1.SchemeBuilder.AddToScheme(scheme.Scheme)
	Expect(err).NotTo(HaveOccurred())

	k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
	Expect(err).NotTo(HaveOccurred())
	return k8sClient
}

func newGardnerEnv(cfg *rest.Config, gardenerCfg *cloudv1.Gardener, infraClient client.Client, awsClient aws.Client, gcpAPI gcp.GCPAPI) *gardener.GardEnvironment {

	nodeControllerRegister := &node_controller.ClusterNodeControllersRegister{
		ConfigProvider: gitops.NewHardcode(),
		InfraClient:    infraClient,
		CRDPaths:       []string{filepath.Join(getManifestPrefix(), "/manifests/tidb-operator-crd.yaml")},
		AWSClient:      awsClient,
		GCPClient:      gcpAPI,
	}
	var err error
	infraapiConfig := &cloudv1.InfraAPIConfig{
		MixedDeployPDWithTiKV: cloudv1.MixedDeployPDWithTiKV{},
	}
	infraapiConfig.MixedDeployPDWithTiKV.SetAlwaysEnabled()
	gardenerEnv, err = gardener.NewGardEnvironment(gardenerCfg, infraapiConfig, []gardener.ShootReconcilerRegister{nodeControllerRegister}, cfg)
	Expect(err).To(Succeed())
	return gardenerEnv
}

var _ = SynchronizedAfterSuite(func() {
	// runs on all nodes
	fmt.Printf("ginkgo node %d exit", GinkgoParallelProcess())
}, func() {
	// runs in node 1 only (synchronized)
	_ = cleanProvider(k8sClient)

	By("tearing down the test environment")
	err := testEnv.Stop()
	Expect(err).NotTo(HaveOccurred())
	if awsClient != nil {
		awsClient.Ctrl.Finish()
	}
})

func getManifestPrefix() string {
	_, b, _, _ := runtime.Caller(0)
	dir := filepath.Join(filepath.Dir(b), "../../testbin")
	return dir
}

var providerTypeTPL = &cloudv1.ProviderType{
	ObjectMeta: metav1.ObjectMeta{
		Name: "name",
	},
	Spec: cloudv1.ProviderTypeSpec{
		Capabilities: nil,
	},
}

var storage = resource.MustParse("1000Gi")
var providerTPL = &cloudv1.Provider{
	ObjectMeta: metav1.ObjectMeta{
		Name: "name",
	},
	Spec: cloudv1.ProviderSpec{
		Type: "name",
		Regions: []cloudv1.Region{
			{
				Name:  "test-region",
				Zones: []string{"z-a", "z-b", "z-c"},
			},
		},
		InstanceTypes: []cloudv1.InstanceType{
			{
				Name:   "compute",
				Arch:   consts.AMD64ArchName,
				CPU:    resource.MustParse("8"),
				Memory: resource.MustParse("16Gi"),
			},
			{
				Name:       "storage",
				Arch:       consts.AMD64ArchName,
				CPU:        resource.MustParse("8"),
				Memory:     resource.MustParse("32Gi"),
				MinStorage: &storage,
				MaxStorage: &storage,
			},
			{
				Name:   "test",
				Arch:   consts.AMD64ArchName,
				CPU:    resource.MustParse("1"),
				Memory: resource.MustParse("2Gi"),
			},
			{
				Name:   "i3.2xlarge",
				Arch:   consts.AMD64ArchName,
				CPU:    resource.MustParse("1"),
				Memory: resource.MustParse("2Gi"),
			},
			{
				Name:   "r5.2xlarge",
				Arch:   consts.AMD64ArchName,
				CPU:    resource.MustParse("8"),
				Memory: resource.MustParse("16Gi"),
			},
			{
				Name:   "c5.9xlarge",
				Arch:   consts.AMD64ArchName,
				CPU:    resource.MustParse("36"),
				Memory: resource.MustParse("72Gi"),
			},
		},
	},
}

func initProvider(client client.Client) error {
	ctx := context.Background()

	regions := []cloudv1.Region{
		{
			Name:  "us-east-2",
			Zones: []string{"z1", "z2", "z3"},
		},
	}

	tp := providerTypeTPL.DeepCopy()
	tp.Name = consts.ProviderTypeAWS
	err := client.Create(ctx, tp)
	if err != nil {
		return err
	}

	tp = providerTypeTPL.DeepCopy()
	tp.Name = consts.ProviderTypeGCP
	err = client.Create(ctx, tp)
	if err != nil {
		return err
	}

	p := providerTPL.DeepCopy()
	p.Name = consts.ProviderTypeAWS
	p.Spec.Type = consts.ProviderTypeAWS
	p.Spec.Regions = append(p.Spec.Regions, regions...)
	spec := &cloudv1.GardenerProviderSpec{
		TypeMeta: metav1.TypeMeta{
			Kind:       reflect.TypeOf(cloudv1.GardenerProviderSpec{}).Name(),
			APIVersion: cloudv1.GroupVersion.String(),
		},
		EnableGracefulShutdownWebhook: pointer.Bool(false),
		AWS: cloudv1.AwsConfig{
			AccountID:              "9527",
			PublicHostedZoneDomain: "aws.pingcap.com",
			CrossplaneProvider:     "provider-aws",
		},
		DataFlowAgent: cloudv1.DataFlowAgentConfig{Image: "test image"},
	}
	p.Spec.ForProvider = &apiruntime.RawExtension{Object: spec}

	err = client.Create(ctx, p)
	if err != nil {
		return err
	}

	p = providerTPL.DeepCopy()
	p.Name = consts.ProviderTypeGCP
	p.Spec.Type = consts.ProviderTypeGCP
	p.Spec.Regions = regions
	spec = &cloudv1.GardenerProviderSpec{
		TypeMeta: metav1.TypeMeta{
			Kind:       reflect.TypeOf(cloudv1.GardenerProviderSpec{}).Name(),
			APIVersion: cloudv1.GroupVersion.String(),
		},
		EnableGracefulShutdownWebhook: pointer.Bool(true),
		AWS: cloudv1.AwsConfig{
			AccountID:          "9527",
			CrossplaneProvider: "provider-aws",
		},
		GCP: cloudv1.GcpConfig{
			ProjectID:              "test-project",
			CertManagerRole:        "aws:dfdfdf/certrole",
			PublicHostedZoneDomain: "gcp.pingcap.com",
			CrossplaneProvider:     "provider-gcp",
		},
		DataFlowAgent: cloudv1.DataFlowAgentConfig{Image: "test image"},
	}
	p.Spec.ForProvider = &apiruntime.RawExtension{Object: spec}
	err = client.Create(ctx, p)
	if err != nil {
		return err
	}

	return nil
}

func cleanProvider(client client.Client) error {
	var err error
	ctx := context.Background()

	// Delete ProviderType
	tp := new(cloudv1.ProviderType)
	err = client.Get(ctx, types.NamespacedName{Name: "aws"}, tp)
	if err != nil {
		return err
	}
	err = client.Delete(ctx, tp)
	if err != nil {
		return err
	}

	err = client.Get(ctx, types.NamespacedName{Name: "gcp"}, tp)
	if err != nil {
		return err
	}
	err = client.Delete(ctx, tp)
	if err != nil {
		return err
	}

	// Delete Provider
	p := new(cloudv1.Provider)
	err = client.Get(ctx, types.NamespacedName{Name: "aws"}, p)
	if err != nil {
		return err
	}
	err = client.Delete(ctx, p)
	if err != nil {
		return err
	}

	err = client.Get(ctx, types.NamespacedName{Name: "gcp"}, p)
	if err != nil {
		return err
	}
	err = client.Delete(ctx, p)
	if err != nil {
		return err
	}

	return nil
}

// CreateShootKubeConfig create the shoot kubeconfig secret to point to the test api server(the pass in rest.Config).
func CreateShootKubeConfig(client client.Client, namespace string, shootName string, cfg *rest.Config) error {
	data, err := GenerateKubeConfiguration("kube-system", cfg)
	if err != nil {
		return err
	}

	secretName := fmt.Sprintf("%s.kubeconfig", shootName)
	secret := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: namespace,
			Name:      secretName,
		},
		Data: map[string][]byte{
			"kubeconfig": data,
		},
	}

	err = client.Create(context.TODO(), secret)
	if err != nil {
		return err
	}

	return err
}

// change from https://github.com/kubernetes/client-go/issues/711#issuecomment-730112049
func GenerateKubeConfiguration(namespace string, cfg *rest.Config) ([]byte, error) {
	if namespace == "" {
		namespace = "kube-system"
	}

	clusters := make(map[string]*clientcmdapi.Cluster)
	clusters["default-cluster"] = &clientcmdapi.Cluster{
		Server:                   cfg.Host,
		CertificateAuthorityData: cfg.CAData,
	}

	contexts := make(map[string]*clientcmdapi.Context)
	contexts["default-context"] = &clientcmdapi.Context{
		Cluster:   "default-cluster",
		Namespace: namespace,
		AuthInfo:  "default-context",
	}

	authinfos := make(map[string]*clientcmdapi.AuthInfo)
	authinfos["default-context"] = &clientcmdapi.AuthInfo{
		// try to cover all auth strategies that aren't plugins
		ClientCertificateData: cfg.CertData,
		ClientKeyData:         cfg.KeyData,
		Token:                 cfg.BearerToken,
		Username:              cfg.Username,
		Password:              cfg.Password,
	}

	clientConfig := clientcmdapi.Config{
		Kind:           "Config",
		APIVersion:     "v1",
		Clusters:       clusters,
		Contexts:       contexts,
		CurrentContext: "default-context",
		AuthInfos:      authinfos,
	}

	data, err := clientcmd.Write(clientConfig)
	return data, err
}

type noopPeerCreator struct{}

func (_ *noopPeerCreator) Create(_ *cloudv1.NetworkPeer, _ *cloudv1.Network) error {
	return nil
}

func ApplyObjectFromDir(client client.Client, dir string) error {
	infos, err := ioutil.ReadDir(dir)
	if err != nil {
		return err
	}
	for _, f := range infos {
		data, err := ioutil.ReadFile(filepath.Join(dir, f.Name()))
		if err != nil {
			return err
		}
		obj := &unstructured.Unstructured{}
		err = yaml.Unmarshal(data, obj)
		if err != nil {
			return err
		}
		err = client.Create(context.TODO(), obj)
		if err != nil {
			return err
		}
	}
	return nil
}
