package openAPIScene

import (
	"encoding/json"
	"fmt"
	l4g "github.com/alecthomas/log4go"
	"github.com/dailymotion/allure-go"
	"github.com/gavv/httpexpect/v2"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/qa-PlatformAPIAuto/models"
	"github.com/tidbcloud/qa-gourd/ProductModel"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

func (o *OpenAPIE2ECase) BackupCluster(projectId, clusterId string) (*httpexpect.Response, string) {
	BackupClusterParam := map[string]string{
		"project_id": projectId,
		"cluster_id": clusterId,
	}
	backupInfo := models.NewBackup("TestBackup")
	urlInfo := o.URLInfoMap["backup"]
	BackupClusterResp := o.OpenAPIRequestInfo.DoOpenAPIRequest(urlInfo.UrlPath, urlInfo.UrlMethod, backupInfo, BackupClusterParam, nil)

	l4g.Info("backup resp:%s", BackupClusterResp.JSON().Raw())
	return BackupClusterResp, BackupClusterResp.JSON().Path("$.id").String().Raw()
}

func (o *OpenAPIE2ECase) GetBackupById(projectId, clusterId, backupId string) (*httpexpect.Response, *models.BackupItem) {
	getBackupByIdParam := map[string]string{
		"project_id": projectId,
		"cluster_id": clusterId,
		"backup_id":  backupId,
	}
	urlInfo := o.URLInfoMap["getBackupById"]
	getBackupByIdResp := o.OpenAPIRequestInfo.DoOpenAPIRequest(urlInfo.UrlPath, urlInfo.UrlMethod, nil, getBackupByIdParam, nil)

	data := getBackupByIdResp.JSON().Raw()
	l4g.Info("backup resp:%s", data)
	resByre, resByteErr := json.Marshal(data)
	var backupInfo models.BackupItem
	if resByteErr == nil {
		jsonRes := json.Unmarshal(resByre, &backupInfo)
		if jsonRes == nil {
			return getBackupByIdResp, &backupInfo
		} else {
			l4g.Error("Unmarshal backupInfo failed:%s", jsonRes)
		}
	} else {
		l4g.Error("Unmarshal backupInfo failed:%s", resByteErr)
	}
	return getBackupByIdResp, nil
}

func (o *OpenAPIE2ECase) CheckBackupAndRestore() {
	password := o.OpenAPIRequestInfo.Cluster.Password
	port, _ := strconv.Atoi(o.OpenAPIRequestInfo.Cluster.Port)
	clusterId := o.OpenAPIRequestInfo.Cluster.ClusterId
	projectId := o.OpenAPIRequestInfo.User.ProjectId
	tidbCount := o.OpenAPIRequestInfo.Cluster.TiDBNodeCount
	tikvCount := o.OpenAPIRequestInfo.Cluster.TiKVNodeCount
	tiflashCount := o.OpenAPIRequestInfo.Cluster.TiFlashNodeCount
	t := o.Reporter.T
	_, tikvStorageSizeGib, tiflashStorageSizeGib, tidbNodeSize, tikvNodeSize, tiflashNodeSize := o.OpenAPIRequestInfo.GetAvailableNodeSize(o.OpenAPIRequestInfo.Cluster.Provider, o.OpenAPIRequestInfo.Cluster.Region, 8)

	var restoreClusterId string
	res, backupId := o.OpenAPIRequestInfo.BackupCluster(projectId, clusterId)
	allure.Step(
		allure.Story("Backup cluster"),
		allure.Description(fmt.Sprintf("backup cluster, backup resp:%s", res.JSON().Raw())),
		allure.Action(func() {
			var backup *ProductModel.BackupItem
			res.Status(200)
			for round := 0; round < 15; round += 1 {
				res, backup = o.OpenAPIRequestInfo.GetBackupById(projectId, clusterId, backupId)
				if backup.Status == "SUCCESS" {
					break
				}
				time.Sleep(time.Minute * 1)
			}
			res.Status(200)
			require.Equal(t, backup.Status, "SUCCESS")
		}))
	var restoreReq *ProductModel.Restore
	if tiflashCount == 0 {
		restoreReq = &ProductModel.Restore{
			BackupId: backupId,
			Name:     "openapi-restore",
			Config: ProductModel.ClusterConfig{
				RootPassword: password,
				Port:         int32(port),
				Components: &ProductModel.ClusterComponents{
					Tidb: &ProductModel.TiDBComponent{NodeSize: tidbNodeSize, NodeQuantity: int32(tidbCount)},
					Tikv: &ProductModel.TiKVComponent{NodeSize: tikvNodeSize, StorageSizeGib: int32(tikvStorageSizeGib), NodeQuantity: int32(tikvCount)},
				},
				IpAccessList: []*ProductModel.IpAccessListItem{
					{
						Cidr:        "0.0.0.0/0",
						Description: "allow all",
					},
				},
			},
		}
	} else {
		restoreReq = &ProductModel.Restore{
			BackupId: backupId,
			Name:     "openapi-restore",
			Config: ProductModel.ClusterConfig{
				RootPassword: password,
				Port:         int32(port),
				Components: &ProductModel.ClusterComponents{
					Tidb:    &ProductModel.TiDBComponent{NodeSize: tidbNodeSize, NodeQuantity: int32(tidbCount)},
					Tikv:    &ProductModel.TiKVComponent{NodeSize: tikvNodeSize, StorageSizeGib: int32(tikvStorageSizeGib), NodeQuantity: int32(tikvCount)},
					Tiflash: &ProductModel.TiFlashComponent{NodeSize: tiflashNodeSize, StorageSizeGib: int32(tiflashStorageSizeGib), NodeQuantity: int32(tiflashCount)},
				},
				IpAccessList: []*ProductModel.IpAccessListItem{
					{
						Cidr:        "0.0.0.0/0",
						Description: "allow all",
					},
				},
			},
		}
	}

	res, restore := o.OpenAPIRequestInfo.CreateRestore(projectId, restoreReq)
	allure.Step(
		allure.Story("restore cluster"),
		allure.Description(fmt.Sprintf("restore cluster, restore resp:%s", res.JSON().Raw())),
		allure.Action(func() {
			var restoreTask *ProductModel.RestoreTaskpResp
			res.Status(200)
			restoreId := restore.Id
			restoreClusterId = restore.ClusterId
			for round := 0; round < 60; round += 1 {
				res, restoreTask = o.OpenAPIRequestInfo.GetRestoreTask(projectId, restoreId)
				res.Status(200)
				if restoreTask.Status == "SUCCESS" {
					break
				}
				time.Sleep(time.Minute * 1)
			}
			require.Equal(t, restoreTask.Status, "SUCCESS")

		}))

	res = o.OpenAPIRequestInfo.DeleteBackup(projectId, clusterId, backupId)
	allure.Step(
		allure.Story("delete backup"),
		allure.Description(fmt.Sprintf("delete cluster, restore resp:%s", res.JSON().Raw())),
		allure.Action(func() {
			res.Status(200)
			res = o.OpenAPIRequestInfo.ListBackups(projectId, clusterId)
			res.Status(200)
			require.Equal(t, res.JSON().Path("$.total").Number().Raw(), float64(0))

		}))

	result := o.OpenAPIRequestInfo.DeleteClusterById(restoreClusterId)
	allure.Step(
		allure.Story("delete backup"),
		allure.Description(fmt.Sprintf("delete cluster, restore resp:%s", res.JSON().Raw())),
		allure.Action(func() {
			require.Equal(t, result, true)
			res.Status(200)

		}))

}

func (o *OpenAPIE2ECase) Import(importType, dataformat string) {
	clusterId := o.OpenAPIRequestInfo.Cluster.ClusterId
	projectId := o.OpenAPIRequestInfo.User.ProjectId
	var importReq *ProductModel.Import
	var accountId, externalId, url, roleArn, totalSizeBytes, content, importId string
	res, roleInfoResp := o.OpenAPIRequestInfo.GetRoleInfo(projectId, clusterId)
	if importType == "S3" || importType == "LOCAL_FILE" {
		accountId = roleInfoResp.AwsImportRole.AccountId
		externalId = roleInfoResp.AwsImportRole.ExternalId
		o.OpenAPIRequestInfo.GetWebRequestInfo().PutS3FullAccessPolicy(accountId, externalId)
		roleArn = fmt.Sprintf("%s%s", "arn:aws:iam::676546228356:role/", externalId)
		if importType == "S3" {
			if dataformat == "SQL" {
				url = "s3://import-scene-test/basic-operation/"
			}
		} else {
			localFilePath := "../conf/behaviors.csv"
			cmd := exec.Command("bash", "-c", fmt.Sprintf("cat %s | wc -c | xargs echo", localFilePath))
			output, err := cmd.Output()
			l4g.Info("++++++run result:%s", string(output))
			if err != nil {
				l4g.Error("run %s failed, result is %s", cmd, output)
			}
			totalSizeBytes = strings.ReplaceAll(string(output), "\n", "")
			cmd = exec.Command("bash", "-c", fmt.Sprintf("cat %s | gzip -c | base64", localFilePath))
			output, err = cmd.Output()
			if err != nil {
				l4g.Error("run %s failed, result is %s", cmd, output)
			}
			content = strings.ReplaceAll(string(output), "\n", "")
			uploadLocalFileReq := &ProductModel.UploadLocalFile{
				LocalFileName: "behaviors.csv",
				Payload: ProductModel.PayloadInfo{
					totalSizeBytes,
					content,
				},
			}
			res, loadResp := o.OpenAPIRequestInfo.UploadLocalFile(projectId, clusterId, uploadLocalFileReq)
			allure.Step(
				allure.Story("upload local file"),
				allure.Description(fmt.Sprintf("upload local file, upload resp:%s", res.JSON().Raw())),
				allure.Action(func() {
					res.Status(200)
					url = fmt.Sprintf("file://%s/", loadResp.UploadStubId)
				}))

		}

	} else {
		if dataformat == "SQL" {
			url = "gs://import-scene-test/basic-operation/"
		}
		accountId = roleInfoResp.GcpImportRole.AccountId
		//bucketList := strings.Split(url, "/")
		o.OpenAPIRequestInfo.GetWebRequestInfo().PutGcpBucketPolicy(accountId, url)
		roleArn = ""
	}
	spec := ProductModel.SpecInfo{
		Source: ProductModel.SourceInfo{
			TypeData: importType,
			Uri:      url,
			AwsAssumeRoleAccess: &ProductModel.AwsAssumeRoleAccessInfo{
				AssumeRole: roleArn,
			},
			AwsKeyAccess: nil,
			Format: &ProductModel.FormatInfo{
				TypeData: dataformat,
				CsvConfig: ProductModel.CsvConfigInfo{
					Delimiter:       ",",
					Quote:           "\"",
					BackslashEscape: true,
					HasHeaderRow:    false,
				},
			},
		},
		Target: ProductModel.TargetInfo{
			Tables: nil,
		},
	}

	var option ProductModel.OptionsInfo

	if importType == "LOCAL_FILE" {
		table := ProductModel.Table{
			DatabaseName:    "db",
			TableName:       "test2",
			FileNamePattern: "",
		}

		spec.Target.Tables = append(spec.Target.Tables, table)
		previewReq := &ProductModel.Preview{
			Spec:           spec,
			LimitRowsCount: 10,
		}
		res, previewResp := o.OpenAPIRequestInfo.Preview(projectId, clusterId, previewReq)
		allure.Step(
			allure.Story("preview local file"),
			allure.Description(fmt.Sprintf("preview local file, preview resp:%s", res.JSON().Raw())),
			allure.Action(func() {
				res.Status(200)
				require.NotEqual(o.Reporter.T, len(previewResp.TablePreviews[0].SchemaPreview.ColumnDefinitions), 0)
				require.NotEqual(o.Reporter.T, len(previewResp.TablePreviews[0].DataPreview.Rows), 0)

				preCreateTable := ProductModel.PreCreateTable{
					DatabaseName: "db",
					TableName:    "test2",
					Schema:       previewResp.TablePreviews[0].SchemaPreview,
				}
				option = ProductModel.OptionsInfo{
					PreCreateTables: []ProductModel.PreCreateTable{preCreateTable},
				}
				importReq = &ProductModel.Import{
					Name:    "import_openapi-test",
					Spec:    spec,
					Options: option,
				}
			}))
	} else {
		importReq = &ProductModel.Import{
			Name: "import_openapi-test",
			Spec: spec,
		}
	}

	if importType != "LOCAL_FILE" {
		_, importResp := o.OpenAPIRequestInfo.DoImport(projectId, clusterId, importReq)
		importId = importResp.Id
		cancelImportReq := &ProductModel.UpdateImport{
			Action: "CANCEL",
		}
		res = o.OpenAPIRequestInfo.UpdateImport(projectId, clusterId, importId, cancelImportReq)
		allure.Step(
			allure.Story("cancel import"),
			allure.Description(fmt.Sprintf("cancel import, cancel resp:%s", res.JSON().Raw())),
			allure.Action(func() {
				res.Status(200)
				var importTask *ProductModel.ImportDetail
				for round := 0; round < 60; round += 1 {
					res, importTask = o.OpenAPIRequestInfo.GetImportTask(projectId, clusterId, importId)
					res.Status(200)
					if importTask.Status.Phase == "CANCELED" {
						break
					}
					time.Sleep(time.Minute * 1)
				}
				require.Equal(o.Reporter.T, importTask.Status.Phase, "CANCELED")

			}))
	}

	res, importResp := o.OpenAPIRequestInfo.DoImport(projectId, clusterId, importReq)
	allure.Step(
		allure.Story("import cluster"),
		allure.Description(fmt.Sprintf("import cluster, import resp:%s", res.JSON().Raw())),
		allure.Action(func() {
			res.Status(200)
			importId = importResp.Id
			var importTask *ProductModel.ImportDetail
			for round := 0; round < 60; round += 1 {
				res, importTask = o.OpenAPIRequestInfo.GetImportTask(projectId, clusterId, importId)
				res.Status(200)
				l4g.Info("importTask.Status.Phase:%s", importTask.Status.Phase)
				if importTask.Status.Phase == "COMPLETED" {
					break
				}
				time.Sleep(time.Minute * 1)
			}
			require.Equal(o.Reporter.T, importTask.Status.Phase, "COMPLETED")

		}))
}
