package openAPIScene

import (
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/gavv/httpexpect/v2"
	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/qa-PlatformAPIAuto/common"
	u "github.com/tidbcloud/qa-PlatformAPIAuto/util"
	"github.com/tidbcloud/qa-gourd/ProductModel"
	"github.com/tidbcloud/qa-gourd/util"

	l4g "github.com/alecthomas/log4go"
	_ "github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/idl/pbgen/proto/openapi/cm"
	"github.com/tidbcloud/qa-PlatformAPIAuto/models"
)

func (o *OpenAPIE2ECase) CheckListClusterInProject() {
	clusters1 := o.OpenAPIRequestInfo.ListClusterInProject(0, 1)
	o.Reporter.StepReport(fmt.Sprintf("list cluster in project, page: 0 ,page size: 1, listClusterResp:%+v", clusters1),
		func() {
			require.Greater(o.Reporter.T, clusters1.Total, uint32(0))
			require.Greater(o.Reporter.T, len(clusters1.Items), 0)
		})

	clusters2 := o.OpenAPIRequestInfo.ListClusterInProject(2, 5)
	o.Reporter.StepReport(fmt.Sprintf("list cluster in project, page: 2 ,page size: 5,  listClusterResp:%+v", clusters2),
		func() {
			require.Greater(o.Reporter.T, clusters2.Total, uint32(0))
			if clusters2.Total <= 5{
				require.Equal(o.Reporter.T, len(clusters2.Items), 0)
			}else{
				require.Greater(o.Reporter.T, len(clusters2.Items), 0)
			}
		})

	clusters3 := o.OpenAPIRequestInfo.ListClusterInProject(1, 0)
	o.Reporter.StepReport(fmt.Sprintf("list cluster in project, page: 1,page size: 0 , listClusterResp:%+v", clusters3),
		func() {
			require.Greater(o.Reporter.T, clusters3.Total, uint32(0))
			require.Greater(o.Reporter.T, len(clusters3.Items), 0)
		})
}

func (o *OpenAPIE2ECase) CheckPublicConnect() {
	cluster := o.OpenAPIRequestInfo.GetClusterInfo()
	//check cluster
	_, err := o.OpenAPIRequestInfo.Cluster.ConnectTidb(cluster.Status.ConnectionStrings.Standard.Host, int64(cluster.Status.ConnectionStrings.Standard.Port), o.OpenAPIRequestInfo.Cluster.Password, "test", cluster.Status.ConnectionStrings.DefaultUser)

	o.Reporter.StepReport(fmt.Sprintf("cluster public endpoint connect, err:%+v", err),
		func() {
			if err != nil {
				require.Fail(o.Reporter.T, "connect cluster failed")
			}
		})
}

func (o *OpenAPIE2ECase) CheckModifyCluster() {
	tidbCount := o.OpenAPIRequestInfo.Cluster.TiDBNodeCount
	tikvCount := o.OpenAPIRequestInfo.Cluster.TiKVNodeCount
	tiflashCount := o.OpenAPIRequestInfo.Cluster.TiFlashNodeCount
	projectId := o.OpenAPIRequestInfo.User.ProjectId
	clusterId := o.OpenAPIRequestInfo.Cluster.ClusterId
	err, tikvStorageSize, tiflashStorageSize, tidbNodeSize, tikvNodeSize, tiflashNodeSize := o.OpenAPIRequestInfo.GetAvailableNodeSize(o.OpenAPIRequestInfo.Cluster.Provider, o.OpenAPIRequestInfo.Cluster.Region, 8)
	if err != nil {
		return
	}
	tidbNodeSpec := models.UpdateTiDBComponent{
		NodeQuantity: int32(tidbCount + 1),
		NodeSize:     tidbNodeSize,
	}
	tikvNodeSpec := models.UpdateTiKVComponent{
		NodeQuantity:   int32(tikvCount + 3),
		StorageSizeGib: int32(tikvStorageSize + 10),
		NodeSize:       tikvNodeSize,
	}

	tiflashNodeSpec := models.UpdateTiFlashComponent{
		NodeSize:       tiflashNodeSize,
		StorageSizeGib: int32(tiflashStorageSize + 10),
		NodeQuantity:   int32(tiflashCount + 1),
	}
	configs := &models.UpdateClusterConfig{
		Components: &models.UpdateClusterComponents{&tidbNodeSpec, &tikvNodeSpec, &tiflashNodeSpec},
	}
	req := &models.UpdateClusterReq{Config: configs}
	modifyClusterParam := map[string]string{
		"project_id": projectId,
		"id":         clusterId,
	}
	urlInfo := o.URLInfoMap["modifyCluster"]
	res := o.OpenAPIRequestInfo.DoOpenAPIRequest(urlInfo.UrlPath, urlInfo.UrlMethod, req, modifyClusterParam, nil)
	o.Reporter.StepReport(fmt.Sprintf("modify cluster, body:%+v, modify cluster res:%s", req, res.JSON().Raw()),
		func() {
			res.Status(200)
			//check status
			for round := 0; round < 90; round += 1 {
				cluster := o.OpenAPIRequestInfo.GetClusterInfo()
				if cluster.Status.ClusterStatus == "MODIFYING" {
					time.Sleep(time.Second * 30)
					continue
				} else if cluster.Status.ClusterStatus == "AVAILABLE" {
					require.Equal(o.Reporter.T, cluster.Config.Components.Tidb.NodeSize, tidbNodeSize)
					require.Equal(o.Reporter.T, cluster.Config.Components.Tidb.NodeQuantity, int32(tidbCount+1))
					require.Equal(o.Reporter.T, cluster.Config.Components.Tikv.NodeSize, tikvNodeSize)
					require.Equal(o.Reporter.T, cluster.Config.Components.Tikv.NodeQuantity, int32(tikvCount+3))
					require.Equal(o.Reporter.T, cluster.Config.Components.Tikv.StorageSizeGib, int32(tikvStorageSize+10))
					require.Equal(o.Reporter.T, cluster.Config.Components.Tiflash.NodeQuantity, int32(tiflashCount+1))
					require.Equal(o.Reporter.T, cluster.Config.Components.Tiflash.StorageSizeGib, int32(tiflashStorageSize+10))
					require.Equal(o.Reporter.T, cluster.Config.Components.Tiflash.NodeSize, tiflashNodeSize)
				} else {
					require.Fail(o.Reporter.T, "modify cluster Failed!")
				}
			}
		})

	tikvNodeSpec = models.UpdateTiKVComponent{
		NodeQuantity:   int32(tikvCount + 3),
		StorageSizeGib: int32(tikvStorageSize + 20),
	}
	if o.OpenAPIRequestInfo.Cluster.Provider == "AWS" {
		configs = &models.UpdateClusterConfig{
			Components: &models.UpdateClusterComponents{nil, &tikvNodeSpec, nil},
		}
		req = &models.UpdateClusterReq{Config: configs}
		res = o.OpenAPIRequestInfo.DoOpenAPIRequest(urlInfo.UrlPath, urlInfo.UrlMethod, req, modifyClusterParam, nil)
		o.Reporter.StepReport(fmt.Sprintf("modify cluster, body:%+v, modify cluster res:%s", req, res.JSON().Raw()),
			func() {
				res.JSON().Path("$.message").String().Equal("You've reached the maximum modification rate " +
					"per storage of COMPONENT_TYPE_TIKV. Wait at least 6 hours between modifications per storage of COMPONENT_TYPE_TIKV.")
				res.Status(400).JSON().Path("$.code").Equal(60100018)
			})
	}

	tidbNodeSpec = models.UpdateTiDBComponent{
		NodeQuantity: int32(tidbCount),
	}
	tikvNodeSpec = models.UpdateTiKVComponent{
		NodeQuantity: int32(tikvCount),
	}

	tiflashNodeSpec = models.UpdateTiFlashComponent{
		NodeQuantity: int32(0),
	}

	configs = &models.UpdateClusterConfig{
		Components: &models.UpdateClusterComponents{&tidbNodeSpec, &tikvNodeSpec, &tiflashNodeSpec},
	}

	req = &models.UpdateClusterReq{Config: configs}
	res = o.OpenAPIRequestInfo.DoOpenAPIRequest(urlInfo.UrlPath, urlInfo.UrlMethod, req, modifyClusterParam, nil)
	o.Reporter.StepReport(fmt.Sprintf("modify cluster, body:%+v, modify cluster res:%s", req, res.JSON().Raw()),
		func() {
			res.Status(200)
			//check status
			for round := 0; round < 45; round += 1 {
				cluster := o.OpenAPIRequestInfo.GetClusterInfo()
				if cluster.Status.ClusterStatus == "MODIFYING" {
					time.Sleep(time.Second * 30)
					continue
				} else if cluster.Status.ClusterStatus == "AVAILABLE" {
					require.Equal(o.Reporter.T, cluster.Config.Components.Tidb.NodeSize, tidbNodeSize)
					require.Equal(o.Reporter.T, cluster.Config.Components.Tikv.NodeSize, tikvNodeSize)
					require.Equal(o.Reporter.T, cluster.Config.Components.Tidb.NodeQuantity, int32(tidbCount))
					require.Equal(o.Reporter.T, cluster.Config.Components.Tikv.NodeQuantity, int32(tikvCount))
					require.Equal(o.Reporter.T, cluster.Config.Components.Tikv.StorageSizeGib, int32(tikvStorageSize+10))
					/*if provider == "aws"{
						require.Equal(t, cluster.Config.Components.Tikv.StorageSizeGib, int32(tikvStorageSize+10))
					}else{
						require.Equal(t, cluster.Config.Components.Tikv.StorageSizeGib, int32(tikvStorageSize+20))
					}*/
					require.Nil(o.Reporter.T, cluster.Config.Components.Tiflash)
					//require.Equal(t, cluster.Config.Components.Tiflash.NodeQuantity, int32(0))
					//require.Equal(t, cluster.Config.Components.Tiflash.StorageSizeGib, int32(tiflashStorageSize+10))
					//require.Equal(t, cluster.Config.Components.Tiflash.NodeSize, tiflashNodeSize)
				} else {
					require.Fail(o.Reporter.T, "modify cluster Failed!")
				}
			}
		})
}

func (o *OpenAPIE2ECase) CheckClusterItem() {
	region := o.OpenAPIRequestInfo.Cluster.Region
	provider := o.OpenAPIRequestInfo.Cluster.Provider
	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, o.OpenAPIRequestInfo.Cluster.CPU)

	cluster := o.OpenAPIRequestInfo.GetClusterInfo()
	require.Equal(t, "AVAILABLE", cluster.Status.ClusterStatus)
	require.Equal(t, region, cluster.Region)
	require.Equal(t, provider, cluster.CloudProvider)
	require.Equal(t, projectId, cluster.ProjectId)
	tidbC := tidbCount
	tikvC := tikvCount
	tiflashC := tiflashCount

	if cluster.ClusterType == "DEVELOPER" {
		require.Equal(t, cluster.Config.Components.Tidb.NodeSize, "Shared0")
		require.Equal(t, cluster.Config.Components.Tidb.StorageSizeGib, int32(0))
		require.Equal(t, cluster.Config.Components.Tidb.NodeQuantity, int32(1))
		require.Equal(t, cluster.Config.Components.Tikv.NodeSize, "Shared0")
		require.Equal(t, cluster.Config.Components.Tikv.StorageSizeGib, int32(0))
		require.Equal(t, cluster.Config.Components.Tikv.NodeQuantity, int32(1))
		require.Equal(t, cluster.Config.Components.Tiflash.NodeSize, "Shared0")
		require.Equal(t, cluster.Config.Components.Tiflash.StorageSizeGib, int32(0))
		require.Equal(t, cluster.Config.Components.Tiflash.NodeQuantity, int32(1))
	} else {
		require.Equal(t, cluster.Config.Components.Tidb.NodeSize, tidbNodeSize)
		require.Equal(t, cluster.Config.Components.Tidb.StorageSizeGib, int32(0))
		require.Equal(t, cluster.Config.Components.Tidb.NodeQuantity, int32(tidbCount))
		require.Equal(t, cluster.Config.Components.Tikv.NodeSize, tikvNodeSize)
		require.Equal(t, cluster.Config.Components.Tikv.StorageSizeGib, int32(tikvStorageSizeGib))
		require.Equal(t, cluster.Config.Components.Tikv.NodeQuantity, int32(tikvCount))
		if tiflashCount != 0 {
			require.Equal(t, cluster.Config.Components.Tiflash.NodeSize, tiflashNodeSize)
			require.Equal(t, cluster.Config.Components.Tiflash.StorageSizeGib, int32(tiflashStorageSizeGib))
			require.Equal(t, cluster.Config.Components.Tiflash.NodeQuantity, int32(tiflashCount))
		}
	}

	for _, node := range cluster.Status.NodeMap.Tidb {
		require.Contains(t, node.AvailabilityZone, cluster.Region)
		tidbC--
		require.Contains(t, node.NodeName, "tidb-")
		require.Equal(t, node.NodeSize, tidbNodeSize)
		require.Equal(t, int(node.StorageSizeGib), 0)
		ramG, _ := strconv.Atoi(node.RamBytes)
		if node.NodeSize == "Shared0" {
			require.Equal(t, node.VcpuNum, int32(0))
			require.Equal(t, node.RamBytes, "0")
		} else {
			require.Equal(t, node.NodeSize, strconv.Itoa(int(node.VcpuNum))+"C"+strconv.Itoa(ramG/1024/1024/1024)+"G")
		}
	}
	for _, node := range cluster.Status.NodeMap.Tikv {
		require.Contains(t, node.AvailabilityZone, cluster.Region)
		ramG, _ := strconv.Atoi(node.RamBytes)
		tikvC--
		require.Contains(t, node.NodeName, "tikv-")
		require.Equal(t, node.NodeSize, tikvNodeSize)
		require.Equal(t, int(node.StorageSizeGib), tikvStorageSizeGib)
		if node.NodeSize == "Shared0" {
			require.Equal(t, node.VcpuNum, int32(0))
			require.Equal(t, node.RamBytes, "0")
		} else {
			require.Equal(t, node.NodeSize, strconv.Itoa(int(node.VcpuNum))+"C"+strconv.Itoa(ramG/1024/1024/1024)+"G")
		}
	}
	if cluster.Status.NodeMap.Tiflash != nil {
		for _, node := range cluster.Status.NodeMap.Tiflash {
			require.Contains(t, node.AvailabilityZone, cluster.Region)
			tiflashC--
			require.Contains(t, node.NodeName, "tiflash-")
			require.Equal(t, node.NodeSize, tiflashNodeSize)
			require.Equal(t, int(node.StorageSizeGib), tiflashStorageSizeGib)
			ramG, _ := strconv.Atoi(node.RamBytes)
			if node.NodeSize == "Shared0" {
				require.Equal(t, node.VcpuNum, int32(0))
				require.Equal(t, node.RamBytes, "0")
			} else {
				require.Equal(t, node.NodeSize, strconv.Itoa(int(node.VcpuNum))+"C"+strconv.Itoa(ramG/1024/1024/1024)+"G")
			}
		}
	}
	require.Zero(t, tidbC)
	require.Zero(t, tikvC)
	require.Zero(t, tiflashC)

}

func (o *OpenAPIE2ECase) CheckPauseAndResume() {
	projectId := o.OpenAPIRequestInfo.User.ProjectId
	clusterId := o.OpenAPIRequestInfo.Cluster.ClusterId
	pause := true
	configs := &models.UpdateClusterConfig{
		Paused: &pause,
	}
	req := &models.UpdateClusterReq{Config: configs}
	//paused backing cluster
	_, backupId := o.BackupCluster(projectId, clusterId)

	modifyClusterParam := map[string]string{
		"project_id": projectId,
		"id":         clusterId,
	}
	urlInfo := o.URLInfoMap["modifyCluster"]
	res := o.OpenAPIRequestInfo.DoOpenAPIRequest(urlInfo.UrlPath, urlInfo.UrlMethod, req, modifyClusterParam, nil)

	l4g.Info("pause backing cluster, body:%+v, pause cluster res:%s, %s", req, res.JSON().Raw(), res.Raw().Status)
	o.Reporter.StepReport(fmt.Sprintf("pause backing cluster, body:%+v, pause cluster res:%s", req, res.JSON().Raw()),
		func() {
			res.Status(400).JSON().Path("$.code").Equal(60100027)
			res.Status(400).JSON().Path("$.message").String().Contains("the cluster cannot be paused if it is backing up")
			//check status
		})
	var backup *models.BackupItem
	for round := 0; round < 20; round += 1 {
		_, backup = o.GetBackupById(projectId, clusterId, backupId)
		if backup.Status == "SUCCESS" {
			break
		}
		time.Sleep(time.Second * 30)
	}
	l4g.Info("check backup result, expectStatus:%s, actual status:%s", "SUCCESS", backup.Status)
	o.Reporter.StepReport(fmt.Sprintf("check backup result, expectStatus:%s, actual status:%s", "SUCCESS", backup.Status),
		func() {
			require.Equal(o.Reporter.T, "SUCCESS", backup.Status)
		})
	time.Sleep(time.Second * 30)
	res = o.OpenAPIRequestInfo.DoOpenAPIRequest(urlInfo.UrlPath, urlInfo.UrlMethod, req, modifyClusterParam, nil)
	l4g.Info("pause cluster, body:%+v, pause cluster res:%s", req, res.JSON().Raw())
	o.Reporter.StepReport(fmt.Sprintf("pause cluster, body:%+v, pause cluster res:%s", req, res.JSON().Raw()),
		func() {
			res.Status(200)
			//check status
		})

	cluster := o.OpenAPIRequestInfo.GetClusterInfo()
	for round := 0; round < 30; round += 1 {
		if cluster.Status.ClusterStatus == "PAUSED" {
			break
		}
		time.Sleep(time.Second * 30)
		cluster = o.OpenAPIRequestInfo.GetClusterInfo()
	}
	l4g.Info("check cluster status, expectStatus:%s, actual status:%s", "PAUSED", cluster.Status.ClusterStatus)
	o.Reporter.StepReport(fmt.Sprintf("check cluster status, expectStatus:%s, actual status:%s", "PAUSED", cluster.Status.ClusterStatus),
		func() {
			require.Equal(o.Reporter.T, "PAUSED", cluster.Status.ClusterStatus)
			//check status
		})

	// pause again
	res = o.OpenAPIRequestInfo.DoOpenAPIRequest(urlInfo.UrlPath, urlInfo.UrlMethod, req, modifyClusterParam, nil)
	l4g.Info("pause cluster, body:%+v, pause cluster res:%s", req, res.JSON().Raw())
	o.Reporter.StepReport(fmt.Sprintf("pause cluster, body:%+v, pause cluster res:%s", req, res.JSON().Raw()),
		func() {
			res.Status(200)
			//check status
		})
	// scale paused cluster
	tidbNodeSpec := models.UpdateTiDBComponent{
		NodeQuantity: int32(o.OpenAPIRequestInfo.Cluster.TiDBNodeCount + 1),
	}
	tikvNodeSpec := models.UpdateTiKVComponent{
		NodeQuantity: int32(o.OpenAPIRequestInfo.Cluster.TiKVNodeCount),
	}
	scaleConfigs := &models.UpdateClusterConfig{
		Components: &models.UpdateClusterComponents{&tidbNodeSpec, &tikvNodeSpec, nil},
	}
	req = &models.UpdateClusterReq{Config: scaleConfigs}
	res = o.OpenAPIRequestInfo.DoOpenAPIRequest(urlInfo.UrlPath, urlInfo.UrlMethod, req, modifyClusterParam, nil)
	l4g.Info("pause cluster, body:%+v, pause cluster res:%s", req, res.JSON().Raw())
	o.Reporter.StepReport(fmt.Sprintf("pause cluster, body:%+v, pause cluster res:%s", req, res.JSON().Raw()),
		func() {
			res.Status(400).JSON().Path("$.code").Equal(49900001)
			res.Status(400).JSON().Path("$.message").String().Contains("cluster status is not AVAILABLE")
		})

	time.Sleep(time.Minute * 2)

	pause = false
	configs = &models.UpdateClusterConfig{
		Paused: &pause,
	}
	req = &models.UpdateClusterReq{Config: configs}
	res = o.OpenAPIRequestInfo.DoOpenAPIRequest(urlInfo.UrlPath, urlInfo.UrlMethod, req, modifyClusterParam, nil)
	cluster = o.OpenAPIRequestInfo.GetClusterInfo()

	l4g.Info("check cluster status, expectStatus:%s, actual status:%s", "RESUMING", cluster.Status.ClusterStatus, res.JSON().Raw())
	o.Reporter.StepReport(fmt.Sprintf("resume cluster, check cluster status, expectStatus:%s, actual status:%s", "RESUMING", cluster.Status.ClusterStatus),
		func() {
			require.Equal(o.Reporter.T, "RESUMING", cluster.Status.ClusterStatus)
			//check status
		})

	// resume again
	res = o.OpenAPIRequestInfo.DoOpenAPIRequest(urlInfo.UrlPath, urlInfo.UrlMethod, req, modifyClusterParam, nil)
	l4g.Info("resume cluster, body:%+v, resume cluster res:%s", req, res.JSON().Raw())
	o.Reporter.StepReport(fmt.Sprintf("resume cluster again, resume cluster, body:%+v, resume cluster res:%s", req, res.JSON().Raw()),
		func() {
			res.Status(200)
			//check status
		})

	req = &models.UpdateClusterReq{Config: scaleConfigs}
	res = o.OpenAPIRequestInfo.DoOpenAPIRequest(urlInfo.UrlPath, urlInfo.UrlMethod, req, modifyClusterParam, nil)
	l4g.Info("resume cluster, body:%+v, resume cluster res:%s", req, res.JSON().Raw())
	o.Reporter.StepReport(fmt.Sprintf("resume cluster when cluster is scaling, body:%+v, resume cluster res:%s", req, res.JSON().Raw()),
		func() {
			res.JSON().Path("$.code").Number().Equal(49900001)
			res.Status(400).JSON().Path("$.message").String().Contains("cluster status is not AVAILABLE")
		})

	for round := 0; round < 30; round += 1 {
		cluster = o.OpenAPIRequestInfo.GetClusterInfo()
		if cluster.Status.ClusterStatus == "RESUMING" {
			time.Sleep(time.Second * 30)
			continue
		} else if cluster.Status.ClusterStatus == "AVAILABLE" {
			break
		} else {
			require.Fail(o.Reporter.T, "resume cluster Failed!")
		}
	}
	o.Reporter.StepReport(fmt.Sprintf("check cluster's status, expect:%s, actual:%s, body:%+v, "+
		"resume cluster res:%s", "AVAILABLE", cluster.Status.ClusterStatus, req, res.JSON().Raw()),
		func() {
			require.Equal(o.Reporter.T, "AVAILABLE", cluster.Status.ClusterStatus)
		})
}

const (
	listPrivateEndpointsURL = "/api/v1beta/projects/{project_id}/private_endpoints"
	endpointServiceURL      = "/api/v1beta/projects/{project_id}/clusters/{cluster_id}/private_endpoint_service"
	clusterEndpointURL      = "/api/v1beta/projects/{project_id}/clusters/{cluster_id}/private_endpoints"
	deleteEndpointURL       = "/api/v1beta/projects/{project_id}/clusters/{cluster_id}/private_endpoints/{endpoint_id}"
)

func (o *OpenAPIE2ECase) CreateEndpointService() *common.PrivateEndpointServiceResp {
	projectId := o.OpenAPIRequestInfo.User.ProjectId
	clusterId := o.OpenAPIRequestInfo.Cluster.ClusterId
	//query private endpoint service
	pathParam := map[string]string{
		"project_id": projectId,
		"cluster_id": clusterId,
	}
	res := o.OpenAPIRequestInfo.DoOpenAPIRequest(endpointServiceURL, http.MethodGet, nil, pathParam, nil)
	l4g.Info(fmt.Sprintf("query private endpoint service, res: %v", res.JSON().Raw()))
	o.Reporter.StepReport(fmt.Sprintf("query private endpoint service,res: %v", res.JSON().Raw()), func() {
		res.Status(http.StatusOK)
		res.JSON().Path("$.private_endpoint_service.cloud_provider").IsEqual(strings.ToUpper(o.OpenAPIRequestInfo.Cluster.Provider))
	})

	if res.JSON().Path("$.private_endpoint_service.status").String().Raw() == "NOT_CONFIGURE" {
		//create privatelink service
		createResp := o.OpenAPIRequestInfo.DoOpenAPIRequest(endpointServiceURL, http.MethodPost, nil, pathParam, nil)
		l4g.Info(fmt.Sprintf("create private endpoint service, res: %v", createResp.JSON().Raw()))
		o.Reporter.StepReport(fmt.Sprintf("create private endpoint service,res: %v", createResp.JSON().Raw()), func() {
			createResp.Status(http.StatusOK)
			createResp.JSON().Path("$.private_endpoint_service.cloud_provider").IsEqual(strings.ToUpper(o.OpenAPIRequestInfo.Cluster.Provider))
			createResp.JSON().Path("$.private_endpoint_service.status").IsEqual("CREATING")
		})
		//double create check
		createResp = o.OpenAPIRequestInfo.DoOpenAPIRequest(endpointServiceURL, http.MethodPost, nil, pathParam, nil)
		l4g.Info(fmt.Sprintf("create private endpoint service second time, res: %v", createResp.JSON().Raw()))
		o.Reporter.StepReport(fmt.Sprintf("create private endpoint service,res: %v", createResp.JSON().Raw()), func() {
			createResp.Status(http.StatusBadRequest)
			createResp.JSON().Path("$.code").IsEqual(49900001)
			createResp.JSON().Path("$.message").IsEqual("private endpoint service has been created")
		})
	}
	var endpointService = common.PrivateEndpointServiceResp{}
	if res.JSON().Path("$.private_endpoint_service.status").String().Raw() != "ACTIVE" {
		for round := 0; round < 30; round += 1 {
			time.Sleep(time.Second * 30)
			res = o.OpenAPIRequestInfo.DoOpenAPIRequest(endpointServiceURL, http.MethodGet, nil, pathParam, nil)
			endpointService, _ = util.UnmarshalResult(res, endpointService)
			if endpointService.PrivateEndpointService.Status == "ACTIVE" {
				break
			}
		}
	} else {
		endpointService, _ = util.UnmarshalResult(res, endpointService)
	}
	o.Reporter.StepReport(fmt.Sprintf("private endpoint service status is %s, post is: %d", endpointService.PrivateEndpointService.Status, endpointService.PrivateEndpointService.Port), func() {
		assert.Equal(o.Reporter.T, "ACTIVE", endpointService.PrivateEndpointService.Status)
		assert.Equal(o.Reporter.T, o.OpenAPIRequestInfo.Cluster.Port, strconv.Itoa(int(endpointService.PrivateEndpointService.Port)))
	})
	return &endpointService
}

func (o *OpenAPIE2ECase) CheckPrivatelink(dnsName, endpointServiceName string) {
	projectId := o.OpenAPIRequestInfo.User.ProjectId
	clusterId := o.OpenAPIRequestInfo.Cluster.ClusterId

	//create endpoint
	w := o.OpenAPIRequestInfo.GetWebRequestInfo()
	provider := w.Cluster.Provider
	//加载配置
	var gcpEndPointName string
	if provider == ProductModel.GCP {
		gcpEndPointName = "endpoint" + u.GenTempName()
		defer w.StopGCPInstance()
		defer w.DeleteVPCEndpoint(gcpEndPointName)
	}
	var endPoint string
	var err error
	if provider == ProductModel.AWS {
		endPoint, err = w.CreateAWSVPCEndpoint(endpointServiceName)
	} else if provider == ProductModel.GCP {
		endPoint, err = w.CreateGCPVPCEndpoint(gcpEndPointName, endpointServiceName)
	}

	o.Reporter.StepReport(fmt.Sprintf("createEndPoint, result is:%s, reason is:%s", endPoint, err),
		func() {
			assert.Nil(o.Reporter.T, err)
		})

	if err != nil {
		o.OpenAPIRequestInfo.Cluster.RemainFlag = true
		return
	}

	createPrivateLinkBody := map[string]string{"endpoint_name": endPoint}
	pathParam := map[string]string{
		"project_id": projectId,
		"cluster_id": clusterId,
	}

	createRes := o.OpenAPIRequestInfo.DoOpenAPIRequest(clusterEndpointURL, http.MethodPost, createPrivateLinkBody, pathParam, nil)
	l4g.Info("create private link, the body is %s, pathParam is %s, createPrivateLinkResp is %+v", createPrivateLinkBody,
		pathParam, createRes.JSON().Raw())
	o.Reporter.StepReport(fmt.Sprintf("create private link, body:%v, pathParam:%s, createPrivateLinkResp:%s", createPrivateLinkBody, pathParam, createRes.JSON().Raw()), func() {
		createRes.Status(http.StatusOK)
		createRes.JSON().Path("$.private_endpoint.status").NotEqual("Failed")
	})

	if createRes.JSON().Path("$.private_endpoint.status").String().Raw() == "Failed" {
		o.OpenAPIRequestInfo.Cluster.RemainFlag = true
		return
	}
	endPointId := createRes.JSON().Path("$.private_endpoint.id").String().Raw()

	var privateEndpoint *common.PrivateEndpoint
	for round := 0; round < 30; round++ {
		time.Sleep(time.Second * 30)
		resp := o.OpenAPIRequestInfo.DoOpenAPIRequest(clusterEndpointURL, http.MethodGet, nil, pathParam, nil)
		l4g.Info("get private endpoint :%s", resp.JSON().Raw())
		var privateEndpoints = common.ListPrivateEndpointsResp{}
		privateEndpoints, _ = util.UnmarshalResult(resp, privateEndpoints)
		for _, endpoint := range privateEndpoints.Endpoints {
			if endpoint.ClusterId == clusterId && endpoint.EndpointName == endPoint && endpoint.Status == "ACTIVE" {
				privateEndpoint = endpoint
				break
			}
		}
		if privateEndpoint != nil && privateEndpoint.Status == "ACTIVE" {
			break
		}
	}

	o.Reporter.StepReport(fmt.Sprintf("check private endpoint status is %s", privateEndpoint.ServiceStatus), func() {
		assert.Equal(o.Reporter.T, "ACTIVE", privateEndpoint.Status)
		assert.Equal(o.Reporter.T, endPointId, privateEndpoint.Id)
	})
	if privateEndpoint.Status != "ACTIVE" {
		o.OpenAPIRequestInfo.Cluster.RemainFlag = true
		return
	}
	//project list check
	projectPath := map[string]string{
		"project_id": projectId,
	}
	listRes := o.OpenAPIRequestInfo.DoOpenAPIRequest(listPrivateEndpointsURL, http.MethodGet, nil, projectPath, nil)
	l4g.Info(fmt.Sprintf("query privatelinklist, res: %v", listRes.JSON().Raw()))
	o.Reporter.StepReport(fmt.Sprintf("query private endpoints, res: %v", listRes.JSON().Raw()), func() {
		listRes.Status(http.StatusOK)
		find := false
		var privateEndpoints = common.ListPrivateEndpointsResp{}
		privateEndpoints, _ = util.UnmarshalResult(listRes, privateEndpoints)
		for _, endpoint := range privateEndpoints.Endpoints {
			if endpoint.Id == privateEndpoint.Id && endpoint.ClusterId == clusterId && endpoint.EndpointName == endPoint && endpoint.Status == "ACTIVE" {
				find = true
				break
			}
		}
		assert.True(o.Reporter.T, find)
	})

	var result bool
	if provider == ProductModel.GCP {
		endPointHost := gcpEndPointName + "." + dnsName
		result = w.CheckConnectTiDBInVM(endPointHost, "root")
	} else if provider == ProductModel.AWS {
		w.EnableAWSVPCEndpointDNS(endPoint)
		result = w.CheckConnectTiDBInVM(dnsName, "root")
	}
	if result == false {
		o.OpenAPIRequestInfo.Cluster.RemainFlag = true
		return
	}
	//delete endpoint
	pathParam["endpoint_id"] = privateEndpoint.Id
	deleteRes := o.OpenAPIRequestInfo.DoOpenAPIRequest(deleteEndpointURL, http.MethodDelete, nil, pathParam, nil)
	o.Reporter.StepReport(fmt.Sprintf("delete private endpoint, res: %v", deleteRes.JSON().Raw()), func() {
		deleteRes.Status(http.StatusOK)
	})
}

const (
	accesspointURL               = "/api/v1beta/projects/{project_id}/clusters/{id}/accesspoints/{ac_id}"
	accesspointsURL              = "/api/v1beta/projects/{project_id}/clusters/{id}/accesspoints"
	webAccesspointsURL           = "/api/v1/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/accesspoints"
	clusterURL                   = "/api/v1beta/projects/{project_id}/clusters/{id}"
	addAccesspointPrivatelinkURL = "/api/v1beta/projects/{project_id}/clusters/{id}/accesspoints/{ac_id}/privateLinkEndpoints"
	acPrivatelinkURL             = "/api/v1beta/projects/{project_id}/clusters/{id}/accesspoints/{ac_id}/privateLinkEndpoints/{pl_id}"
)

func (o *OpenAPIE2ECase) AddAccessPoint(tidbNode int, acName string) *common.Accesspoint {
	projectId := o.OpenAPIRequestInfo.User.ProjectId
	clusterId := o.OpenAPIRequestInfo.Cluster.ClusterId

	pathParam := map[string]string{
		"project_id": projectId,
		"id":         clusterId,
	}

	//add accesspoint
	body := map[string]string{
		"display_name": acName,
	}
	res := o.OpenAPIRequestInfo.DoOpenAPIRequest(accesspointsURL, http.MethodPost, body, pathParam, nil)
	l4g.Info(fmt.Sprintf("add accesspoint,body: %v,res: %v", body, res.JSON().Raw()))
	o.Reporter.StepReport(fmt.Sprintf("add accesspoint,body: %v,res: %v", body, res.JSON().Raw()), func() {
		res.Status(http.StatusOK)
	})
	if res.Raw().StatusCode != http.StatusOK {
		return nil
	}
	acId := res.JSON().Path("$.id").String().Raw()

	//scale cluster
	tidbNodeSpec := models.UpdateTiDBComponent{
		NodeQuantity:  int32(tidbNode),
		AccesspointId: acId,
	}

	config := &models.UpdateClusterConfig{
		Components: &models.UpdateClusterComponents{&tidbNodeSpec, nil, nil},
	}
	req := &models.UpdateClusterReq{Config: config}
	res = o.OpenAPIRequestInfo.DoOpenAPIRequest(clusterURL, http.MethodPatch, req, pathParam, nil)
	l4g.Info(fmt.Sprintf("scale accesspoint tidb, body: %v,res: %v", req, res.JSON().Raw()))
	o.Reporter.StepReport(fmt.Sprintf("scale accesspoint tidb,body: %v,res: %v", body, res.JSON().Raw()), func() {
		res.Status(http.StatusOK)
	})
	if res.Raw().StatusCode != http.StatusOK {
		return nil
	}
	if !o.waitClusterStatusUntilAvailable("MODIFYING", 30) {
		require.Fail(o.Reporter.T, "scale ac tidb node failed")
	}

	//query accesspoint
	pathParam["ac_id"] = acId
	res = o.OpenAPIRequestInfo.DoOpenAPIRequest(accesspointURL, http.MethodGet, nil, pathParam, nil)
	var accesspoint = common.Accesspoint{}
	accesspoint, err := util.UnmarshalResult(res, accesspoint)
	if err != nil {
		require.Fail(o.Reporter.T, "get accesspoint failed!")
	}
	if accesspoint.PrivateEndpoint.PrivateLinkService.Status != "ACTIVE" {
		for round := 0; round < 30; round += 1 {
			time.Sleep(30 * time.Second)
			res = o.OpenAPIRequestInfo.DoOpenAPIRequest(accesspointURL, http.MethodGet, nil, pathParam, nil)
			accesspoint, _ = util.UnmarshalResult(res, accesspoint)
			if accesspoint.PrivateEndpoint.PrivateLinkService.Status == "ACTIVE" {
				break
			}
		}
	}
	o.Reporter.StepReport(fmt.Sprintf("get ac info is: %+v", accesspoint), func() {
		assert.Equal(o.Reporter.T, "ACTIVE", accesspoint.PrivateEndpoint.PrivateLinkService.Status)
		assert.Equal(o.Reporter.T, o.OpenAPIRequestInfo.Cluster.Port, strconv.Itoa(int(accesspoint.PrivateEndpoint.Port)))
		assert.Equal(o.Reporter.T, int32(tidbNode), accesspoint.TidbNodeQuantity)
	})
	return &accesspoint
}

func (o *OpenAPIE2ECase) CheckAccessPointList(acList []*common.Accesspoint, isOpenAPI bool) {
	orgId := o.OpenAPIRequestInfo.User.OrgId
	projectId := o.OpenAPIRequestInfo.User.ProjectId
	clusterId := o.OpenAPIRequestInfo.Cluster.ClusterId

	var res *httpexpect.Response
	if isOpenAPI {
		pathParam := map[string]string{
			"project_id": projectId,
			"id":         clusterId,
		}
		res = o.OpenAPIRequestInfo.DoOpenAPIRequest(accesspointsURL, http.MethodGet, nil, pathParam, nil)
		l4g.Info(fmt.Sprintf("query accesspoint list, path param is: %v,res: %v", pathParam, res.JSON().Raw()))
	} else {
		pathParam := map[string]string{
			"org_id":     orgId,
			"project_id": projectId,
			"cluster_id": clusterId,
		}
		res = o.OpenAPIRequestInfo.GetWebRequestInfo().DoWebAPIRequest(webAccesspointsURL, http.MethodGet, nil, pathParam, nil)
		l4g.Info(fmt.Sprintf("query accesspoint list, path param is: %v,res: %v", pathParam, res.JSON().Raw()))
	}

	var acListResp common.ListAccesspointsResp
	acListResp, err := util.UnmarshalResult(res, acListResp)
	if err != nil {
		require.Fail(o.Reporter.T, "get accesspoint list failed!")
	}
	o.Reporter.StepReport(fmt.Sprintf("query accesspoints,res: %v", res.JSON().Raw()), func() {
		res.Status(http.StatusOK)
		assert.Equal(o.Reporter.T, int32(len(acList)+1), acListResp.Total)
		for _, item := range acListResp.Items {
			flag := false
			if item.DisplayName == "default" {
				flag = true
			} else {
				for _, ac := range acList {
					if ac.Id == item.Id {
						flag = true
						if isOpenAPI {
							assert.Equal(o.Reporter.T, ac.TidbNodeQuantity, item.TidbNodeQuantity) //web api has no this content
						}
						assert.Equal(o.Reporter.T, ac.DisplayName, item.DisplayName)
						break
					}
				}
			}
			assert.True(o.Reporter.T, flag)
		}
	})
}

// CheckAccessPointPublicConnect only support 0.0.0.0/0 cidr
func (o *OpenAPIE2ECase) CheckAccessPointPublicConnect(ac *common.Accesspoint, cidr string) {
	projectId := o.OpenAPIRequestInfo.User.ProjectId
	clusterId := o.OpenAPIRequestInfo.Cluster.ClusterId
	pathParam := map[string]string{
		"project_id": projectId,
		"id":         clusterId,
		"ac_id":      string(ac.Id),
	}
	if cidr == "0.0.0.0/0" {
		IPAllowlist := &cm.IPAllowlist{
			Cidr: cidr,
		}
		acPubEndpoint := cm.PublicEndpointSpec{
			IpAllowlist: []*cm.IPAllowlist{IPAllowlist},
		}
		req := map[string]cm.PublicEndpointSpec{
			"public_endpoint": acPubEndpoint,
		}
		res := o.OpenAPIRequestInfo.DoOpenAPIRequest(accesspointURL, http.MethodPatch, req, pathParam, nil)
		l4g.Info(fmt.Sprintf("add public ip allowlist, res: %v", res.JSON().Raw()))
		o.Reporter.StepReport(fmt.Sprintf("add public ip allowlist,res: %v", res.JSON().Raw()), func() {
			res.Status(http.StatusOK)
		})
	}

	w := o.OpenAPIRequestInfo.GetWebRequestInfo()
	if cidr == "0.0.0.0/0" {
		assert.True(o.Reporter.T, w.CheckConnectTiDBByPublicEndpoint("root", ac.PublicEndpoint.Host))
	} else {
		assert.False(o.Reporter.T, w.CheckConnectTiDBByPublicEndpoint("root", ac.PublicEndpoint.Host))
	}

}
func (o *OpenAPIE2ECase) CheckAccessPointPrivateLink(endpoint string, accesspoint *common.Accesspoint) {
	//add privatelink
	projectId := o.OpenAPIRequestInfo.User.ProjectId
	clusterId := o.OpenAPIRequestInfo.Cluster.ClusterId
	pathParam := map[string]string{
		"project_id": projectId,
		"id":         clusterId,
		"ac_id":      string(accesspoint.Id),
	}
	body := map[string]string{
		"endpoint_name": endpoint,
	}
	res := o.OpenAPIRequestInfo.DoOpenAPIRequest(addAccesspointPrivatelinkURL, http.MethodPost, body, pathParam, nil)
	l4g.Info(fmt.Sprintf("add ac privatelink, body: %v,res: %v", body, res.JSON().Raw()))
	o.Reporter.StepReport(fmt.Sprintf("add ac privatelink, body: %v,res: %v", body, res.JSON().Raw()), func() {
		res.Status(http.StatusOK)
	})
	id := res.JSON().Path("$.id").String().Raw()

	w := o.OpenAPIRequestInfo.GetWebRequestInfo()
	w.EnableAWSVPCEndpointDNS(endpoint)
	w.CheckAWSVPCEndpointAvailable(endpoint)
	rel := w.CheckConnectTiDBInVM(accesspoint.PrivateEndpoint.Host, "root")
	o.Reporter.StepReport(fmt.Sprintf("ac privatelink connect result is : %v", rel), func() {
		assert.True(o.Reporter.T, rel)
	})

	//delete privatelink check
	pathParam["pl_id"] = id
	res = o.OpenAPIRequestInfo.DoOpenAPIRequest(acPrivatelinkURL, http.MethodDelete, nil, pathParam, nil)
	l4g.Info(fmt.Sprintf("delete ac privatelink, pathparam is: %v,res: %v", pathParam, res.JSON().Raw()))
	o.Reporter.StepReport(fmt.Sprintf("add ac privatelink, body: %v,res: %v", body, res.JSON().Raw()), func() {
		res.Status(http.StatusOK)
	})
	w.DeleteVPCEndpoint(endpoint)
}

func (o *OpenAPIE2ECase) DeleteAccessPoint(ac *common.Accesspoint) {
	projectId := o.OpenAPIRequestInfo.User.ProjectId
	clusterId := o.OpenAPIRequestInfo.Cluster.ClusterId

	pathParam := map[string]string{
		"project_id": projectId,
		"id":         clusterId,
		"ac_id":      ac.Id,
	}
	res := o.OpenAPIRequestInfo.DoOpenAPIRequest(accesspointURL, http.MethodDelete, nil, pathParam, nil)
	l4g.Info(fmt.Sprintf("delete accesspoint, path param is: %v,res: %v", pathParam, res.JSON().Raw()))
	o.Reporter.StepReport(fmt.Sprintf("delete accesspoint,body: %v,res: %v", pathParam, res.JSON().Raw()), func() {
		res.Status(http.StatusOK)
	})
	//query again
	res = o.OpenAPIRequestInfo.DoOpenAPIRequest(accesspointURL, http.MethodGet, nil, pathParam, nil)
	o.Reporter.StepReport(fmt.Sprintf("query delete accesspoint,res: %v", res.JSON().Raw()), func() {
		res.Status(http.StatusNotFound)
		res.JSON().Path("$.code").IsEqual(49900004)
	})
}

func (o *OpenAPIE2ECase) waitClusterStatusUntilAvailable(curStatus string, waitRound int) bool {
	rel := false
	for round := 0; round < 30; round += 1 {
		cluster := o.OpenAPIRequestInfo.GetClusterInfo()
		if cluster.Status.ClusterStatus == curStatus {
			time.Sleep(time.Second * 30)
			continue
		} else if cluster.Status.ClusterStatus == "AVAILABLE" {
			rel = true
			break
		}
	}
	return rel
}
