//go:build e2e
// +build e2e

package cases

import (
	"fmt"
	"io"
	"net/http"
	"testing"
	"time"

	"github.com/kr/pretty"
	. "github.com/onsi/gomega"
	"github.com/pingcap/log"
	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/message"
	"github.com/tidbcloud/observability/pkg/common/structs"
	"github.com/tidbcloud/observability/test/ft/utils"
	"go.uber.org/zap"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/utils/pointer"
)

var (
	timeout  = time.Minute * 5
	interval = time.Second * 30
)

func Test_Service(t *testing.T) {
	resp, err := http.Get(fmt.Sprintf("%s%s", utils.GetService().ServerURL, utils.ApiPrefix))
	assert.NoError(t, err)
	defer resp.Body.Close()
	service := &message.GetServiceInfoResp{}
	utils.HandleResp(t, resp, service)

	assert.NoError(t, err)
	assert.Equal(t, constants.ObservabilityGlobalServer, service.ServiceName)
}

func TestAuth0Token(t *testing.T) {
	t.Run("empty", func(t *testing.T) {
		log.Info("TestAuth0Token-empty start")
		defer log.Info("TestAuth0Token-empty end")

		s := utils.GetService()
		queryString := fmt.Sprintf("%s%s/clusters/?vendor=%s&region=%s",
			s.ServerURL, utils.ApiPrefix,
			s.Vendor, s.Region)
		request, _ := http.NewRequest("GET", queryString, nil)
		request.Header.Set("Content-Type", "application/json")

		resp, err := http.DefaultClient.Do(request)
		if err != nil {
			panic(err)
		}
		defer resp.Body.Close()
		assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
	})
	t.Run("invalid", func(t *testing.T) {
		log.Info("TestAuth0Token-invalid start")
		defer log.Info("TestAuth0Token-invalid end")

		s := utils.GetService()
		queryString := fmt.Sprintf("%s%s/clusters/?vendor=%s&region=%s",
			s.ServerURL, utils.ApiPrefix,
			s.Vendor, s.Region)
		request, _ := http.NewRequest("GET", queryString, nil)
		request.Header.Set("Content-Type", "application/json")
		request.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("%s %s", constants.BearerPrefix, "invalid"))

		resp, err := http.DefaultClient.Do(request)
		if err != nil {
			panic(err)
		}
		defer resp.Body.Close()
		assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
	})
}

func TestQueryClusters(t *testing.T) {
	type QueryCase struct {
		name string
		message.QueryClustersReq
		expectedCode  int
		contentAssert func(t *testing.T, resp *message.QueryClustersResp)
	}
	s := utils.GetService()
	cases := []QueryCase{
		{
			name:             "empty request",
			QueryClustersReq: message.QueryClustersReq{},
			expectedCode:     http.StatusInternalServerError,
			contentAssert: func(t *testing.T, resp *message.QueryClustersResp) {
				assert.NotEmpty(t, resp.Clusters)
			},
		},
		{
			name: "all",
			QueryClustersReq: message.QueryClustersReq{
				CommonReqInfo: structs.CommonReqInfo{
					Vendor: constants.ProvidedVendor(s.Vendor),
					Region: s.Region,
				},
			},
			expectedCode: 200,
			contentAssert: func(t *testing.T, resp *message.QueryClustersResp) {
				assert.NotEmpty(t, resp.Clusters)
			},
		},
		{
			name: "tenant",
			QueryClustersReq: message.QueryClustersReq{
				CommonReqInfo: structs.CommonReqInfo{
					Vendor: constants.ProvidedVendor(s.Vendor),
					Region: s.Region,
				},
				TenantID: "query1",
			},
			expectedCode: 200,
			contentAssert: func(t *testing.T, resp *message.QueryClustersResp) {
				assert.NotEmpty(t, resp.Clusters)
				assert.Equal(t, 3, len(resp.Clusters))
				assert.Equal(t, "query1", resp.Clusters[1].TenantID)
			},
		},
		{
			name: "project",
			QueryClustersReq: message.QueryClustersReq{
				CommonReqInfo: structs.CommonReqInfo{
					Vendor: constants.ProvidedVendor(s.Vendor),
					Region: s.Region,
				},
				ProjectID: "query3",
			},
			expectedCode: 200,
			contentAssert: func(t *testing.T, resp *message.QueryClustersResp) {
				assert.NotEmpty(t, resp.Clusters)
				assert.Equal(t, 1, len(resp.Clusters))
				assert.Equal(t, "query3", resp.Clusters[0].ProjectID)
			},
		},
		{
			name: "application",
			QueryClustersReq: message.QueryClustersReq{
				CommonReqInfo: structs.CommonReqInfo{
					Vendor: constants.ProvidedVendor(s.Vendor),
					Region: s.Region,
				},
				ApplicationID: "query2",
			},
			expectedCode: 200,
			contentAssert: func(t *testing.T, resp *message.QueryClustersResp) {
				assert.NotEmpty(t, resp.Clusters)
				assert.Equal(t, 1, len(resp.Clusters))
				assert.Equal(t, "query2", resp.Clusters[0].ApplicationID)
			},
		},
		{
			name: "no result",
			QueryClustersReq: message.QueryClustersReq{
				CommonReqInfo: structs.CommonReqInfo{
					Vendor: constants.ProvidedVendor(s.Vendor),
					Region: s.Region,
				},
				TenantID:      "noresult",
				ProjectID:     "query1",
				ApplicationID: "query1",
			},
			expectedCode: 200,
			contentAssert: func(t *testing.T, resp *message.QueryClustersResp) {
				assert.Empty(t, resp.Clusters)
			},
		},
		{
			name: "no result",
			QueryClustersReq: message.QueryClustersReq{
				CommonReqInfo: structs.CommonReqInfo{
					Vendor: constants.ProvidedVendor(s.Vendor),
					Region: s.Region,
				},
				TenantID:      "query1",
				ProjectID:     "noresult",
				ApplicationID: "query1",
			},
			expectedCode: 200,
			contentAssert: func(t *testing.T, resp *message.QueryClustersResp) {
				assert.Empty(t, resp.Clusters)
			},
		},
		{
			name: "no result",
			QueryClustersReq: message.QueryClustersReq{
				CommonReqInfo: structs.CommonReqInfo{
					Vendor: constants.ProvidedVendor(s.Vendor),
					Region: s.Region,
				},
				TenantID:      "query1",
				ProjectID:     "query1",
				ApplicationID: "noresult",
			},
			expectedCode: 200,
			contentAssert: func(t *testing.T, resp *message.QueryClustersResp) {
				assert.Empty(t, resp.Clusters)
			},
		},
		{
			name: "limit 2",
			QueryClustersReq: message.QueryClustersReq{
				CommonReqInfo: structs.CommonReqInfo{
					Vendor: constants.ProvidedVendor(s.Vendor),
					Region: s.Region,
				},
				ContinueRequest: message.ContinueRequest{
					Limit: 2,
				},
			},
			expectedCode: 200,
			contentAssert: func(t *testing.T, resp *message.QueryClustersResp) {
				assert.NotEmpty(t, resp.Clusters)
				assert.Equal(t, 2, len(resp.Clusters))
			},
		},
		{
			name: "limit 1000",
			QueryClustersReq: message.QueryClustersReq{
				CommonReqInfo: structs.CommonReqInfo{
					Vendor: constants.ProvidedVendor(s.Vendor),
					Region: s.Region,
				},
				ContinueRequest: message.ContinueRequest{
					Limit: 1000,
				},
			},
			expectedCode: 200,
			contentAssert: func(t *testing.T, resp *message.QueryClustersResp) {
				assert.NotEmpty(t, resp.Clusters)
				assert.Less(t, 3, len(resp.Clusters))
			},
		}, {
			name: "next gen result",
			QueryClustersReq: message.QueryClustersReq{
				CommonReqInfo: structs.CommonReqInfo{
					Vendor: constants.ProvidedVendor(s.Vendor),
					Region: s.Region,
				},
				TenantID:      nextgenTenantID,
				BizType:       utils.NexgenBizType,
				ProjectID:     "",
				ApplicationID: "333",
			},
			expectedCode: 200,
			contentAssert: func(t *testing.T, resp *message.QueryClustersResp) {
				assert.NotEmpty(t, resp.Clusters)
				assert.Equal(t, 1, len(resp.Clusters))
				assert.Equal(t, nextgenTenantID, resp.Clusters[0].TenantID)
				assert.Equal(t, "", resp.Clusters[0].ProjectID)
				assert.Equal(t, "333", resp.Clusters[0].ApplicationID)
			},
		},
	}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			log.Info("TestQueryClusters-" + c.name + " start")
			defer log.Info("TestQueryClusters-" + c.name + " end")

			request := utils.QueryReq(c.QueryClustersReq)
			resp, err := http.DefaultClient.Do(request)
			assert.NoError(t, err)
			defer resp.Body.Close()
			clustersResp := &message.QueryClustersResp{}
			utils.HandleResp(t, resp, clustersResp)
			c.contentAssert(t, clustersResp)
		})
	}
}

type CreateCase struct {
	name string
	message.CreateClusterReq
	expectedCode  int
	contentAssert func(t *testing.T, resp *structs.ObservabilityCluster)
}

func TestCreateFieldValidation(t *testing.T) {
	cases := []CreateCase{
		{
			name: "without Vendor",
			CreateClusterReq: utils.HandleWithDefault(func(req message.CreateClusterReq) message.CreateClusterReq {
				req.Vendor = ""
				return req
			}),
			expectedCode: 400,
		},
		{
			name: "invalid Vendor",
			CreateClusterReq: utils.HandleWithDefault(func(req message.CreateClusterReq) message.CreateClusterReq {
				req.Vendor = "invalid"
				return req
			}),
			expectedCode: 400,
		},
		{
			name: "without Region",
			CreateClusterReq: utils.HandleWithDefault(func(req message.CreateClusterReq) message.CreateClusterReq {
				req.Region = ""
				return req
			}),
			expectedCode: 400,
		},
		{
			name: "invalid Region",
			CreateClusterReq: utils.HandleWithDefault(func(req message.CreateClusterReq) message.CreateClusterReq {
				req.Region = "invalid"
				return req
			}),
			expectedCode: 400,
		},
		{
			name: "without TenantID",
			CreateClusterReq: utils.HandleWithDefault(func(req message.CreateClusterReq) message.CreateClusterReq {
				req.TenantID = ""
				return req
			}),
			expectedCode: 400,
		},
		{
			name: "without ApplicationID",
			CreateClusterReq: utils.HandleWithDefault(func(req message.CreateClusterReq) message.CreateClusterReq {
				req.ApplicationID = ""
				return req
			}),
			expectedCode: 400,
		},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			log.Info("TestCreateFieldValidation-" + c.name + " start")
			defer log.Info("TestCreateFieldValidation-" + c.name + " end")

			request := utils.CreateReq(c.CreateClusterReq)
			resp, err := http.DefaultClient.Do(request)
			assert.NoError(t, err)
			defer resp.Body.Close()
			assert.Equal(t, c.expectedCode, resp.StatusCode)
		})
	}
}

func TestCreateDuplicatedConcurrently(t *testing.T) {
	r := utils.HandleWithDefault(func(req message.CreateClusterReq) message.CreateClusterReq {
		req.TenantID = "concurrentt"
		req.ProjectID = "concurrentp"
		req.ApplicationID = "con3"
		return req
	})

	for i := 0; i < 5; i++ {
		go func() {
			resp, err := http.DefaultClient.Do(utils.CreateReq(r))
			if err != nil {
				log.Info(err.Error())
			}
			defer resp.Body.Close()
			log.Info("create", zap.Int("status", resp.StatusCode))
		}()
	}

	r2 := utils.HandleWithDefault(func(req message.CreateClusterReq) message.CreateClusterReq {
		req.TenantID = "concurrentt2"
		req.ProjectID = "concurrentp2"
		req.ApplicationID = "csdafa"
		return req
	})

	for i := 0; i < 5; i++ {
		go func() {
			resp, err := http.DefaultClient.Do(utils.CreateReq(r2))
			if err != nil {
				log.Info(err.Error())
			}
			defer resp.Body.Close()
			log.Info("create", zap.Int("status", resp.StatusCode))
		}()
	}
	log.Info("wait...")
	time.Sleep(1 * time.Minute)
}

func TestCreateDuplicatedValidation(t *testing.T) {
	cases := []CreateCase{
		{
			name: "duplicated cluster",
			CreateClusterReq: utils.HandleWithDefault(func(req message.CreateClusterReq) message.CreateClusterReq {
				return req
			}),
			expectedCode: 409,
		},
		{
			name: "duplicated id",
			CreateClusterReq: utils.HandleWithDefault(func(req message.CreateClusterReq) message.CreateClusterReq {
				req.ID = createdID
				return req
			}),
			expectedCode: 400,
		},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			log.Info("TestCreateDuplicatedValidation-" + c.name + " start")
			defer log.Info("TestCreateDuplicatedValidation-" + c.name + " end")

			request := utils.CreateReq(c.CreateClusterReq)
			resp, err := http.DefaultClient.Do(request)
			assert.NoError(t, err)
			defer resp.Body.Close()
			assert.Equal(t, c.expectedCode, resp.StatusCode)
		})
	}
}

func TestCreateSucceed(t *testing.T) {
	cases := []CreateCase{
		{
			name: "different application",
			CreateClusterReq: utils.HandleWithDefault(func(req message.CreateClusterReq) message.CreateClusterReq {
				req.ApplicationID = "application1"
				return req
			}),
			expectedCode: 202,
			contentAssert: func(t *testing.T, cluster *structs.ObservabilityCluster) {
				defaultCluster := utils.GetCluster(createdID)

				assert.Equal(t, "application1", cluster.ApplicationID)
				assert.Equal(t, defaultCluster.AgentInfo.RoleARN, cluster.AgentInfo.RoleARN)
				assert.Equal(t, defaultCluster.StorageInfo[0].BucketName, cluster.StorageInfo[0].BucketName)
			},
		},
		{
			name: "different project",
			CreateClusterReq: utils.HandleWithDefault(func(req message.CreateClusterReq) message.CreateClusterReq {
				req.ProjectID = "project2"
				req.ApplicationID = "application2"
				return req
			}),
			expectedCode: 202,
			contentAssert: func(t *testing.T, cluster *structs.ObservabilityCluster) {
				defaultCluster := utils.GetCluster(createdID)
				assert.Equal(t, "project2", cluster.ProjectID)
				assert.Equal(t, "application2", cluster.ApplicationID)
				assert.Equal(t, defaultCluster.AgentInfo.RoleARN, cluster.AgentInfo.RoleARN)
				assert.Equal(t, defaultCluster.StorageInfo[0].BucketName, cluster.StorageInfo[0].BucketName)
			},
		},
		{
			name: "different tenant",
			CreateClusterReq: utils.HandleWithDefault(func(req message.CreateClusterReq) message.CreateClusterReq {
				req.TenantID = "tenant3"
				req.ProjectID = "project3"
				req.ApplicationID = "application3"
				return req
			}),
			expectedCode: 202,
			contentAssert: func(t *testing.T, cluster *structs.ObservabilityCluster) {
				defaultCluster := utils.GetCluster(createdID)
				assert.Equal(t, "tenant3", cluster.TenantID)
				assert.Equal(t, "project3", cluster.ProjectID)
				assert.Equal(t, "application3", cluster.ApplicationID)
				assert.NotEmpty(t, cluster.Tags)

				assert.NotEmpty(t, cluster.StorageInfo)
				assert.NotEmpty(t, cluster.MonitorInfo.DNSName)
				assert.NotEmpty(t, cluster.MonitorInfo.SpecID)
				assert.NotEmpty(t, cluster.MonitorInfo.EndpointServiceName)

				assert.NotEmpty(t, cluster.AgentInfo.RoleARN)
				assert.NotEqual(t, defaultCluster.AgentInfo.RoleARN, cluster.AgentInfo.RoleARN)
				assert.Equal(t, defaultCluster.StorageInfo[1].BucketName, cluster.StorageInfo[0].BucketName)

				// todo check prometheus
				// todo check s3
			},
		},
		{
			name: "shared muteAlert",
			CreateClusterReq: utils.HandleWithDefault(func(req message.CreateClusterReq) message.CreateClusterReq {
				req.ApplicationID = "muteAlert"
				req.ShareMode = constants.SHARED
				req.MuteAlert = true
				return req
			}),
			expectedCode: 202,
			contentAssert: func(t *testing.T, cluster *structs.ObservabilityCluster) {
				defaultCluster := utils.GetCluster(createdID)

				assert.Equal(t, "muteAlert", cluster.ApplicationID)
				assert.Equal(t, constants.SHARED, cluster.ShareMode)
				assert.Equal(t, defaultCluster.AgentInfo.RoleARN, cluster.AgentInfo.RoleARN)
				assert.Equal(t, defaultCluster.StorageInfo[0].BucketName, cluster.StorageInfo[0].BucketName)

				// todo check prometheus
				// todo check s3
			},
		},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			log.Info("TestCreateSucceed-" + c.name + " start")
			defer log.Info("TestCreateSucceed-" + c.name + " end")

			request := utils.CreateReq(c.CreateClusterReq)
			resp, err := http.DefaultClient.Do(request)
			assert.NoError(t, err)
			defer resp.Body.Close()
			assert.Equal(t, c.expectedCode, resp.StatusCode)

			clustersResp := &message.CreateClusterResp{}
			utils.HandleResp(t, resp, clustersResp)
			assert.NotEmpty(t, clustersResp.ID)
			defer utils.DeleteCluster(clustersResp.ID)

			utils.CheckStatus(t, clustersResp.ID, utils.GetCluster, constants.CREATING, constants.ACTIVE)
			finalCluster := utils.GetCluster(clustersResp.ID)
			c.contentAssert(t, finalCluster)
		})
	}
}

func TestDelete(t *testing.T) {
	t.Run("404", func(t *testing.T) {
		log.Info("TestDelete-404 start")
		defer log.Info("TestDelete-404 end")

		resp := utils.DeleteCluster("404")
		assert.Equal(t, http.StatusNotFound, resp.StatusCode)
	})
	t.Run("status", func(t *testing.T) {
		log.Info("TestDelete-status start", zap.String("cluster", toDeleteID))
		defer log.Info("TestDelete-status end")

		utils.CheckStatus(t, toDeleteID, utils.GetCluster, constants.ACTIVE, constants.ACTIVE)
		// ensure call delete succeed
		err := wait.Poll(5*time.Second, 1*time.Minute, func() (done bool, err error) {
			resp := utils.DeleteCluster(toDeleteID)
			return resp.StatusCode == http.StatusAccepted, nil
		})
		assert.NoError(t, err)

		err = wait.Poll(interval, 1*time.Minute, func() (done bool, err error) {
			existed, cluster := utils.GetClusterIfExisted(toDeleteID)
			if existed {
				return cluster.Status == constants.DELETING, nil
			} else {
				r := utils.DeleteCluster(toDeleteID)
				return http.StatusNotFound == r.StatusCode, nil
			}
		})
		assert.NoError(t, err)

	})

	t.Run("nextgen delete", func(t *testing.T) {
		log.Info("TestDelete-status start", zap.String("cluster", toDeleteNextGenID))
		defer log.Info("TestDelete-status end")

		utils.CheckStatus(t, toDeleteNextGenID, utils.GetCluster, constants.ACTIVE, constants.ACTIVE)
		// ensure call delete succeed
		err := wait.Poll(5*time.Second, 1*time.Minute, func() (done bool, err error) {
			resp := utils.DeleteCluster(toDeleteNextGenID)
			return resp.StatusCode == http.StatusAccepted, nil
		})
		assert.NoError(t, err)

		err = wait.Poll(interval, 1*time.Minute, func() (done bool, err error) {
			existed, cluster := utils.GetClusterIfExisted(toDeleteNextGenID)
			if existed {
				return cluster.Status == constants.DELETING, nil
			} else {
				r := utils.DeleteCluster(toDeleteNextGenID)
				return http.StatusNotFound == r.StatusCode, nil
			}
		})
		assert.NoError(t, err)

	})
	t.Run("delete while creating", func(t *testing.T) {
		log.Info("TestDelete-delete while creating start")
		defer log.Info("TestDelete-delete while creating end")

		dwc := utils.CreateCluster(utils.HandleWithDefault(func(req message.CreateClusterReq) message.CreateClusterReq {
			req.TenantID = "dwc"
			req.ProjectID = "dwc"
			req.ApplicationID = "dwc"
			return req
		}))

		resp := utils.DeleteCluster(dwc.ID)
		assert.Equal(t, http.StatusAccepted, resp.StatusCode)
	})
}

func TestUpdateSpec(t *testing.T) {
	s := utils.GetService()
	t.Run("404", func(t *testing.T) {
		log.Info("TestUpdateSpec-404 start")
		defer log.Info("TestUpdateSpec-404 end")

		resp := utils.ModifySpec("invalid", s.Specs[0])
		assert.Equal(t, http.StatusNotFound, resp.StatusCode)
	})
	t.Run("empty spec", func(t *testing.T) {
		log.Info("TestUpdateSpec-empty spec start")
		defer log.Info("TestUpdateSpec-empty spec end")

		resp := utils.ModifySpec(toModifySpecID, "")
		assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
	})
	t.Run("invalid spec", func(t *testing.T) {
		log.Info("TestUpdateSpec-invalid spec start")
		defer log.Info("TestUpdateSpec-invalid spec end")

		resp := utils.ModifySpec(toModifySpecID, "ssss")
		assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
	})
	t.Run("succeed", func(t *testing.T) {
		log.Info("TestUpdateSpec-succeed start")
		defer log.Info("TestUpdateSpec-succeed end")

		resp := utils.ModifySpec(toModifySpecID, s.Specs[1])
		assert.Equal(t, http.StatusAccepted, resp.StatusCode)
		utils.CheckStatus(t, toModifySpecID, utils.GetCluster, constants.UPDATING, constants.ACTIVE)
		// todo check prometheus
	})
}

func TestCreateClusterGCP(t *testing.T) {
	if utils.GetService().Vendor != string(constants.GCP) {
		return
	}
	cases := []CreateCase{
		{
			name: "GCP vendor",
			CreateClusterReq: utils.HandleWithDefault(func(req message.CreateClusterReq) message.CreateClusterReq {
				req.Vendor = constants.GCP
				req.TenantID = "clustergcp"
				req.ProjectID = "clustergcp"
				req.ApplicationID = "clustergcp"
				req.AgentAccessARN = "regional-server@gcp-autonomous-dev.iam.gserviceaccount.com"
				return req
			}),
			expectedCode: 202,
			contentAssert: func(t *testing.T, cluster *structs.ObservabilityCluster) {
				defaultCluster := utils.GetCluster(createdID)

				assert.Equal(t, "gcp", string(cluster.Vendor))
				assert.Equal(t, defaultCluster.StorageInfo[0].BucketName, cluster.StorageInfo[0].BucketName)
			},
		},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			log.Info("TestCreateClusterGCP-" + c.name + " start")
			defer log.Info("TestCreateClusterGCP-" + c.name + " end")
			request := utils.CreateReq(c.CreateClusterReq)
			resp, err := http.DefaultClient.Do(request)
			assert.NoError(t, err)
			assert.Equal(t, c.expectedCode, resp.StatusCode)

			clustersResp := &message.CreateClusterResp{}
			utils.HandleResp(t, resp, clustersResp)
			assert.NotEmpty(t, clustersResp.ID)
			defer utils.DeleteCluster(clustersResp.ID)

			utils.CheckStatus(t, clustersResp.ID, utils.GetCluster, constants.CREATING, constants.ACTIVE)
			finalCluster := utils.GetCluster(clustersResp.ID)
			c.contentAssert(t, finalCluster)

		})
	}
}

//
//func TestConcurrentCreateShared(t *testing.T) {
//	log.Info("TestConcurrentCreateShared start")
//	defer log.Info("TestConcurrentCreateShared end")
//
//	tenant := []string{
//		"sharedt1",
//		"sharedt2",
//		"sharedt3",
//	}
//
//	project := []string{
//		"sharedp1",
//		"sharedp2",
//		"sharedp3",
//	}
//	for _, ten := range tenant {
//		_ = utils.CreateCluster(utils.DefaultCreateSharedReq(ten, "default", "default"))
//	}
//	time.Sleep(time.Second * 5)
//	for _, te := range tenant {
//		for _, pro := range project {
//			for i := 0; i < 2; i++ {
//				a := te
//				b := pro
//				c := strconv.Itoa(i)
//				go func() {
//					resp := utils.CreateCluster(utils.DefaultCreateSharedReq(a, b, c))
//					assert.NotEmpty(t, resp.ID)
//					assert.Equal(t, a, resp.TenantID)
//					assert.Equal(t, b, resp.ProjectID)
//					assert.Equal(t, c, resp.ApplicationID)
//				}()
//			}
//		}
//	}
//	time.Sleep(time.Second * 5)
//}

func TestSyncOverlayRule(t *testing.T) {
	t.Run("create overlay-template", func(t *testing.T) {
		resp := utils.ModifyPrometheusRuleTemplate("overlay-template", "create", utils.OverlayRule)
		assert.Equal(t, http.StatusAccepted, resp.StatusCode)
	})
	// TODO: better to use ginkgo rather than defer
	defer utils.DeletePrometheusTemplate("overlay-template")

	request := utils.CreateReq(utils.HandleWithDefault(func(req message.CreateClusterReq) message.CreateClusterReq {
		req.RuleTemplateID = "overlay-template"
		req.TenantID = "overlayrule"
		req.ProjectID = "prj-overlay"
		req.ApplicationID = "cluster-overlay"
		return req
	}))
	resp, err := http.DefaultClient.Do(request)
	assert.NoError(t, err)
	if resp.StatusCode == 202 {
		log.Info("TestSyncOverlayRule-create cluster success")
	} else if resp.StatusCode == 409 {
		log.Info("TestSyncOverlayRule-create cluster already exists, skip")
		return
	} else {
		log.Error("TestSyncOverlayRule-create cluster failed", zap.Int("status", resp.StatusCode))
		t.FailNow()
	}
	clustersResp := &message.CreateClusterResp{}
	utils.HandleResp(t, resp, clustersResp)
	assert.NotEmpty(t, clustersResp.ID)
	cluster := utils.GetCluster(clustersResp.ID)

	updated := structs.AlertRuleBase{
		AlertRuleID: "resources:5",
		For:         pointer.Int64(1),
		Threshold:   pointer.Float64(100),
	}
	t.Run("update alert rule", func(t *testing.T) {
		rules := map[string]structs.AlertRuleBase{
			"resources:5": updated,
		}
		resp := utils.UpdateClusterPrometheusRule(cluster.BizType, cluster.TenantID, cluster.ProjectID, cluster.ApplicationID, rules)
		assert.Equal(t, http.StatusAccepted, resp.StatusCode)
	})

	t.Run("check alert rule updated", func(t *testing.T) {
		g := NewGomegaWithT(t)
		g.Eventually(func() error {
			resp, err := utils.QueryPrometheusRule(cluster.BizType, cluster.TenantID, cluster.ProjectID, cluster.ApplicationID)
			if err != nil {
				return err
			}
			exists := false
			var updatedRule structs.AlertRuleBase
			for _, group := range resp.AlertRuleList {
				if group.Name != "resources" {
					continue
				}
				for _, rule := range group.Rules {
					if rule.AlertRuleID != "resources:5" {
						continue
					}
					exists = true
					updatedRule = rule.AlertRuleBase
				}
			}
			if !exists {
				return fmt.Errorf("could not find the updated rule in resp %+v", resp.AlertRuleList)
			}
			if *updatedRule.For != *updated.For {
				return fmt.Errorf("rules not updated, diff: %s", pretty.Diff(updatedRule, updated))
			}
			return nil
		}, time.Minute, interval).Should(Succeed(), "check rule updated in queryAlertRule API")

		g.Eventually(func() error {
			resp, err := utils.QueryClusterAlerts(cluster.BizType, cluster.TenantID, cluster.ProjectID, cluster.ApplicationID, structs.AlertStateActive)
			if err != nil {
				return err
			}
			exists := false
			var alert structs.UserAlertInfo
			for i, a := range resp.Alerts {
				if a.RuleName != "OverlayRuleTest" {
					continue
				}
				alert = *resp.Alerts[i]
				exists = true
			}
			if !exists {
				return fmt.Errorf("could not find the alert in resp %+v", *resp)
			}
			if alert.AlertName != "Total TiDB node CPU utilization exceeded 100 for 1 minutes" || alert.Severity != structs.AlertSeverityWarning {
				return fmt.Errorf("alert %+v in wrong format", alert)
			}
			return nil
		}, timeout, interval).Should(Succeed(), "check alerts triggred after updating alert rule")
	})

	t.Run("update overlay-template after user rules being modified", func(t *testing.T) {
		g := NewGomegaWithT(t)
		resp := utils.ModifyPrometheusRuleTemplate("overlay-template", "create", utils.OverlayRule, utils.MessageChange)
		assert.Equal(t, http.StatusAccepted, resp.StatusCode)
		g.Eventually(func() error {
			resp, err := utils.QueryClusterAlerts(cluster.BizType, cluster.TenantID, cluster.ProjectID, cluster.ApplicationID, structs.AlertStateActive)
			if err != nil {
				return err
			}
			exists := false
			var alert structs.UserAlertInfo
			for i, a := range resp.Alerts {
				if a.RuleName != "OverlayRuleTest" {
					continue
				}
				alert = *resp.Alerts[i]
				exists = true
			}
			if !exists {
				return fmt.Errorf("could not find the alert in resp %+v", *resp)
			}
			if alert.Message != "Overlay Test 100" {
				return fmt.Errorf("alert %+v in wrong format", alert)
			}
			return nil
		}, timeout, interval).Should(Succeed(), "check alerts message updated after template updated")
	})

	disabled := structs.AlertRuleBase{
		AlertRuleID: "resources:5",
		Status:      constants.RULE_DISABLED,
	}
	t.Run("disable alert rule", func(t *testing.T) {
		rules := map[string]structs.AlertRuleBase{
			"resources:5": disabled,
		}
		resp := utils.UpdateClusterPrometheusRule(cluster.BizType, cluster.TenantID, cluster.ProjectID, cluster.ApplicationID, rules)
		assert.Equal(t, http.StatusAccepted, resp.StatusCode)
	})

	t.Run("check alert rule disabled", func(t *testing.T) {
		g := NewGomegaWithT(t)
		g.Eventually(func() error {
			resp, err := utils.QueryPrometheusRule(cluster.BizType, cluster.TenantID, cluster.ProjectID, cluster.ApplicationID)
			if err != nil {
				return err
			}
			exists := false
			for _, group := range resp.AlertRuleList {
				if group.Name != "resources" {
					continue
				}
				for _, rule := range group.Rules {
					if rule.AlertRuleID != "resources:5" {
						continue
					}
					if rule.Status != disabled.Status {
						continue
					}
					exists = true
				}
			}
			if !exists {
				return fmt.Errorf("could not find the rule being disabled in resp %+v", *resp)
			}
			return nil
		}, time.Minute, interval).Should(Succeed(), "check rule disabled in queryAlertRule API")

		g.Eventually(func() error {
			resp, err := utils.QueryClusterAlerts(cluster.BizType, cluster.TenantID, cluster.ProjectID, cluster.ApplicationID, structs.AlertStateAll)
			if err != nil {
				return err
			}
			exists := false
			for _, a := range resp.Alerts {
				if a.RuleName != "OverlayRuleTest" {
					continue
				}
				// all alerts should be in closed
				if a.State == structs.AlertStateActive {
					return fmt.Errorf("alert in unexpected state, expect closed but get active")
				}
				exists = true
			}
			if !exists {
				return fmt.Errorf("could not find the alert in resp %+v", resp)
			}
			return nil
		}, timeout, interval).Should(Succeed(), "check alerts closed after disable alert rule")
	})
}

func TestPauseResumeCluster(t *testing.T) {
	t.Run("pause cluster", func(t *testing.T) {
		log.Info("TestPauseResumeCluster pause start")
		defer log.Info("TestPauseResumeCluster pause end")

		resp := utils.PauseCluster(toPauseResumeID)

		result := &message.PauseClusterResp{}
		respContent, err := io.ReadAll(resp.Body)
		if err != nil {
			panic(err)
		}
		err = utils.UnmarshalRespData(respContent, result)
		if err != nil {
			panic(err)
		}
		log.Info("pause cluster", zap.String("toPauseResumeID", toPauseResumeID), zap.Any("resp", resp), zap.Any("result", result), zap.String("respContent", string(respContent)))

		assert.Equal(t, http.StatusAccepted, resp.StatusCode)
	})

	t.Run("check cluster status", func(t *testing.T) {
		log.Info("TestPauseResumeCluster Check start")
		defer log.Info("TestPauseResumeCluster Check end")

		var (
			resp      *message.DetailClusterResp
			errCnt    = 0
			maxErrCnt = 5
		)

		for {
			resp = utils.QueryCluster(toPauseResumeID)
			log.Info("check cluster status ", zap.String("toPauseResumeID", toPauseResumeID), zap.Any("resp", resp))

			if resp.Status == "TO_BE_PAUSED" || resp.Status == "PAUSED" {
				break
			} else {
				errCnt++
				if errCnt < maxErrCnt {
					time.Sleep(5 * time.Second)
					continue
				}
			}
		}

		status := fmt.Sprintf("%s", resp.Status)
		if status == "TO_BE_PAUSED" {
			log.Info("cluster status is TO_BE_PAUSED")
		} else if status == "PAUSED" {
			log.Info("cluster status is PAUSED")
		} else {
			log.Error("unexpected cluster status", zap.String("status", status))
			t.FailNow()
		}
	})

	t.Run("resume cluster", func(t *testing.T) {
		log.Info("TestPauseResumeCluster resume start")
		defer log.Info("TestPauseResumeCluster resume end")

		resp := utils.ResumeCluster(toPauseResumeID)

		result := &message.ResumeClusterResp{}
		respContent, err := io.ReadAll(resp.Body)
		if err != nil {
			panic(err)
		}
		err = utils.UnmarshalRespData(respContent, result)
		if err != nil {
			panic(err)
		}
		log.Info("resume cluster", zap.String("toPauseResumeID", toPauseResumeID), zap.Any("resp", resp), zap.Any("result", result))

		assert.Equal(t, http.StatusAccepted, resp.StatusCode)
	})

	time.Sleep(60 * time.Second)

	t.Run("check cluster status - active", func(t *testing.T) {
		log.Info("TestPauseResumeCluster ActiveCheck start")
		defer log.Info("TestPauseResumeCluster ActiveCheck end")

		var (
			resp      *message.DetailClusterResp
			errCnt    = 0
			maxErrCnt = 5
		)

		for {
			resp = utils.QueryCluster(toPauseResumeID)
			log.Info("check cluster status - active", zap.String("toPauseResumeID", toPauseResumeID), zap.Any("resp", resp))

			if resp.Status == "ACTIVE" {
				break
			} else {
				errCnt++
				if errCnt < maxErrCnt {
					time.Sleep(5 * time.Second)
					continue
				}
			}
		}

		status := fmt.Sprintf("%s", resp.Status)
		assert.Equal(t, "ACTIVE", status)
	})

}

// historical/metrics/api/v1/dev-nextgen/orgs/60205/clusters/10618241629349515085/prometheus/query_range
func TestHistorical(t *testing.T) {
	s := utils.GetService()
	t.Run("historical query", func(t *testing.T) {
		log.Info("TestHistorical start")
		defer log.Info("TestHistorical end")

		queryString := fmt.Sprintf("%s/historical/metrics/api/v1/%s/orgs/%s/clusters/%s/prometheus/query_range?query=up&start=1700000000&end=1700003600&step=60",
			s.DsURL, utils.NexgenBizType, nextgenTenantID, "todelete")

		request, _ := http.NewRequest("GET", queryString, nil)
		request.Header.Set("Content-Type", "application/json")
		request.Header.Set(constants.AuthorizationHeader, fmt.Sprintf("%s %s", constants.BearerPrefix, "valid-token"))

		resp, err := http.DefaultClient.Do(request)
		if err != nil {
			panic(err)
		}
		assert.Equal(t, http.StatusOK, resp.StatusCode)
	})
}
