package tidbcloud_cluster

import (
	"context"
	"fmt"
	"os"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	apiresource "github.com/pingcap/test-infra/common/model/resource"
	"github.com/pingcap/test-infra/sdk/e2etest/framework"
	"github.com/pingcap/test-infra/sdk/pkg/tidbcloud"
	"github.com/pingcap/test-infra/sdk/resource"
	"github.com/pingcap/test-infra/sdk/resource/impl/k8s"
)

var tidbCloudConfiguration tidbcloud.TiDBCloudConfiguration

var _ = PDescribe("TiDBCloudCluster Test", func() {
	const WaitReadyTimeOut = 30 * time.Minute
	const WaitRestoreTimeOut = 60 * time.Minute
	Context("TiDBCloudClusterClient", func() {
		f := framework.NewDefaultFramework("testbed-tidbcloudcluster-client")

		It("apis should works well", func() {
			c := f.CreateTiDBCloudCluster(f.CreateTestbed(), tidbCloudConfiguration)

			Expect(c.GetSpec()).Should(Equal(apiresource.TiDBCloudClusterSpec{
				AccountEmail: "manjunpeng@pingcap.com",
				OrgID:        "1369847559691367419",
				ProjectID:    "1369847559691427556",
				Provider:     "aws",
				Password:     "5631qmpWX#t6uDV6",
				Connect:      "standard",
				TiDB: apiresource.TiDBCloudClusterTiDBSpec{
					Replicas:        1,
					NodeProfileName: "4 vCPU, 16 GiB (Beta)",
				},
				TiKV: apiresource.TiDBCloudClusterTiKVSpec{
					Replicas:        3,
					NodeProfileName: "4 vCPU, 16 GiB (Beta)",
					StorageSizeGi:   200,
				},
				ClusterConfig: apiresource.TiDBCloudClusterConfigSpec{
					TiKV: &apiresource.TiDBCloudConfiguration{
						Config: &apiresource.GenericConfig{MP: map[string]interface{}{
							"log-level": "debug",
						}},
					},
				},
			}))
			f.WaitForTiDBCloudClusterReadyWithoutPing(c, WaitReadyTimeOut)

			status, err := c.GetStatus()
			framework.ExpectNoError(err)
			Expect(status.State).Should(Equal(apiresource.TiDBCloudClusterStateNormal))
			f.DeleteTiDBCloudCluster(tidbCloudConfiguration, c.ResourceHandle)
		})
		It("tidbCloudClusterController works well", func() {
			controller := f.CreateTiDBCloudClusterController(tidbCloudConfiguration)
			ctx, cancel := context.WithCancel(context.TODO())
			defer cancel()
			go func() {
				controller.StartUntil(ctx, 1)
			}()

			c := f.CreateTiDBCloudCluster(f.CreateTestbed(), tidbCloudConfiguration)
			f.WaitForTiDBCloudClusterReadyWithoutPing(c, WaitReadyTimeOut)
			status, err := c.GetStatus()
			framework.ExpectNoError(err)

			time.Sleep(time.Minute)
			bookSecret := f.DeleteTiDBCloudClusterObj(c.ClientSet, c.Namespace, c.Name)
			time.Sleep(time.Minute)
			f.WaitTiDBCloudClusterDeleted(tidbCloudConfiguration, c.Namespace, c.Name, status.ClusterID, string(bookSecret.Data["auth_token"]), string(bookSecret.Data["org_id"]), string(bookSecret.Data["project_id"]))
		})
	})

	Context("TiDBCloudCluster.ResetCluster", func() {
		f := framework.NewDefaultFramework("testbed-tidbcloudcluster-resetcluster")

		It("TiDBCloudCluster.ResetCluster should clean all data", func() {
			c := f.CreateTiDBCloudCluster(f.CreateTestbed(), tidbCloudConfiguration)
			f.WaitForTiDBCloudClusterReadyWithoutPing(c, WaitReadyTimeOut)
			defer f.DeleteTiDBCloudCluster(tidbCloudConfiguration, c.ResourceHandle)
			framework.ExpectNoError(c.Sync())

			databaseName := "yelo"
			db, err := c.GetDB("")
			framework.ExpectNoError(err)
			_, err = db.Exec(fmt.Sprintf("create database %s", databaseName))
			framework.ExpectNoError(err)

			framework.ExpectNoError(c.ResetCluster())
			_, err = c.GetDB("")
			framework.ExpectNoError(err, "should connect successfully when dbName not specified")
			_, err = c.GetDB(databaseName)
			framework.ExpectError(err, "should fail because db was deleted")
		})
	})

	Context("TiDBCloudCluster.Import", func() {
		f := framework.NewDefaultFramework("testbed-tidbcloudcluster-import")

		It("should work well", func() {
			c := f.CreateTiDBCloudCluster(f.CreateTestbed(), tidbCloudConfiguration)
			f.WaitForTiDBCloudClusterReadyWithoutPing(c, WaitReadyTimeOut)
			err := c.Import(context.TODO(), resource.TiDBCloudImportOptions{
				DataFormat: resource.TiDBCloudImportDataFormatSQL,
				AwsRoleArn: "arn:aws:iam::946646677266:role/test-infra-s3-workload-datasets",
				SourceURL:  "s3://qa-workload-datasets/tpch1sf/",
			})
			framework.ExpectNoError(err)
		})
	})

	Context("TiDBCloudCluster.Backup/Restore", func() {
		f := framework.NewDefaultFramework("testbed-tidbcloudcluster-backuprestore")

		It("should work well", func() {
			controller := f.CreateTiDBCloudClusterController(tidbCloudConfiguration)
			ctx, cancel := context.WithCancel(context.TODO())
			defer cancel()
			go func() {
				controller.StartUntil(ctx, 1)
			}()

			c := f.CreateTiDBCloudCluster(f.CreateTestbed(), tidbCloudConfiguration)
			f.WaitForTiDBCloudClusterReadyWithoutPing(c, WaitReadyTimeOut)

			backupID, err := c.Backup(ctx, resource.TiDBCloudBackupOptions{
				BackupName: "backup-1",
			})
			framework.ExpectNoError(err)
			c2, err := c.Restore(ctx, resource.TiDBCloudRestoreOptions{
				ClusterName: "restore-1",
				BackupID:    backupID,
				Password:    c.Spec.Password,
			})
			framework.ExpectNoError(err)
			f.WaitForTiDBCloudClusterReady(c2.(*k8s.TiDBCloudCluster), WaitRestoreTimeOut)
		})
	})

	Context("TiDBCloudCluster.InplaceRestore", func() {
		f := framework.NewDefaultFramework("testbed-tidbcloudcluster-inplacerestore")

		It("should work well", func() {
			c := f.CreateTiDBCloudCluster(f.CreateTestbed(), tidbCloudConfiguration)
			f.WaitForTiDBCloudClusterReadyWithoutPing(c, WaitReadyTimeOut)
			ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
			defer cancel()

			dbName := "test"
			bucketName := "qa-workload-datasets"
			objectPrefix := "tpcc1"
			err := c.InplaceRestore(ctx, resource.TiDBCloudInplaceRestoreOptions{
				BackupType:   resource.TiDBCloudInplaceRestoreBackupTypeDB,
				DBName:       &dbName,
				BucketName:   bucketName,
				ObjectPrefix: objectPrefix,
			})
			framework.ExpectNoError(err)
		})
	})

	Context("TiDBCloudCluster.Chaos", func() {
		f := framework.NewDefaultFramework("testbed-tidbcloudcluster-chaos")
		It("chaos apis should works well", func() {
			c := f.CreateTiDBCloudCluster(f.CreateTestbed(), tidbCloudConfiguration)
			f.WaitForTiDBCloudClusterReadyWithoutPing(c, WaitReadyTimeOut)

			selector, err := c.ChaosSelect(resource.TiDBClusterSelectorOpts{
				TiDBClusterTiDBSelectorSpec: &resource.TiDBClusterTiDBSelectorSpec{
					Peer: &[]int{0}[0],
				},
			})
			framework.ExpectNoError(err)
			tikv2aTarget, err := c.ChaosSelect(resource.TiDBClusterSelectorOpts{
				TiDBClusterTiKVSelectorSpec: &resource.TiDBClusterTiKVSelectorSpec{
					StoreLabelSelector: &metav1.LabelSelector{
						MatchLabels: map[string]string{
							"failure-domain.beta.kubernetes.io/zone": "us-west-2a",
						},
					},
				},
			})
			framework.ExpectNoError(err)
			Expect(len(tikv2aTarget)).Should(Equal(1))
			allSelector, err := c.ChaosSelect(resource.TiDBClusterSelectorOpts{
				TiDBClusterPDSelectorSpec: &resource.TiDBClusterPDSelectorSpec{
					All: true,
				},
				TiDBClusterTiDBSelectorSpec: &resource.TiDBClusterTiDBSelectorSpec{
					All: true,
				},
			})
			framework.ExpectNoError(err)

			{
				chaosId, err := c.ChaosClient().Create(resource.InjectChaosOptions{
					Selector:       selector,
					ExperimentSpec: &resource.FailureExperimentSpec{},
				})

				framework.ExpectNoError(err)
				Expect(chaosId).ShouldNot(BeNil())

				err = c.ChaosClient().Delete(chaosId)
				framework.ExpectNoError(err)
			}
			{
				chaosId, err := c.ChaosClient().Create(resource.InjectChaosOptions{
					Selector: allSelector,
					Target:   &tikv2aTarget,
					ExperimentSpec: &resource.NetworkPartitionSpec{
						Scheduler: &resource.ExperimentScheduler{Cron: "@every 30s"},
					},
				})

				framework.ExpectNoError(err)
				Expect(chaosId).ShouldNot(BeNil())
				err = c.ChaosClient().Delete(chaosId)
				framework.ExpectNoError(err)
			}

			{
				chaosId, err := c.ChaosClient().Create(resource.InjectChaosOptions{
					Selector: selector,
					ExperimentSpec: &resource.NetworkLatencySpec{
						Latency:     "1000ms",
						Jitter:      "5ms",
						Correlation: "25",
					},
				})

				framework.ExpectNoError(err)
				Expect(chaosId).ShouldNot(BeNil())

				err = c.ChaosClient().Delete(chaosId)
				framework.ExpectNoError(err)
			}
			{
				chaosId, err := c.ChaosClient().Create(resource.InjectChaosOptions{
					Selector: selector,
					Target:   &tikv2aTarget,
					ExperimentSpec: &resource.NetworkLossSpec{
						Loss:        "100",
						Correlation: "25",
					},
				})

				framework.ExpectNoError(err)
				Expect(chaosId).ShouldNot(BeNil())
				err = c.ChaosClient().Delete(chaosId)
				framework.ExpectNoError(err)
			}
		})
	})
})

func init() {
	tidbCloudConfiguration = tidbcloud.TiDBCloudConfiguration{
		TiDBCloudURL:             os.Getenv("TIDBCLOUD_URL"),
		ThanosURL:                os.Getenv("TIDBCLOUD_THANOS_URL"),
		Auth0ClientID:            os.Getenv("TIDBCLOUD_AUTH0_CLIENT_ID"),
		Auth0ClientSecret:        os.Getenv("TIDBCLOUD_AUTH0_CLIENT_SECRET"),
		Auth0Domain:              os.Getenv("TIDBCLOUD_AUTH0_DOMAIN"),
		CloudProvider:            os.Getenv("TIDBCLOUD_CLOUD_PROVIDER"),
		Region:                   os.Getenv("TIDBCLOUD_REGION"),
		BaseEKSAPIServerEndpoint: os.Getenv("TIDBCLOUD_BASE_EKS_APISERVER_ENDPOINT"),
		BaseEKSAssumeRole:        os.Getenv("TIDBCLOUD_BASE_EKS_ASSUME_ROLE"),
		BaseEKSClusterName:       os.Getenv("TIDBCLOUD_BASE_EKS_CLUSTER_NAME"),
		ShootKubeConfigSecretNS:  os.Getenv("TIDBCLOUD_SHOOT_KUBE_CONFIG_SECRET_NS"),
	}
}
