package autogencase

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"testing"
	"time"

	compute "cloud.google.com/go/compute/apiv1"
	"cloud.google.com/go/compute/apiv1/computepb"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/ec2"
	ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
	"github.com/icholy/digest"
	"github.com/samber/lo"
	"github.com/spf13/viper"
	"github.com/stretchr/testify/suite"
	"go.uber.org/ratelimit"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"

	"github.com/tidbcloud/tidb-management-service/functional-test/cases/openapidedicated/autogencase/model"
	dedicatedapiclient "github.com/tidbcloud/tidb-management-service/functional-test/pkg/dedicatedapi/api/client"
	"github.com/tidbcloud/tidb-management-service/utilities/randstr"
)

type TestFuzzyPrivateEndpointConnectionServiceSuite struct {
	suite.Suite

	// can set by config.yaml
	orgID             string
	projectID         string
	clusterID         string // maybe created during the e2e or perpare ahead
	clusterName       string
	clusterNamePrefix string
	tidbNodeGroupID   string
	plsServiceName    string
	provider          string
	openAPIHost       string
	openAPIKey        string
	skipTearDown      bool
	env               string
	region            string
	dbAPITesterDSN    string
	dbAPIGeneratorDSN string

	ghStartAt   time.Time
	ghRunID     string
	executionID string

	// internal struct
	dedicatedAPICli              *dedicatedapiclient.APIClient
	ec2Cli                       *ec2.Client                        // aws client
	computeAddressCli            *compute.AddressesClient           // gcp client
	computeForwardingRuleCli     *compute.ForwardingRulesClient     // gcp client
	armnetworkPrivateEndpointCli *armnetwork.PrivateEndpointsClient // azure client
	armnetworkInterfaceCli       *armnetwork.InterfacesClient
	resultRecordDAO              *model.ResultRecordDAO
	caseRecordDAO                *model.CaseRecordDAO

	casesGroupByOperationID          map[string][]*model.CaseRecord // operation_id -> cases
	resultRecords                    map[string][]*model.ResultRecord
	clustersToClean                  map[string]struct{}
	vpcEndpointsToClean              map[string]struct{}
	privateServiceConnectionsToClean map[string]struct{}
	privateEndpointsToClean          map[string]struct{}
	placeHolderLookup                placeHolderLookup
	ratelimiter                      ratelimit.Limiter
}

func TestFuzzyPrivateEndpointConnectionService(t *testing.T) {
	testSuite := &TestFuzzyPrivateEndpointConnectionServiceSuite{
		skipTearDown:                     viper.GetBool("skip_tear_down"),
		orgID:                            viper.GetString("org_id"),
		projectID:                        viper.GetString("prj_id"),
		clusterID:                        viper.GetString("cluster_id"),
		provider:                         viper.GetString("cloud_provider"),
		openAPIKey:                       viper.GetString("api_key"),
		openAPIHost:                      viper.GetString("host"),
		clusterNamePrefix:                viper.GetString("cluster_name_prefix"),
		dbAPITesterDSN:                   viper.GetString("db_api_tester_dsn"),
		dbAPIGeneratorDSN:                viper.GetString("db_api_generator_dsn"),
		executionID:                      viper.GetString("execution_id"),
		ghRunID:                          os.Getenv("GITHUB_RUN_ID"),
		casesGroupByOperationID:          make(map[string][]*model.CaseRecord),
		resultRecords:                    make(map[string][]*model.ResultRecord),
		clustersToClean:                  make(map[string]struct{}),
		vpcEndpointsToClean:              make(map[string]struct{}),
		privateServiceConnectionsToClean: make(map[string]struct{}),
		privateEndpointsToClean:          make(map[string]struct{}),
		placeHolderLookup:                make(placeHolderLookup),
		ratelimiter:                      ratelimit.New(100, ratelimit.Per(time.Minute)),
	}
	testSuite.ghStartAt = time.Now()
	switch {
	case strings.Contains(testSuite.openAPIHost, "dev") || strings.Contains(testSuite.openAPIHost, "nightly"):
		testSuite.env = "dev"
	case strings.Contains(testSuite.openAPIHost, "staging"):
		testSuite.env = "staging"
	case strings.Contains(testSuite.openAPIHost, "prod"):
		testSuite.env = "prod"
	}
	switch testSuite.provider {
	case "aws":
		testSuite.region = "us-west-2"
	case "gcp":
		testSuite.region = "us-west1"
	case "azure":
		testSuite.region = "eastus2"
	}
	testSuite.clusterName = testSuite.clusterNamePrefix + randstr.GenWithCharset(4, randstr.AlphabetCharset)
	testSuite.placeHolderLookup["clusterId"] = testSuite.clusterID
	testSuite.placeHolderLookup["regionId"] = testSuite.provider + "-" + testSuite.region
	suite.Run(t, testSuite)
}

func (su *TestFuzzyPrivateEndpointConnectionServiceSuite) SetupSuite() {
	ctx := context.Background()
	{
		cfg := dedicatedapiclient.NewConfiguration()
		cfg.Debug = false
		// Set the server URL. Notice the base path is not included in the host.
		cfg.Servers = dedicatedapiclient.ServerConfigurations{
			{
				URL: fmt.Sprintf("https://%s/v1beta1", su.openAPIHost),
			},
		}
		client := dedicatedapiclient.NewAPIClient(cfg)
		// Set the digest auth.
		username, password, _ := strings.Cut(su.openAPIKey, ":")
		cfg.HTTPClient.Transport = &digest.Transport{
			Username: username,
			Password: password,
		}
		su.dedicatedAPICli = client
		su.Require().NotNil(su.dedicatedAPICli)
	}

	switch su.provider {
	case "aws":
		awsCfg, err := config.LoadDefaultConfig(context.Background())
		su.Require().NoError(err, "load default aws config failed")
		su.ec2Cli = ec2.NewFromConfig(awsCfg)
		su.Require().NotNil(su.ec2Cli)
	case "gcp":
		addressCli, err := compute.NewAddressesRESTClient(ctx)
		su.Require().NoError(err, "NewAddressesRESTClient failed")
		su.computeAddressCli = addressCli
		forwardingRuleCli, err := compute.NewForwardingRulesRESTClient(ctx)
		su.Require().NoError(err, "NewForwardingRulesRESTClient failed")
		su.computeForwardingRuleCli = forwardingRuleCli
		su.Require().NotNil(su.computeAddressCli)
		su.Require().NotNil(su.computeForwardingRuleCli)
	case "azure":
		cred, err := azidentity.NewDefaultAzureCredential(nil)
		su.Require().NoError(err, "NewDefaultAzureCredential failed")
		privateEndpointCli, err := armnetwork.NewPrivateEndpointsClient(azureCustomerSubscriptionID, cred, nil)
		su.Require().NoError(err, "NewPrivateEndpointsClient failed")
		su.armnetworkPrivateEndpointCli = privateEndpointCli
		su.Require().NotNil(su.armnetworkPrivateEndpointCli)
		interfaceCli, err := armnetwork.NewInterfacesClient(azureCustomerSubscriptionID, cred, nil)
		su.Require().NoError(err, "NewInterfacesClient failed")
		su.armnetworkInterfaceCli = interfaceCli
		su.Require().NotNil(su.armnetworkInterfaceCli)
	}
	{
		db, err := gorm.Open(mysql.Open(su.dbAPITesterDSN), &gorm.Config{})
		su.Require().NoError(err)
		err = db.AutoMigrate(&model.ResultRecord{})
		su.Require().NoError(err)
		dao, err := model.NewResultRecordDAO(db)
		su.Require().NoError(err)
		su.resultRecordDAO = dao
		su.Require().NotNil(su.resultRecordDAO)
	}
	{
		db, err := gorm.Open(mysql.Open(su.dbAPIGeneratorDSN), &gorm.Config{})
		su.Require().NoError(err)
		dao, err := model.NewCaseRecordDAO(db)
		su.Require().NoError(err)
		su.caseRecordDAO = dao
		su.Require().NotNil(su.resultRecordDAO)
	}
	{
		ctx := context.Background()
		records, err := su.caseRecordDAO.ListCaseRecord(ctx, ServiceNamePrivateEndpointConnectionService)
		su.Require().NoError(err)
		for i := range records {
			record := &(records[i])
			group := record.OperationID
			su.casesGroupByOperationID[group] = append(su.casesGroupByOperationID[group], record)
		}
	}
}

func (su *TestFuzzyPrivateEndpointConnectionServiceSuite) TearDownSuite() {
	if su.skipTearDown {
		return
	}
	su.T().Logf("cluster to clean:%v", su.clustersToClean)
	ctx := context.Background()
	for id := range su.clustersToClean {
		_, resp, err := su.dedicatedAPICli.ClusterServiceAPI.ClusterServiceDeleteCluster(ctx, id).
			Execute() //nolint:bodyclose // resp body is already closed in Execute()
		if resp != nil && resp.StatusCode == http.StatusNotFound {
			continue
		}
		if err != nil {
			su.T().Logf("call ClusterServiceDeleteCluster failed, err:%v, clusterName:%v, x-debug-trace-id:%v",
				err, su.clusterName, resp.Header.Get("x-debug-trace-id"))
		}
	}
	switch su.provider {
	case "aws":
		su.T().Logf("vpcendpoint to clean:%v", su.vpcEndpointsToClean)
		params := new(ec2.DeleteVpcEndpointsInput)
		for id := range su.vpcEndpointsToClean {
			params.VpcEndpointIds = append(params.VpcEndpointIds, id)
		}
		_, err := su.ec2Cli.DeleteVpcEndpoints(ctx, params)
		su.Require().NoError(err)
	case "gcp":
		su.T().Logf("psc to clean:%v", su.privateServiceConnectionsToClean)
		for name := range su.privateServiceConnectionsToClean {
			{
				req := new(computepb.DeleteForwardingRuleRequest)
				req.ForwardingRule = name
				req.Project = gcpCustomerProject
				req.Region = gcpCustomerRegion
				_, err := su.computeForwardingRuleCli.Delete(ctx, req)
				su.Require().NoError(err)
			}
			{
				req := new(computepb.DeleteAddressRequest)
				req.Address = name
				req.Project = gcpCustomerProject
				req.Region = gcpCustomerRegion
				_, err := su.computeAddressCli.Delete(ctx, req)
				su.Require().NoError(err)
			}
		}
	case "azure":
		su.T().Logf("privateendpoint to clean:%v", su.privateEndpointsToClean)
		for id := range su.privateEndpointsToClean {
			poller, err := su.armnetworkPrivateEndpointCli.BeginDelete(ctx, azureCustomerResourceGroup, id, nil)
			su.Require().NoError(err)
			_, err = poller.PollUntilDone(ctx, nil)
			su.Require().NoError(err)
		}
	}

	for name := range su.privateServiceConnectionsToClean {
		{
			req := new(computepb.DeleteForwardingRuleRequest)
			req.ForwardingRule = name
			req.Project = gcpCustomerProject
			req.Region = gcpCustomerRegion
			_, err := su.computeForwardingRuleCli.Delete(ctx, req)
			su.Require().NoError(err)
		}
		{
			req := new(computepb.DeleteAddressRequest)
			req.Address = name
			req.Project = gcpCustomerProject
			req.Region = gcpCustomerRegion
			_, err := su.computeAddressCli.Delete(ctx, req)
			su.Require().NoError(err)
		}
	}
}

func (su *TestFuzzyPrivateEndpointConnectionServiceSuite) Test_100_PrepareCluster() {
	ctx := context.Background()
	if su.clusterID == "" {
		spec := dedicatedapiclient.TidbCloudOpenApidedicatedv1beta1Cluster{
			DisplayName: su.clusterName,
			RegionId:    su.provider + "-" + su.region,
			Labels:      lo.ToPtr(map[string]string{"tidb.cloud/project": su.projectID}),
			TidbNodeSetting: dedicatedapiclient.V1beta1ClusterTidbNodeSetting{
				NodeSpecKey: lo.ToPtr("2C8G"),
				TidbNodeGroups: []dedicatedapiclient.Dedicatedv1beta1TidbNodeGroup{
					{
						NodeCount: 1,
					},
				},
			},
			TikvNodeSetting: dedicatedapiclient.V1beta1ClusterStorageNodeSetting{
				NodeCount:     3,
				NodeSpecKey:   "2C8G",
				StorageSizeGi: 200,
				StorageType:   lo.ToPtr(dedicatedapiclient.CLUSTERSTORAGENODESETTINGSTORAGETYPE_STANDARD),
			},
			Port:         4000,
			RootPassword: lo.ToPtr("31415926"),
		}
		// bs, _ := json.Marshal(spec)
		// su.T().Log(string(bs))
		req := su.dedicatedAPICli.ClusterServiceAPI.ClusterServiceCreateCluster(ctx).Cluster(spec)
		cluster, resp, err := req.Execute() //nolint:bodyclose // resp body is already closed in Execute()
		if err != nil {
			var body string
			if resp != nil {
				bs, _ := io.ReadAll(resp.Body)
				body = string(bs)
			}
			su.T().
				Logf("call ClusterServiceCreateCluster failed, err:%v, clusterName:%v, x-debug-trace-id:%v, resp_body:%v",
					err, su.clusterName, resp.Header.Get("x-debug-trace-id"), body)
		}
		su.Require().NoErrorf(err,
			"call ClusterServiceCreateCluster failed, clusterName:%v, x-debug-trace-id:%v",
			su.clusterName, resp.Header.Get("x-debug-trace-id"))
		su.Require().NotNil(cluster.ClusterId)
		su.clusterID = cluster.GetClusterId()
		su.clustersToClean[su.clusterID] = struct{}{}
		su.placeHolderLookup["clusterId"] = su.clusterID
		su.T().Logf("create cluster %v", su.clusterID)
	}
}

func (su *TestFuzzyPrivateEndpointConnectionServiceSuite) Test_101_WaitClusterAvailable() {
	ctx := context.Background()
	// Timeout: 30 minutes.
	for range 30 {
		time.Sleep(30 * time.Second)
		cluster, resp, err := su.dedicatedAPICli.ClusterServiceAPI.ClusterServiceGetCluster(ctx, su.clusterID).
			Execute() //nolint:bodyclose // resp body is already closed in Execute()
		if err != nil {
			var body string
			if resp != nil {
				bs, _ := io.ReadAll(resp.Body)
				body = string(bs)
			}
			su.T().Logf("call ClusterServiceGetCluster failed, err:%v, clusterID:%v, x-debug-trace-id:%v, resp_body:%v",
				err, su.clusterID, resp.Header.Get("x-debug-trace-id"), body)
		}
		su.Require().NoErrorf(err, "call ClusterServiceGetCluster failed, clusterID:%v", su.clusterID)
		su.T().Logf("cluster %v, state: %v", su.clusterID, cluster.GetState())
		if cluster.GetState() == dedicatedapiclient.COMMONV1BETA1CLUSTERSTATE_ACTIVE {
			break
		}
	}
	req := su.dedicatedAPICli.TidbNodeGroupServiceAPI.TidbNodeGroupServiceListTidbNodeGroups(ctx, su.clusterID)
	tidbnodegroups, resp, err := req.Execute() //nolint:bodyclose // resp body is already closed in Execute()
	if err != nil {
		var body string
		if resp != nil {
			bs, _ := io.ReadAll(resp.Body)
			body = string(bs)
		}
		su.T().
			Logf("call TidbNodeGroupServiceListTidbNodeGroups failed, err:%v, clusterID:%v, x-debug-trace-id:%v, resp_body:%v",
				err, su.clusterID, resp.Header.Get("x-debug-trace-id"), body)
	}
	su.Require().NoErrorf(err,
		"call TidbNodeGroupServiceListTidbNodeGroups failed, clusterID:%v, x-debug-trace-id:%v",
		su.clusterID, resp.Header.Get("x-debug-trace-id"))
	su.Require().NotEmpty(tidbnodegroups.GetTidbNodeGroups())
	id := tidbnodegroups.GetTidbNodeGroups()[0].GetTidbNodeGroupId()
	su.tidbNodeGroupID = id
	su.placeHolderLookup["tidbNodeGroupId"] = id
	su.placeHolderLookup["privateEndpointConnection.tidbNodeGroupId"] = id
}

func (su *TestFuzzyPrivateEndpointConnectionServiceSuite) Test_101_WaitPrivateLinkServiceActive() {
	ctx := context.Background()
	// Timeout: 30 minutes.
	for range 30 {
		time.Sleep(30 * time.Second)
		req := su.dedicatedAPICli.PrivateEndpointConnectionServiceAPI.PrivateEndpointConnectionServiceGetPrivateLinkService(
			ctx,
			su.clusterID,
			su.tidbNodeGroupID,
		)
		pls, resp, err := req.Execute() //nolint:bodyclose // resp body is already closed in Execute()
		if err != nil {
			var body string
			if resp != nil {
				bs, _ := io.ReadAll(resp.Body)
				body = string(bs)
			}
			su.T().
				Logf("call PrivateEndpointConnectionServiceGetPrivateLinkService failed, err:%v, tidbNodeGroupID:%v, x-debug-trace-id:%v, resp_body:%v",
					err, su.tidbNodeGroupID, resp.Header.Get("x-debug-trace-id"), body)
		}
		su.Require().NoErrorf(err,
			"call PrivateEndpointConnectionServiceGetPrivateLinkService failed, tidbNodeGroupID:%v, x-debug-trace-id:%v",
			su.tidbNodeGroupID, resp.Header.Get("x-debug-trace-id"))
		state := pls.GetState()
		su.Require().NotEqual(dedicatedapiclient.DEDICATEDV1BETA1PRIVATELINKSERVICESTATE_DELETING, state)
		if state == dedicatedapiclient.DEDICATEDV1BETA1PRIVATELINKSERVICESTATE_CREATING {
			continue
		}
		if state == dedicatedapiclient.DEDICATEDV1BETA1PRIVATELINKSERVICESTATE_ACTIVE {
			su.plsServiceName = pls.GetServiceName()
			return
		}
	}
}

func (su *TestFuzzyPrivateEndpointConnectionServiceSuite) Test_103_PrepareAWSVPCEndpoints() {
	// create vpcendpoint which is used by CreatePrivateEndpointConnection case
	if su.provider != "aws" {
		return
	}
	ctx := context.Background()
	id, err := su.createVpcEndpoint(
		ctx,
		su.plsServiceName,
		awsCustomerVpcID,
		strings.Split(awsCustomerVpcSubnetIds, ","),
	)
	su.Require().NoErrorf(err, "createVpcEndpoint failed")
	su.placeHolderLookup["endpointId"] = id
	su.placeHolderLookup["endpointId_666"] = id
	su.vpcEndpointsToClean[id] = struct{}{}
}

func (su *TestFuzzyPrivateEndpointConnectionServiceSuite) Test_104_PrepareGCPPrivateServiceConnection() {
	// create psc which is used by CreatePrivateEndpointConnection case
	if su.provider != "gcp" {
		return
	}
	ctx := context.Background()
	pscName := "e2efuzzypsc" + randstr.GenWithCharset(4, randstr.AlphabetCharset)
	err := su.createPSC(
		ctx,
		pscName,
		gcpCustomerProject,
		gcpCustomerRegion,
		gcpCustomerNetwork,
		gcpCustomerSubNetwork,
		su.plsServiceName,
	)
	su.Require().NoError(err, "createPSC failed")
	su.placeHolderLookup["endpointId"] = pscName
	su.placeHolderLookup["endpointId_666"] = pscName
	su.privateServiceConnectionsToClean[pscName] = struct{}{}
}

func (su *TestFuzzyPrivateEndpointConnectionServiceSuite) Test_105_PrepareAzurePrivateEndpoint() {
	// create private endpoint which is used by CreatePrivateEndpointConnection case
	if su.provider != "azure" {
		return
	}
	ctx := context.Background()
	privateEndpointName := "e2e-fuzzy-private-endpoint-" + randstr.GenWithCharset(4, randstr.AlphabetCharset)
	id, err := su.createPrivateEndpoint(ctx,
		azureCustomerResourceGroup,
		privateEndpointName,
		azureCustomerRegion,
		su.plsServiceName,
		azureCustomerSubnetResourceID)
	su.Require().NoError(err, "createPrivateEndpoint failed")
	su.placeHolderLookup["endpointId"] = id
	su.placeHolderLookup["endpointId_666"] = id
	su.privateEndpointsToClean[id] = struct{}{}
}

func (su *TestFuzzyPrivateEndpointConnectionServiceSuite) Test_106_PreparePrivateEndpointConnections() {
	ctx := context.Background()
	var endpointID string
	var privateIPAddress *string

	switch su.provider {
	case "aws":
		id, err := su.createVpcEndpoint(
			ctx,
			su.plsServiceName,
			awsCustomerVpcID,
			strings.Split(awsCustomerVpcSubnetIds, ","),
		)
		su.Require().NoErrorf(err, "createVpcEndpoint failed")
		su.vpcEndpointsToClean[id] = struct{}{}
		endpointID = id
	case "gcp":
		pscName := "e2efuzzypsc" + randstr.GenWithCharset(4, randstr.AlphabetCharset)
		err := su.createPSC(
			ctx,
			pscName,
			gcpCustomerProject,
			gcpCustomerRegion,
			gcpCustomerNetwork,
			gcpCustomerSubNetwork,
			su.plsServiceName,
		)
		su.Require().NoErrorf(err, "createPSC failed")
		su.privateServiceConnectionsToClean[pscName] = struct{}{}
		endpointID = pscName
	case "azure":
		privateEndpointName := "e2e-fuzzy-private-endpoint-" + randstr.GenWithCharset(4, randstr.AlphabetCharset)
		id, err := su.createPrivateEndpoint(ctx,
			azureCustomerResourceGroup,
			privateEndpointName,
			azureCustomerRegion,
			su.plsServiceName,
			azureCustomerSubnetResourceID)
		su.Require().NoErrorf(err, "createPrivateEndpoint failed")
		su.privateEndpointsToClean[id] = struct{}{}
		endpointID = id
		resp, err := su.armnetworkInterfaceCli.Get(ctx, azureCustomerResourceGroup, privateEndpointName+"-nic", nil)
		su.Require().NoErrorf(err, "get netowrk interface failed")
		su.Require().NotEmpty(resp.Properties.IPConfigurations)
		su.Require().NotNil(resp.Properties.IPConfigurations[0])
		su.Require().NotNil(resp.Properties.IPConfigurations[0].Properties)
		privateIPAddress = resp.Properties.IPConfigurations[0].Properties.PrivateIPAddress
		su.Require().NotNil(privateIPAddress)
	}

	params := dedicatedapiclient.PrivateEndpointConnectionServiceCreatePrivateEndpointConnectionRequest{
		EndpointId:                  endpointID,
		PrivateEndpointConnectionId: lo.ToPtr("private-endpoint-" + su.clusterID),
		Labels:                      lo.ToPtr(map[string]string{"tidb.cloud/project": su.projectID}),
	}
	if su.provider == "azure" {
		params.PrivateIpAddress.Set(privateIPAddress)
	}
	req := su.dedicatedAPICli.PrivateEndpointConnectionServiceAPI.PrivateEndpointConnectionServiceCreatePrivateEndpointConnection(
		ctx,
		su.clusterID,
		su.tidbNodeGroupID,
	)
	req = req.PrivateEndpointConnection(params)
	privateEndpointConn, resp, err := req.Execute() //nolint:bodyclose // resp body is already closed in Execute()
	if err != nil {
		var body string
		if resp != nil {
			bs, _ := io.ReadAll(resp.Body)
			body = string(bs)
		}
		payload, _ := json.Marshal(params)
		su.T().
			Logf("call PrivateEndpointConnectionServiceCreatePrivateEndpointConnection failed, err:%v, payload:%v, resp_body:%v",
				err, string(payload), body)
	}
	su.Require().NoError(err, "call PrivateEndpointConnectionServiceCreatePrivateEndpointConnection failed")
	su.placeHolderLookup["privateEndpointConnectionId"] = privateEndpointConn.GetPrivateEndpointConnectionId()
}

func (su *TestFuzzyPrivateEndpointConnectionServiceSuite) Test_200_GetPrivateLinkService() {
	operationID := ServiceNamePrivateEndpointConnectionService + "_" + "GetPrivateLinkService"
	su.runTestCasses(operationID)
}

func (su *TestFuzzyPrivateEndpointConnectionServiceSuite) Test_201_ListPrivateEndpointConnections() {
	operationID := ServiceNamePrivateEndpointConnectionService + "_" + "ListPrivateEndpointConnections"
	su.runTestCasses(operationID)
}

func (su *TestFuzzyPrivateEndpointConnectionServiceSuite) Test_202_CreatePrivateEndpointConnection() {
	operationID := ServiceNamePrivateEndpointConnectionService + "_" + "CreatePrivateEndpointConnection"
	su.runTestCasses(operationID)
}

func (su *TestFuzzyPrivateEndpointConnectionServiceSuite) Test_203_GetPrivateEndpointConnection() {
	operationID := ServiceNamePrivateEndpointConnectionService + "_" + "GetPrivateEndpointConnection"
	su.runTestCasses(operationID)
}

func (su *TestFuzzyPrivateEndpointConnectionServiceSuite) Test_999_Summary() {
	ctx := context.Background()
	finishAt := time.Now()
	totalFialed := 0

	for _, results := range su.resultRecords {
		for i := range results {
			result := results[i]
			result.WorkflowFinishAt = finishAt
			result.Env = su.env
			result.CloudProvider = su.provider
			result.WorkflowStartAt = su.ghStartAt
			result.RunID = su.ghRunID
			result.ExecutionID = su.executionID
			err := su.resultRecordDAO.CreateResultRecord(ctx, result)
			su.Require().NoError(err)
		}
	}
	for operationID, results := range su.resultRecords {
		failed := 0
		for _, result := range results {
			if !result.Expected {
				failed++
			}
		}
		total := len(su.casesGroupByOperationID[operationID])
		su.T().Logf("operation %s has %v out of %v cases failed", operationID, failed, total)
		totalFialed += failed
	}
	// su.Require().Zerof(totalFialed, "%v cases failed", totalFialed)
}

// internal func
func (su *TestFuzzyPrivateEndpointConnectionServiceSuite) doRequest(
	tc *model.CaseRecord,
) (*http.Request, *http.Response, time.Duration) {
	req, err := buildRequest(tc, su.placeHolderLookup, su.openAPIHost)
	su.Require().NoError(err, "build request failed, case id %v", tc.CaseID)
	cli, err := buildHttpClientWithDigestAuth(tc, su.openAPIKey)
	su.Require().NoErrorf(err, "build http client failed, case id %v", tc.CaseID)
	_ = su.ratelimiter.Take()
	start := time.Now()
	resp, err := cli.Do(req)
	dur := time.Since(start)
	su.Require().NoError(err)
	bs, err := io.ReadAll(resp.Body)
	_ = resp.Body.Close()
	su.Require().NoError(err)
	resp.Body = ReaderWrapper{bytes.NewReader(bs)}
	return req, resp, dur
}

func (su *TestFuzzyPrivateEndpointConnectionServiceSuite) runTestCasses(operationID string) {
	testcases, ok := su.casesGroupByOperationID[operationID]
	if !ok {
		su.T().Logf("operation %s has not case", operationID)
		return
	}
	for _, tc := range testcases {
		req, resp, dur := su.doRequest(tc) //nolint:bodyclose // resp body is already closed in doRequest()
		result, err := buildResultRecord(tc, req, resp, dur)
		su.Require().NoError(err)
		su.resultRecords[operationID] = append(su.resultRecords[operationID], result)
	}
	// records := su.resultRecords[operationID]
	// for _, record := range records {
	// 	su.T().Log(record.String())
	// }
}

func (su *TestFuzzyPrivateEndpointConnectionServiceSuite) createVpcEndpoint(
	ctx context.Context,
	plsServiceName, customerVpcID string,
	customerSubnetIDs []string,
) (string, error) {
	params := &ec2.CreateVpcEndpointInput{
		ServiceName: lo.ToPtr(plsServiceName),
		VpcId:       lo.ToPtr(customerVpcID),
		DnsOptions: &ec2types.DnsOptionsSpecification{
			DnsRecordIpType: ec2types.DnsRecordIpTypeIpv4,
		},
		VpcEndpointType: ec2types.VpcEndpointTypeInterface,
		SubnetIds:       customerSubnetIDs,
	}
	out, err := su.ec2Cli.CreateVpcEndpoint(ctx, params)
	if err != nil {
		return "", err
	}
	id := lo.FromPtr(out.VpcEndpoint.VpcEndpointId)
	return id, nil
}

func (su *TestFuzzyPrivateEndpointConnectionServiceSuite) createPSC(
	ctx context.Context,
	name, project, region, network, subnetwork, plsServiceName string,
) error {
	{
		req := new(computepb.InsertAddressRequest)
		req.Project = project
		req.Region = region
		req.AddressResource = &computepb.Address{
			Name:       lo.ToPtr(name),
			Subnetwork: lo.ToPtr(subnetwork)}
		_, err := su.computeAddressCli.Insert(ctx, req)
		if err != nil {
			return err
		}
	}
	{
		req := new(computepb.InsertForwardingRuleRequest)
		req.Project = project
		req.Region = region
		req.ForwardingRuleResource = &computepb.ForwardingRule{
			Network:   lo.ToPtr(network),
			IPAddress: lo.ToPtr(name),
			Target:    lo.ToPtr(plsServiceName),
			ServiceDirectoryRegistrations: []*computepb.ForwardingRuleServiceDirectoryRegistration{
				{
					Namespace: lo.ToPtr("tidb" + su.clusterID),
					Service:   lo.ToPtr(name),
				},
			},
		}
		_, err := su.computeForwardingRuleCli.Insert(ctx, req)
		if err != nil {
			return err
		}
	}
	return nil
}

func (su *TestFuzzyPrivateEndpointConnectionServiceSuite) createPrivateEndpoint(
	ctx context.Context,
	resourceGroup, name, region, privateLinksServiceID, subnetResourceID string,
) (string, error) {
	// disable the auto approval
	params := armnetwork.PrivateEndpoint{
		Location: lo.ToPtr(region),

		Properties: &armnetwork.PrivateEndpointProperties{
			CustomNetworkInterfaceName: lo.ToPtr(name + "-nic"),
			ManualPrivateLinkServiceConnections: []*armnetwork.PrivateLinkServiceConnection{
				{
					Name: lo.ToPtr(name),
					Properties: &armnetwork.PrivateLinkServiceConnectionProperties{
						PrivateLinkServiceID: lo.ToPtr(privateLinksServiceID),
					},
				}},
			Subnet: &armnetwork.Subnet{
				ID: lo.ToPtr(subnetResourceID),
			},
		},
	}

	poller, err := su.armnetworkPrivateEndpointCli.BeginCreateOrUpdate(ctx, resourceGroup, name, params, nil)
	if err != nil {
		return "", err
	}
	resp, err := poller.PollUntilDone(ctx, nil)
	if err != nil {
		return "", err
	}
	id := *resp.ID
	return id, nil
}
