package common

import (
	"bytes"
	"crypto/md5"
	"crypto/sha256"
	"crypto/tls"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"hash"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strings"
	"testing"
	"time"

	l4g "github.com/alecthomas/log4go"
	"github.com/gavv/httpexpect/v2"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

type Msg func(t *testing.T)

var Envn string
var RequestUrl string
var OrgId string
var AlertEmail string
var ProjectId string
var TenantEmail string
var RedirectUrl string
var RecievePasswordEmail string
var AuditEmail string
var AdminEmail string
var AwsRegion string
var GcpRegion string
var CaseList string
var Port string
var PassWord string
var CPU string
var HasTiflash string
var TestCaseMap = make(map[string]Msg)
var CreateClusterStartTime int
var RemainClusterFlag bool
var RunUser string
var InfraImage string
var FeatureGates string
var ExpFeatureGates string
var RequireSecureTransport string
var S3AccessKey string
var S3SecretKey string

type HTTPConfigOpt func(*httpexpect.Config)

func UseRequireReporter(t require.TestingT) HTTPConfigOpt {
	return func(config *httpexpect.Config) {
		config.Reporter = httpexpect.NewRequireReporter(t)
	}
}

func NewConfig(t assert.TestingT, url string) httpexpect.Config {
	reporter := httpexpect.NewAssertReporter(t)
	config := httpexpect.Config{
		BaseURL:        url,
		RequestFactory: httpexpect.DefaultRequestFactory{},
		Client:         http.DefaultClient,
		Reporter:       reporter,
	}
	return config
}

func Get(t assert.TestingT, url []string, header map[string]string, pathparam map[string]string,
	param interface{}, opts ...HTTPConfigOpt) *httpexpect.Response {
	config := NewConfig(t, url[0])
	for _, opt := range opts {
		opt(&config)
	}

	req := httpexpect.NewRequest(config, "GET", url[1])
	//req.WithTimeout(time.Duration(60)*time.Second)
	req.WithRetryDelay(30*time.Second, 30*time.Second)
	req.WithMaxRetries(3)
	req.WithRetryPolicy(httpexpect.RetryAllErrors)
	req.WithRedirectPolicy(httpexpect.DontFollowRedirects)
	if header != nil {
		req.WithHeaders(header)
	}
	if pathparam != nil {
		req.WithPathObject(pathparam)
	}
	if param != nil {
		req.WithQueryObject(param)
	}
	resp := req.Expect()
	return resp
}
func GetWithRedirect(t assert.TestingT, url []string, header map[string]string, pathparam map[string]string,
	param interface{}, opts ...HTTPConfigOpt) *httpexpect.Response {
	config := NewConfig(t, url[0])
	for _, opt := range opts {
		opt(&config)
	}
	req := httpexpect.NewRequestC(config, "GET", url[1])
	req.WithRetryDelay(30*time.Second, 30*time.Second)
	req.WithMaxRetries(3)
	req.WithRetryPolicy(httpexpect.RetryAllErrors)
	req.WithRedirectPolicy(httpexpect.FollowAllRedirects)
	if header != nil {
		req.WithHeaders(header)
	}
	if pathparam != nil {
		req.WithPathObject(pathparam)
	}
	if param != nil {
		req.WithQueryObject(param)
	}
	resp := req.Expect()
	return resp
}
func PostForm(t assert.TestingT, url []string, header map[string]string, body interface{},
	pathparam map[string]string, opts ...HTTPConfigOpt) *httpexpect.Response {
	config := NewConfig(t, url[0])
	for _, opt := range opts {
		opt(&config)
	}

	req := httpexpect.NewRequest(config, "POST", url[1])
	if header != nil {
		req.WithHeaders(header)
	}
	if pathparam != nil {
		req.WithPathObject(pathparam)
	}
	if body != nil {
		req.WithForm(body)
	}
	resp := req.Expect()
	return resp
}

// use PutForm to get 400 response for validate bankcard check
func PutForm(t assert.TestingT, url []string, header map[string]string, body interface{},
	pathparam map[string]string, opts ...HTTPConfigOpt) *httpexpect.Response {
	config := NewConfig(t, url[0])
	for _, opt := range opts {
		opt(&config)
	}

	req := httpexpect.NewRequest(config, "PUT", url[1])
	if header != nil {
		req.WithHeaders(header)
	}
	if pathparam != nil {
		req.WithPathObject(pathparam)
	}
	if body != nil {
		req.WithJSON(body)
	}
	resp := req.Expect()
	return resp
}

func Post(t assert.TestingT, url []string, header map[string]string, body interface{},
	pathparam map[string]string, param interface{}, opts ...HTTPConfigOpt) *httpexpect.Response {
	config := NewConfig(t, url[0])
	for _, opt := range opts {
		opt(&config)
	}

	req := httpexpect.NewRequest(config, "POST", url[1])
	req.WithRetryDelay(10*time.Second, 10*time.Second)
	req.WithMaxRetries(3)
	req.WithRetryPolicy(httpexpect.RetryAllErrors)
	if header != nil {
		req.WithHeaders(header)
	}
	if pathparam != nil {
		req.WithPathObject(pathparam)
	}
	if param != nil {
		req.WithQueryObject(param)
	}
	if body != nil {
		req.WithJSON(body)
	}
	resp := req.Expect()
	return resp
}

func Put(t assert.TestingT, url []string, header map[string]string, body interface{},
	pathparam map[string]string, param interface{}, opts ...HTTPConfigOpt) *httpexpect.Response {
	l4g.Info("url：%s, body: %v, pathparam:%s, param:%s", url, body, pathparam, param)
	config := NewConfig(t, url[0])
	for _, opt := range opts {
		opt(&config)
	}

	req := httpexpect.NewRequest(config, "PUT", url[1])
	req.WithRetryDelay(10*time.Second, 10*time.Second)
	req.WithMaxRetries(3)
	req.WithRetryPolicy(httpexpect.RetryAllErrors)
	if header != nil {
		req.WithHeaders(header)
	}
	if pathparam != nil {
		req.WithPathObject(pathparam)
	}
	if body != "" {
		req.WithJSON(body)
	}
	if param != nil {
		req.WithQueryObject(param)
	}
	resp := req.Expect()
	return resp
}

func Put1(t assert.TestingT, url []string, header map[string]string, body []byte,
	pathparam map[string]string, param interface{}, opts ...HTTPConfigOpt) *httpexpect.Response {
	l4g.Info("url：%s, pathparam:%s, param:%s", url, pathparam, param)
	config := NewConfig(t, url[0])
	for _, opt := range opts {
		opt(&config)
	}

	req := httpexpect.NewRequest(config, "PUT", url[1])
	req.WithRetryDelay(10*time.Second, 10*time.Second)
	req.WithMaxRetries(3)
	req.WithRetryPolicy(httpexpect.RetryAllErrors)
	if header != nil {
		req.WithHeaders(header)
	}
	if pathparam != nil {
		req.WithPathObject(pathparam)
	}
	//if body != "" {
	//req.WithJSON(body)
	req.WithBytes(body)
	//}
	if param != nil {
		req.WithQueryObject(param)
	}
	resp := req.Expect()
	return resp
}

func PutFile(t assert.TestingT, url []string, header map[string]string, fileName, filePath string,
	pathparam map[string]string, param interface{}, opts ...HTTPConfigOpt) *httpexpect.Response {
	l4g.Info("url：%s, pathparam:%s, param:%s", url, pathparam, param)
	config := NewConfig(t, url[0])
	for _, opt := range opts {
		opt(&config)
	}

	req := httpexpect.NewRequest(config, "PUT", url[1])
	req.WithRetryDelay(10*time.Second, 10*time.Second)
	req.WithMaxRetries(3)
	req.WithRetryPolicy(httpexpect.RetryAllErrors)
	if header != nil {
		req.WithHeaders(header)
	}
	if pathparam != nil {
		req.WithPathObject(pathparam)
	}
	if param != nil {
		req.WithQueryObject(param)
	}
	fh, _ := os.Open(filePath)
	b, _ := ioutil.ReadAll(fh)
	req.WithMultipart().WithFileBytes("", fileName, b)
	fh.Close()

	resp := req.Expect()
	return resp
}

func Delete(t assert.TestingT, url []string, header map[string]string,
	pathparam map[string]string, param interface{}, opts ...HTTPConfigOpt) *httpexpect.Response {
	config := NewConfig(t, url[0])
	for _, opt := range opts {
		opt(&config)
	}

	req := httpexpect.NewRequest(config, "DELETE", url[1])
	req.WithRetryDelay(10*time.Second, 10*time.Second)
	req.WithMaxRetries(0)
	req.WithRetryPolicy(httpexpect.RetryAllErrors)
	if header != nil {
		req.WithHeaders(header)
	}
	if pathparam != nil {
		req.WithPathObject(pathparam)
	}
	if param != nil {
		req.WithQueryObject(param)
	}
	resp := req.Expect()
	l4g.Info("Delete response is %s", resp)
	// TODO: remove retry logic since our httpexpect lib version has bug in cloneURL. After we upgrade the lib and fix all compatible problems, this retry logic should be retained.
	// if resp.Raw().StatusCode == Status409 || resp.Raw().StatusCode == StatusOK {
	// 	return resp
	// } else {
	// 	req.WithMaxRetries(3)
	// 	resp = req.Expect()
	// }
	return resp
}

func Patch(t assert.TestingT, url []string, header map[string]string, body interface{},
	pathparam map[string]string, opts ...HTTPConfigOpt) *httpexpect.Response {
	config := NewConfig(t, url[0])
	for _, opt := range opts {
		opt(&config)
	}

	req := httpexpect.NewRequest(config, "PATCH", url[1])
	req.WithRetryDelay(10*time.Second, 10*time.Second)
	req.WithMaxRetries(3)
	req.WithRetryPolicy(httpexpect.RetryAllErrors)
	if header != nil {
		req.WithHeaders(header)
	}
	if pathparam != nil {
		req.WithPathObject(pathparam)
	}
	if body != nil {
		req.WithJSON(body)
	}
	resp := req.Expect()
	return resp
}

// TODO(okJiang): typo name
type DbassRequestInfo struct {
	CommonEnv             Env
	UrlInfos              []UrlInfo
	Header                map[string]string
	RecievePasswordHeader map[string]string
	Host                  string
	ClusterId             string
}

func NewDbassRequestInfo(t assert.TestingT) *DbassRequestInfo {
	commonEnv := GetCommonInfo(Envn)
	l4g.Info("commonEnv is:%s", commonEnv)
	urlInfos := GetUrlInfo()
	if RequestUrl != "" {
		commonEnv.Url = RequestUrl
	}
	if OrgId != "" {
		commonEnv.OrgId = OrgId
	}
	if ProjectId != "" {
		commonEnv.ProjectId = ProjectId
	}

	if TenantEmail != "" {
		commonEnv.Email = TenantEmail
	}
	if AdminEmail != "" {
		commonEnv.AdminEmail = AdminEmail
	}
	if RedirectUrl != "" {
		commonEnv.RedirectUri = RedirectUrl
	}
	if RecievePasswordEmail != "" {
		commonEnv.RecievePasswordEmail = RecievePasswordEmail
	}
	if AuditEmail != "" {
		commonEnv.AuditEmail = AuditEmail
	}
	if AwsRegion != "" {
		commonEnv.AwsRegion = AwsRegion
	}
	if GcpRegion != "" {
		commonEnv.GcpRegion = GcpRegion
	}
	if Port != "" {
		commonEnv.Port = Port
	}
	if PassWord != "" {
		commonEnv.Password = PassWord
	}

	if RunUser != "" {
		commonEnv.RunUser = RunUser
	}
	if RequireSecureTransport != "" {
		commonEnv.RequireSecureTransport = RequireSecureTransport
	}

	if S3AccessKey != "" {
		commonEnv.AccessKey = S3AccessKey
	}

	if S3SecretKey != "" {
		commonEnv.SecretKey = S3SecretKey
	}

	if CPU != "" {
		commonEnv.TidbCpu = CPU
		commonEnv.TikvCpu = CPU
		if CPU != "2" && CPU != "4" && HasTiflash == "true" {
			commonEnv.TiflashCpu = CPU
		} else {
			commonEnv.TiflashCpu = ""
		}
		commonEnv.TidbMem = ""
		commonEnv.TikvMem = ""
		commonEnv.TiflashMem = ""
	}
	host := strings.Trim(commonEnv.Url, " ")
	globalDbassRequest := &DbassRequestInfo{CommonEnv: commonEnv, UrlInfos: urlInfos, Host: host, ClusterId: ""}
	if commonEnv.TokenPath != "" {
		l4g.Info("+++++++++++++")
		globalDbassRequest.SetHeaderByPath()
	} else {

		globalDbassRequest.SetHeaderByAPI(t)
	}

	/*header := map[string]string{
		"Authorization": fmt.Sprintf("Bearer %s", strings.Trim(commonEnv.Authorization, " ")),
	}*/

	return globalDbassRequest
}

func (dbassRequestInfo *DbassRequestInfo) ResetHost(server, provider, region string) {
	commonEnv := GetCommonInfo(Envn)
	if server == "o11yRegional" {
		dbassRequestInfo.Host = strings.Replace(strings.Trim(commonEnv.O11yRegionalUrl, " "), "region", region+"."+provider, -1)
		l4g.Info("Reset host %s", dbassRequestInfo.Host)
	} else if server == "o11yGlobal" {
		dbassRequestInfo.Host = strings.Trim(commonEnv.O11yGlobalUrl, " ")
		l4g.Info("Reset host %s", dbassRequestInfo.Host)
	} else {
		dbassRequestInfo.Host = strings.Trim(commonEnv.Url, " ")
		l4g.Info("Reset host %s", dbassRequestInfo.Host)
	}
}

func (dbassRequestInfo *DbassRequestInfo) SetToken(token string) {
	dbassRequestInfo.CommonEnv.Authorization = "Bearer " + token
}

func (dbassRequestInfo *DbassRequestInfo) SetHeader(header map[string]string) {
	dbassRequestInfo.Header = header
}

// get token fron jenkins if token path is not empty
func (dbassRequestInfo *DbassRequestInfo) setHeaderToken(t assert.TestingT, email string) {
	var currToken string
	if dbassRequestInfo.CommonEnv.TokenPath != "" {
		currToken = GetTokenFromJenkins(dbassRequestInfo.CommonEnv.TokenPath, email)
		dbassRequestInfo.SetHeader(map[string]string{
			"Authorization":   fmt.Sprintf("%s", currToken),
			"X-FUNCTION-TEST": "true",
		})
	} else {
		currToken = dbassRequestInfo.GetTokenByAPI(t, email)
		dbassRequestInfo.SetHeader(map[string]string{
			"Authorization":   fmt.Sprintf("Bearer %s", currToken),
			"X-FUNCTION-TEST": "true",
		})
	}
}

func (dbassRequestInfo *DbassRequestInfo) GetTokenByAPI(t assert.TestingT, email string) string {
	tokenBody := NewToken(dbassRequestInfo.CommonEnv.Auth0ClientSecret, dbassRequestInfo.CommonEnv.Auth0ClientId,
		dbassRequestInfo.CommonEnv.Auth0, email)
	getTokenResp := dbassRequestInfo.DbassRequest(t, "getToken", tokenBody, nil, nil, nil)
	//l4g.Info("getTokenResp is: %s, tokenBody is: %s", getTokenResp.JSON().Raw(), tokenBody)
	token := getTokenResp.JSON().Path("$.token").String().Raw()
	return token
}

func (dbassRequestInfo *DbassRequestInfo) SetHeaderByAPI(t assert.TestingT) {
	//获取token
	token := dbassRequestInfo.GetTokenByAPI(t, dbassRequestInfo.CommonEnv.Email)
	dbassRequestInfo.CommonEnv.Authorization = token
	dbassRequestInfo.Header = map[string]string{
		"Authorization":   fmt.Sprintf("Bearer %s", token),
		"X-FUNCTION-TEST": "true",
		//"Authorization": fmt.Sprintf("Bearer %s", dbassRequestInfo.CommonEnv.Authorization),
	}
	l4g.Info("header is :%s", dbassRequestInfo.Header)
	if dbassRequestInfo.CommonEnv.RecievePasswordEmail != "" {
		token = dbassRequestInfo.GetTokenByAPI(t, dbassRequestInfo.CommonEnv.RecievePasswordEmail)
		dbassRequestInfo.RecievePasswordHeader = map[string]string{
			"Authorization": fmt.Sprintf("Bearer %s", token),
		}
	}

}

func (dbassRequestInfo *DbassRequestInfo) ReSetHeaderToken(t assert.TestingT, email string) {
	var token string
	//获取token
	if dbassRequestInfo.CommonEnv.TokenPath != "" {
		token = GetTokenFromJenkins(dbassRequestInfo.CommonEnv.TokenPath, email)
		dbassRequestInfo.Header = map[string]string{
			"Authorization": strings.Replace(token, "\n", "", -1),
		}
		dbassRequestInfo.Header = map[string]string{
			"Authorization":   token,
			"X-FUNCTION-TEST": "true",
		}
		l4g.Info("header is :%s", dbassRequestInfo.Header)
	} else {
		token = dbassRequestInfo.GetTokenByAPI(t, email)
		dbassRequestInfo.CommonEnv.Authorization = token
		dbassRequestInfo.Header = map[string]string{
			"Authorization":   fmt.Sprintf("Bearer %s", token),
			"X-FUNCTION-TEST": "true",
		}
		l4g.Info("header is :%s", dbassRequestInfo.Header)
	}

}

func GetTokenFromJenkins(tokenPath, email string) string {
	l4g.Info("SetHeaderByPath")
	// Remove spaces before and after the email
	email = strings.TrimSpace(email)
	l4g.Info("email is : [%s]", email)

	path := tokenPath + "/" + email
	data, err := ioutil.ReadFile(path)
	if err != nil {
		l4g.Info("read token file failed: %s", err)
		return ""
	}
	return string(data)
	// token_owner := "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlJEWTNNVUpGTUVReU9VVTBSa0U1TXpCRk5rRkRRVEl3UkRNMU0wSXpNelJHUVRrMVFUbEZRZyJ9.eyJodHRwczovL3RpZGJjbG91ZC5jb20vbGFzdF9wYXNzd29yZF9yZXNldCI6IjIwMjItMDYtMDdUMDU6NTU6MDEuNTY1WiIsImVtYWlsIjoibmVlYml2QHNuYXBtYWlsLmNjIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlzcyI6Imh0dHBzOi8vdGlkYi5hdXRoMC5jb20vIiwic3ViIjoiYXV0aDB8NjBkNDg1ODY2ZTMxODQwMDcwYmVlZTI3IiwiYXVkIjoiNkladGhDZm1SS0lQRW5RU1Q4YURidE03U0dkTW5pUmwiLCJpYXQiOjE2NTUyMDE4MzcsImV4cCI6MTY1NTIzNzgzNywiYXRfaGFzaCI6InVib29vZGJxMUZLNDR6dDZWdDhoTVEiLCJub25jZSI6Ii5kWjhwUV9MdUQ0OWttTko5eUc2OXZ2bGtsU0VoRUw3In0.vt-EMkRgHd0E_26OV8-dVNXJ2FerFZFh5x4myLwKO3UUN8v5zqcb-dKJiSg_0fuTG2rP7Oq5SaR_NKcqhfEDSL_pABE7gEqoBP-T4bWzYf2rbxjOwJzloMOMokM1Uyu_n5aDGmrOvriKXqZEIB4xWXdA9Zh04VICK2FRH0dF7PWgxQpZP7gOlAoErTkWcldSHNsbvTqPvSphBSvlTZQnoY7O2DkI20z8Zc-7_91IDV6HXmyDH390pNmipp0qH5KUxbcGDm2Zc8NO46K-m8x7srHCRm5jyPI9k-qJluwo73BkuDY1n6U91Mu_YLm4f0f9zsRJtC3wOaV2-ow59gv0lw"
	// token_audit := "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlJEWTNNVUpGTUVReU9VVTBSa0U1TXpCRk5rRkRRVEl3UkRNMU0wSXpNelJHUVRrMVFUbEZRZyJ9.eyJodHRwczovL3RpZGJjbG91ZC5jb20vbGFzdF9wYXNzd29yZF9yZXNldCI6IjIwMjItMDYtMDdUMDU6NTc6MzUuMDIzWiIsImVtYWlsIjoienV0Y2FqYUBzbmFwbWFpbC5jYyIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJpc3MiOiJodHRwczovL3RpZGIuYXV0aDAuY29tLyIsInN1YiI6ImF1dGgwfDYyNjdjODZlMzZlYmYyMDA2OWM5NDk0ZiIsImF1ZCI6IjZJWnRoQ2ZtUktJUEVuUVNUOGFEYnRNN1NHZE1uaVJsIiwiaWF0IjoxNjU1MjA1NzUzLCJleHAiOjE2NTUyNDE3NTMsImF0X2hhc2giOiJ5OFRPTFk5T0pMMVU0NmE1WkJyNjF3Iiwibm9uY2UiOiJxNHpJRllNQ2IweWIwenhjZkE4YVNYbnpHajBWRGZSNyJ9.OPJ9r47cPBZQIwf8d_WpyEeLEPcTZ5RimuXlY7RodHHr9qcMCobUvB-rX_jJupIIIdU1PpEh8vEseghGZTikNF-N8L3cambIrIUb_zxTEZzZAiRsh5ib7zXLGRSQBk5whIvu9Dzrix-iV9xtMi0kSLGKX72D1cFDGbqi0R9t_4EqOVxP8bxVxzBlNhknRc8GExmAV4OFpUpDE4TN1yHCWKhh_i0_MYYC3i7glYsrorRRZjfCJj4qJDUlbjpTI6zOJ2CO4KxwZVyZpg-jub8u9BwjyuOi8njAg-J2l9mYI5nGfLW4FOvIyhO9_UG8hlUetZN3LaQe1bJL7oOPvGsr4Q"
	// if email == "neebiv@snapmail.cc" {
	// 	return token_owner
	// } else if email == "zutcaja@snapmail.cc" {
	// 	return token_audit
	// }
	// return ""
}

func (dbassRequestInfo *DbassRequestInfo) SetHeaderByPath() {
	//获取token
	token := GetTokenFromJenkins(dbassRequestInfo.CommonEnv.TokenPath, dbassRequestInfo.CommonEnv.Email)
	dbassRequestInfo.Header = map[string]string{
		"Authorization": strings.Replace(token, "\n", "", -1),
	}
	dbassRequestInfo.Header = map[string]string{
		"Authorization":   token,
		"X-FUNCTION-TEST": "true",
	}
	//l4g.Info("header is :%s", dbassRequestInfo.Header)
	if dbassRequestInfo.CommonEnv.RecievePasswordEmail != "" {
		//token = GetTokenFromJenkins(dbassRequestInfo.CommonEnv.TokenPath, dbassRequestInfo.CommonEnv.RecievePasswordEmail)
		dbassRequestInfo.RecievePasswordHeader = map[string]string{
			"Authorization": strings.Replace(token, "\n", "", -1),
		}
	}

}

func (dbassRequestInfo *DbassRequestInfo) DbassRequest(t assert.TestingT, urlName string, body interface{},
	pathparam map[string]string, queryparam interface{}, header map[string]string, opts ...HTTPConfigOpt) *httpexpect.Response {
	var urlPath, method string
	var url []string
	var resp *httpexpect.Response
	if header == nil {
		header = dbassRequestInfo.Header
	}

	for _, v := range dbassRequestInfo.UrlInfos {
		if v.UrlName == urlName {
			urlPath = strings.Trim(v.UrlPath, " ")
			method = strings.Trim(v.UrlMethod, " ")
			//respStatus = v.UrlRespStatus
			break
		}
	}
	l4g.Info("---------  [%s]  DbassRequest Begin  ----------", urlName)
	// l4g.Info(`Begin to do DbassRequest :
	// 			[ urlName is : [ %s ] ],
	// 			[ urlPath is : [ %s ] ],
	// 			[ method is : [ %s ] ] `, urlName, urlPath, method)
	// return 400 response directly if url is "validateBankCard"
	if urlName == "validateBankCard" {
		url = []string{dbassRequestInfo.Host, urlPath}
		resp = PutForm(t, url, header, body, pathparam)
		return resp
	}
	if urlName == "addBankCard" {
		url = []string{dbassRequestInfo.CommonEnv.AddBankUrl, urlPath}

		resp = PostForm(t, url, nil, body, pathparam)
	} else {
		if urlName == "visitAuthenticate" || urlName == "visitAuthorize" {
			url = []string{fmt.Sprintf("https://%s", dbassRequestInfo.CommonEnv.Auth0), urlPath}
		} else if urlName == "getChangefeedMetric" {
			observabilityHost := getObservabilityHost(pathparam)
			url = []string{fmt.Sprintf("https://%s", observabilityHost), urlPath}
		} else {
			url = []string{dbassRequestInfo.Host, urlPath}
		}
		for retry := 0; retry < 1; retry++ {
			switch method {
			case "POST":
				resp = Post(t, url, header, body, pathparam, queryparam, opts...)
			case "GET":
				resp = Get(t, url, header, pathparam, queryparam, opts...)
			case "PUT":
				resp = Put(t, url, header, body, pathparam, queryparam, opts...)
			case "PATCH":
				resp = Patch(t, url, header, body, pathparam, opts...)
			case "DELETE":
				resp = Delete(t, url, header, pathparam, queryparam, opts...)
			}
			l4g.Info(`Do DbassRequest : 
			[ url is : [ %s ] ],
			[ Method is : [ %s ] ], 
			[ PathParam is : [ %+v ] ], 
			[ QueryParam is : [ %+v ] ], 
			[ Body is : [ %+v ] ], 
			[ Status Code is : [ %d ] ], 
			[ Trace is : [ %s ] ],
			[ Full resp is : [ %s ] ] `, url, method, pathparam, queryparam, body, resp.Raw().StatusCode, resp.Header("X-Debug-Trace-Id").Raw(), resp.Body().Raw())
			if resp.Raw().StatusCode == StatusOK || resp.Raw().StatusCode == 302 || resp.Raw().StatusCode == 202 {
				break
			}
			time.Sleep(time.Second * 1)
		}
	}

	l4g.Info("---------  [%s]  DbassRequest End  ----------", urlName)

	// l4g.Info("end to DbassRequest, urlName: %s, urlPath:%s, method:%s, resp:%s", urlName, urlPath, method, resp)
	return resp

}

func getObservabilityHost(pathParams map[string]string) string {
	l4g.Info("pathParams:%v", pathParams)
	projectID, ok := pathParams["project_id"]
	if ok {
		if projectID == "3199745" || projectID == "1369847559694038290" {
			// for dataflow new arch test project in dev and staging, use the staging o11y host
			l4g.Info("url:www.ds.us-west-2.aws.observability-staging.pingcap.cloud")
			return "www.ds.us-west-2.aws.observability-staging.pingcap.cloud"
		}
		l4g.Info("url:%s", dbassRequestInfo.CommonEnv.ObservabilityHost)
		return dbassRequestInfo.CommonEnv.ObservabilityHost
	} else {
		l4g.Error("can not get project_id key")
		return ""
	}

}

//DbassRequestInfo single instance.

var (
	GlobalDbassRequest *DbassRequestInfo
)

func GetDbassRequestInfo(t assert.TestingT) *DbassRequestInfo {
	if GlobalDbassRequest == nil {

		GlobalDbassRequest = NewDbassRequestInfo(t)
	}
	return GlobalDbassRequest
}

type HttpInfo struct {
	RequestMethod string
	RequestUrl    string
	RequestBody   string

	RespStatusCode int
	RespBody       string
}

type HttpInfoOption struct {
	requestBody interface{}
}

type HttpInfoOptionOp func(option *HttpInfoOption)

func NewHttpInfoOption(ops []HttpInfoOptionOp) *HttpInfoOption {
	opt := &HttpInfoOption{}
	for _, op := range ops {
		op(opt)
	}
	return opt
}

func ReqBodyOp(body interface{}) HttpInfoOptionOp {
	return func(option *HttpInfoOption) {
		if body != nil {
			option.requestBody = body
		}
	}
}

func ParseHttpInfo(resp *httpexpect.Response, ops ...HttpInfoOptionOp) *HttpInfo {
	if resp == nil {
		return &HttpInfo{}
	}
	httpInfo := &HttpInfo{}
	opt := NewHttpInfoOption(ops)
	httpInfo.RequestBody = parseReqBody(opt)

	if resp.Raw() != nil && resp.Raw().Request != nil {
		httpInfo.RequestMethod = resp.Raw().Request.Method
		if resp.Raw().Request.URL != nil {
			httpInfo.RequestUrl = resp.Raw().Request.URL.String()
		}
	}

	if resp.Body() != nil {
		httpInfo.RespBody = resp.Body().Raw()
		httpInfo.RespStatusCode = resp.Raw().StatusCode
	}
	return httpInfo
}

func parseReqBody(opt *HttpInfoOption) string {
	if opt.requestBody == nil {
		return ""
	}
	body, err := json.Marshal(opt.requestBody)
	if err != nil {
		l4g.Error("marshal request body error!", err)
		return ""
	}
	return string(body)
}

func (h *HttpInfo) ToString() string {
	bytes, err := json.Marshal(h)
	if err != nil {
		return ""
	}
	return string(bytes)
}

func (h *HttpInfo) UnmarshalReqBody(body interface{}) {
	if h.RequestBody == "" {
		return
	}
	err := json.Unmarshal([]byte(h.RequestBody), body)
	if err != nil {
		l4g.Error("unmarshal request body error!", err)
	}
}

func (h *HttpInfo) UnmarshalRespBody(body interface{}) {
	if h.RespBody == "" {
		return
	}
	err := json.Unmarshal([]byte(h.RespBody), body)
	if err != nil {
		l4g.Error("unmarshal resp body error!", err)
	}
}

// add digest auth , you can use  digest, err := GetDigestAuth() to get digest auth, and then add it to header
func GetDigestAuth(username, password, method, baseUrl, api string, pathParam, param map[string]string) (string, error) {
	if username == "" || password == "" {
		l4g.Info("username is:%v,password is %v .", username, password)
		return "", errors.New("wrong ak/sk")
	}

	if pathParam != nil {
		for k, v := range pathParam {
			api = strings.Replace(api, "{"+k+"}", v, 1)
		}
	}

	if param != nil {
		temp := ""
		for k, v := range param {
			if temp == "" {
				temp = k + "=" + v
			} else {
				temp = temp + "&" + k + "=" + v
			}
		}
		api = api + "?" + temp
	}

	// get a new digest auth request
	dr := NewRequest(username, password, method, baseUrl+api, "")
	digest_auth, err := dr.Execute()
	if err != nil {
		fmt.Printf("err in digest request is %v", err)
	}
	// set the auth header of req
	l4g.Info("digest_auth is %v", digest_auth)
	return digest_auth, nil
}

const (
	algorithmMD5        = "MD5"
	algorithmMD5Sess    = "MD5-SESS"
	algorithmSHA256     = "SHA-256"
	algorithmSHA256Sess = "SHA-256-SESS"
)

// use to generate authorization
type authorization struct {
	Algorithm string // unquoted
	Cnonce    string // quoted
	Nc        int    // unquoted
	Nonce     string // quoted
	Opaque    string // quoted
	Qop       string // unquoted
	Realm     string // quoted
	Response  string // quoted
	URI       string // quoted
	Userhash  bool   // quoted
	Username  string // quoted
	Username_ string // quoted
}

// use to cal md5
type wwwAuthenticate struct {
	Algorithm string // unquoted
	Domain    string // quoted
	Nonce     string // quoted
	Opaque    string // quoted
	Qop       string // quoted
	Realm     string // quoted
	Stale     bool   // unquoted
	Charset   string // quoted
	Userhash  bool   // quoted
}

// make a digest request to get www-auth
type DigestRequest struct {
	Body       string
	Method     string
	Password   string
	URI        string
	Username   string
	Header     http.Header
	Auth       *authorization
	Wa         *wwwAuthenticate
	CertVal    bool
	HTTPClient *http.Client
}

func NewRequest(username, password, method, uri, body string) DigestRequest {
	dr := DigestRequest{}
	dr.UpdateRequest(username, password, method, uri, body)
	dr.CertVal = true
	return dr
}

func (dr *DigestRequest) UpdateRequest(username, password, method, uri, body string) *DigestRequest {
	dr.Body = body
	dr.Method = method
	dr.Password = password
	dr.URI = uri
	dr.Username = username
	dr.Header = make(map[string][]string)
	return dr
}
func (dr *DigestRequest) getHTTPClient() *http.Client {
	if dr.HTTPClient != nil {
		return dr.HTTPClient
	}
	tlsConfig := tls.Config{}
	if !dr.CertVal {
		tlsConfig.InsecureSkipVerify = true
	}

	return &http.Client{
		Timeout: 30 * time.Second,
		Transport: &http.Transport{
			TLSClientConfig: &tlsConfig,
		},
	}
}

func newAuthorization(dr *DigestRequest) (*authorization, error) {

	ah := authorization{
		Algorithm: dr.Wa.Algorithm,
		Cnonce:    "",
		Nc:        0,
		Nonce:     dr.Wa.Nonce,
		Opaque:    dr.Wa.Opaque,
		Qop:       "",
		Realm:     dr.Wa.Realm,
		Response:  "",
		URI:       "",
		Userhash:  dr.Wa.Userhash,
		Username:  "",
		Username_: "", // TODO
	}

	return ah.refreshAuthorization(dr)
}

func (ah *authorization) refreshAuthorization(dr *DigestRequest) (*authorization, error) {

	ah.Username = dr.Username

	if ah.Userhash {
		ah.Username = ah.hash(fmt.Sprintf("%s:%s", ah.Username, ah.Realm))
	}

	ah.Nc++

	ah.Cnonce = ah.hash(fmt.Sprintf("%d:%s:my_value", time.Now().UnixNano(), dr.Username))

	url, err := url.Parse(dr.URI)
	if err != nil {
		return nil, err
	}

	ah.URI = url.RequestURI()
	ah.Response = ah.computeResponse(dr)

	return ah, nil
}

// compute md5
func (ah *authorization) computeResponse(dr *DigestRequest) (s string) {

	kdSecret := ah.hash(ah.computeA1(dr))
	kdData := fmt.Sprintf("%s:%08x:%s:%s:%s", ah.Nonce, ah.Nc, ah.Cnonce, ah.Qop, ah.hash(ah.computeA2(dr)))

	return ah.hash(fmt.Sprintf("%s:%s", kdSecret, kdData))
}

func (ah *authorization) computeA1(dr *DigestRequest) string {

	algorithm := strings.ToUpper(ah.Algorithm)

	if algorithm == "" || algorithm == algorithmMD5 || algorithm == algorithmSHA256 {
		return fmt.Sprintf("%s:%s:%s", ah.Username, ah.Realm, dr.Password)
	}

	if algorithm == algorithmMD5Sess || algorithm == algorithmSHA256Sess {
		upHash := ah.hash(fmt.Sprintf("%s:%s:%s", ah.Username, ah.Realm, dr.Password))
		return fmt.Sprintf("%s:%s:%s", upHash, ah.Nonce, ah.Cnonce)
	}

	return ""
}

func (ah *authorization) computeA2(dr *DigestRequest) string {

	if strings.Contains(dr.Wa.Qop, "auth-int") {
		ah.Qop = "auth-int"
		return fmt.Sprintf("%s:%s:%s", dr.Method, ah.URI, ah.hash(dr.Body))
	}

	if dr.Wa.Qop == "auth" || dr.Wa.Qop == "" {
		ah.Qop = "auth"
		return fmt.Sprintf("%s:%s", dr.Method, ah.URI)
	}

	return ""
}

func (ah *authorization) hash(a string) string {
	var h hash.Hash
	algorithm := strings.ToUpper(ah.Algorithm)

	if algorithm == "" || algorithm == algorithmMD5 || algorithm == algorithmMD5Sess {
		h = md5.New()
	} else if algorithm == algorithmSHA256 || algorithm == algorithmSHA256Sess {
		h = sha256.New()
	} else {
		// unknown algorithm
		return ""
	}

	io.WriteString(h, a)
	return hex.EncodeToString(h.Sum(nil))
}

// turn a auth struct to string
func (ah *authorization) toString() string {
	var buffer bytes.Buffer

	buffer.WriteString("Digest ")

	if ah.Username != "" {
		buffer.WriteString(fmt.Sprintf("username=\"%s\", ", ah.Username))
	}

	if ah.Realm != "" {
		buffer.WriteString(fmt.Sprintf("realm=\"%s\", ", ah.Realm))
	}

	if ah.Nonce != "" {
		buffer.WriteString(fmt.Sprintf("nonce=\"%s\", ", ah.Nonce))
	}

	if ah.URI != "" {
		buffer.WriteString(fmt.Sprintf("uri=\"%s\", ", ah.URI))
	}

	if ah.Response != "" {
		buffer.WriteString(fmt.Sprintf("response=\"%s\", ", ah.Response))
	}

	if ah.Algorithm != "" {
		buffer.WriteString(fmt.Sprintf("algorithm=%s, ", ah.Algorithm))
	}

	if ah.Cnonce != "" {
		buffer.WriteString(fmt.Sprintf("cnonce=\"%s\", ", ah.Cnonce))
	}

	if ah.Opaque != "" {
		buffer.WriteString(fmt.Sprintf("opaque=\"%s\", ", ah.Opaque))
	}

	if ah.Qop != "" {
		buffer.WriteString(fmt.Sprintf("qop=%s, ", ah.Qop))
	}

	if ah.Nc != 0 {
		buffer.WriteString(fmt.Sprintf("nc=%08x, ", ah.Nc))
	}

	if ah.Userhash {
		buffer.WriteString("userhash=true, ")
	}

	s := buffer.String()

	return strings.TrimSuffix(s, ", ")
}

// get a www-auth struct from resp's header
func newWwwAuthenticate(s string) *wwwAuthenticate {

	var wa = wwwAuthenticate{}

	algorithmRegex := regexp.MustCompile(`algorithm="([^ ,]+)"`)
	algorithmMatch := algorithmRegex.FindStringSubmatch(s)
	if algorithmMatch != nil {
		wa.Algorithm = algorithmMatch[1]
	}

	domainRegex := regexp.MustCompile(`domain="(.+?)"`)
	domainMatch := domainRegex.FindStringSubmatch(s)
	if domainMatch != nil {
		wa.Domain = domainMatch[1]
	}

	nonceRegex := regexp.MustCompile(`nonce="(.+?)"`)
	nonceMatch := nonceRegex.FindStringSubmatch(s)
	if nonceMatch != nil {
		wa.Nonce = nonceMatch[1]
	}

	opaqueRegex := regexp.MustCompile(`opaque="(.+?)"`)
	opaqueMatch := opaqueRegex.FindStringSubmatch(s)
	if opaqueMatch != nil {
		wa.Opaque = opaqueMatch[1]
	}

	qopRegex := regexp.MustCompile(`qop="(.+?)"`)
	qopMatch := qopRegex.FindStringSubmatch(s)
	if qopMatch != nil {
		wa.Qop = qopMatch[1]
	}

	realmRegex := regexp.MustCompile(`realm="(.+?)"`)
	realmMatch := realmRegex.FindStringSubmatch(s)
	if realmMatch != nil {
		wa.Realm = realmMatch[1]
	}

	staleRegex := regexp.MustCompile(`stale=([^ ,])"`)
	staleMatch := staleRegex.FindStringSubmatch(s)
	if staleMatch != nil {
		wa.Stale = (strings.ToLower(staleMatch[1]) == "true")
	}

	charsetRegex := regexp.MustCompile(`charset="(.+?)"`)
	charsetMatch := charsetRegex.FindStringSubmatch(s)
	if charsetMatch != nil {
		wa.Charset = charsetMatch[1]
	}

	userhashRegex := regexp.MustCompile(`userhash=([^ ,])"`)
	userhashMatch := userhashRegex.FindStringSubmatch(s)
	if userhashMatch != nil {
		wa.Userhash = (strings.ToLower(userhashMatch[1]) == "true")
	}

	return &wa
}

func (dr *DigestRequest) Execute() (string, error) {

	req, err := http.NewRequest(dr.Method, dr.URI, bytes.NewReader([]byte(dr.Body)))

	if err != nil {
		return "", err
	}
	req.Header = dr.Header

	client := dr.getHTTPClient()
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}

	if resp.StatusCode == 401 {
		return dr.getNewDigest(resp)
	}

	// return the resp to user to handle resp.body.Close()
	return "", nil
}

// to get a new auth from old 401 resp
func (dr *DigestRequest) getNewDigest(resp *http.Response) (string, error) {
	var (
		auth     *authorization
		wa       *wwwAuthenticate
		waString string
	)

	// body not required for authentication, closing
	resp.Body.Close()

	if waString = resp.Header.Get("WWW-Authenticate"); waString == "" {
		return "", fmt.Errorf("failed to get WWW-Authenticate header, please check your server configuration")
	}
	wa = newWwwAuthenticate(waString)
	dr.Wa = wa
	auth, err := newAuthorization(dr)
	if err != nil {
		return "", err
	}

	return auth.toString(), nil
}
