package e2e

import (
	"context"
	"database/sql"
	"fmt"
	"time"

	_ "github.com/go-sql-driver/mysql"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"

	"github.com/tidbcloud/shared-provider/pkg/utils/labels"
)

var errWait = fmt.Errorf("wait for condition met")

// 1. create old cluster & new cluster
// 2. import data to old cluster
// 3. backup old cluster
// 4. restore the backup to the new cluster.
var _ = Describe("full link test", func() {
	interval := 10 * time.Second
	ctx := context.Background()

	It("Should take import backup and restore for aws shared provider successfully", func() {
		By("create old cluster")
		oldCluster := newCommonCluster(awsNetwork)
		Expect(infraCli.Create(ctx, oldCluster)).Should(Succeed())

		By("create new cluster")
		newCluster := newCommonCluster(awsNetwork)
		Expect(infraCli.Create(ctx, newCluster)).Should(Succeed())

		By("wait old cluster ready")
		waitClusterReady(ctx, oldCluster, 15*time.Minute, interval)
		Expect(oldCluster.Status.SqlEndpoints.PublicEndpoint).NotTo(BeNil())

		By("wait new cluster ready")
		waitClusterReady(ctx, newCluster, 15*time.Minute, interval)
		Expect(newCluster.Status.SqlEndpoints.PublicEndpoint).NotTo(BeNil())

		By("wait old TiDB ready") // should wait TiDB to be ready in order to execute SQL statements
		waitTiDBReady(ctx, *oldCluster.Status.SqlEndpoints.PublicEndpoint, 30*time.Minute, interval)

		By("wait new TiDB ready")
		waitTiDBReady(ctx, *newCluster.Status.SqlEndpoints.PublicEndpoint, 30*time.Minute, interval)

		By("create import")
		cloudImport := &cloudv1.Import{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: namespace,
				Name:      cloudv1.RandomString(6),
				Labels:    e2eResourceLabels(labels.GetProjectFromLabels(oldCluster.Labels)),
			},
			Spec: cloudv1.ImportSpec{
				ClusterName:    oldCluster.Name,
				StorageRequest: "200Mi",
				AccessConfig: cloudv1.AccessConfig{
					RoleArn: "arn:aws:iam::385595570414:role/dbaas-import-e2e-test",
				},
				Source: cloudv1.Source{
					Format:   cloudv1.DataFormatSQL,
					UserName: "cloud_admin",
					Storage: cloudv1.Storage{
						S3: &cloudv1.S3{
							Region: "us-west-2",
							URL:    "s3://dbaas-import-e2e-test/files/e2e/import/simple_dump/",
						},
					},
				},
			},
		}
		Expect(infraCli.Create(ctx, cloudImport)).Should(Succeed())

		By("wait import complete")
		Eventually(func() error {
			err := infraCli.Get(ctx, types.NamespacedName{Namespace: cloudImport.Namespace, Name: cloudImport.Name}, cloudImport)
			if err != nil {
				logger.Errorw("error inspecting import status", "import", cloudImport.Name, "error", err.Error())
				return err
			}
			if cloudImport.Status.Phase != cloudv1.ImportCompleted {
				logger.Infow("wait import complete", "import", cloudImport.Name, "current phase", cloudImport.Status.Phase)
				return errWait
			}
			return nil
		}, 30*time.Minute, interval).Should(Succeed(), "Import should complete")
		logger.Infow("import status", "status", cloudImport.Status)
		Expect(cloudImport.Status.CompletedTables).Should(Equal(uint32(2)))
		// TODO inspect why sometimes failed:
		// Expected
		//     <uint32>: 2
		// to equal
		//     <uint32>: 0
		//
		// Expect(cloudImport.Status.PendingTables).Should(Equal(uint32(0)))
		Expect(cloudImport.Status.TotalSize > uint64(0)).To(BeTrue())
		Expect(cloudImport.Status.CurrentTables).Should(BeEmpty())

		By("Verify data in old cluster for import")
		verifyImportSampleData(ctx, *oldCluster.Status.SqlEndpoints.PublicEndpoint)

		By("delete import")
		Expect(infraCli.Delete(ctx, cloudImport)).Should(Succeed())

		By("wait import to be deleted")
		waitImportDeleted(ctx, cloudImport, 10*time.Minute, interval)

		By("create backup")

		backup := newCommonBackup(oldCluster)
		Expect(infraCli.Create(ctx, backup)).Should(Succeed())

		By("wait backup complete")
		Eventually(func() error {
			err := infraCli.Get(ctx, types.NamespacedName{Namespace: backup.Namespace, Name: backup.Name}, backup)
			if err != nil {
				logger.Errorw("error inspecting backup status", "backup", backup.Name, "error", err.Error())
				return err
			}
			if backup.Status.Phase != cloudv1.BackupComplete {
				logger.Infow("wait backup complete", "backup", backup.Name, "current phase", backup.Status.Phase)
				return errWait
			}
			return nil
		}, 15*time.Minute, interval).Should(Succeed(), "Backup should complete")
		Expect(backup.Status.BackupSize > int64(0)).To(BeTrue())
		Expect(backup.Status.BackupTS).NotTo(BeEmpty())
		Expect(backup.Status.BucketName).NotTo(BeEmpty())

		By("create restore")
		restore := newCommonRestore(newCluster, backup.Name)
		Expect(infraCli.Create(ctx, restore)).Should(Succeed())

		By("wait restore complete")
		Eventually(func() error {
			err := infraCli.Get(ctx, types.NamespacedName{Namespace: restore.Namespace, Name: restore.Name}, restore)
			if err != nil {
				logger.Errorw("error inspecting restore status", "restore", restore.Name, "error", err.Error())
				return err
			}
			if restore.Status.Phase != cloudv1.RestoreComplete {
				logger.Infow("wait restore complete", "restore", restore.Name, "current phase", restore.Status.Phase)
				return errWait
			}
			return nil
		}, 15*time.Minute, interval).Should(Succeed(), "Restore should complete")

		By("Verify data in new cluster for restore")
		verifyImportSampleData(ctx, *newCluster.Status.SqlEndpoints.PublicEndpoint)

		By("delete backup and restore")
		Expect(infraCli.Delete(ctx, backup)).Should(Succeed())
		Expect(infraCli.Delete(ctx, restore)).Should(Succeed())

		By("wait backup and restore to be deleted")
		waitBackupDeleted(ctx, backup, 5*time.Minute, interval)
		waitRestoreDeleted(ctx, restore, 5*time.Minute, interval)

		By("delete old and new clusters")
		Expect(infraCli.Delete(ctx, newCluster)).Should(Succeed())
		Expect(infraCli.Delete(ctx, oldCluster)).Should(Succeed())

		By("wait old and new cluster to be deleted")
		waitClusterDeleted(ctx, oldCluster, 5*time.Minute, interval)
		waitClusterDeleted(ctx, newCluster, 5*time.Minute, interval)
	})
})

// verifyImportSampleData verify import sample data for a TidbCluster.
func verifyImportSampleData(ctx context.Context, endpoint cloudv1.SqlEndpointStatus) {
	dsn := DSN(endpoint)
	db, err := sql.Open("mysql", dsn)
	Expect(err).ShouldNot(HaveOccurred())
	row := db.QueryRowContext(ctx, "SELECT count(*) FROM `test2`.`test1`")
	var got int
	Expect(row.Scan(&got)).Should(Succeed())
	Expect(got).Should(Equal(3))
	Expect(db.Close()).Should(Succeed())
}
