package common

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/ajg/form"
	l4g "github.com/alecthomas/log4go"
	"github.com/dailymotion/allure-go"
	"github.com/stretchr/testify/assert"
)

func CheckServerlessMonitoringMetric(t assert.TestingT, env, provider, region, clusterId string, timeRange int64, m MetricQuery, expectValue float64) {
	l4g.Info("Try to check monitoring data from o11y.")

	dbassRequestInfo = GetDbassRequestInfo(t)
	orgId := dbassRequestInfo.CommonEnv.OrgId
	projectId := dbassRequestInfo.CommonEnv.ProjectId
	// set bizType
	var bizType string
	if env == "staging" {
		bizType = "staging-devtier"
	} else if env == "dev" {
		bizType = "dev-devtier"
	} else {
		bizType = "prod-devtier"
	}
	// check metrics
	pathParam := map[string]string{"biz_type": bizType, "cluster_id": clusterId, "project_id": projectId, "org_id": orgId}
	endTime := time.Now().Unix()
	startTime := endTime - timeRange - 30
	datapoint := int(timeRange / 30)

	defer dbassRequestInfo.ResetHost("", provider, region)
	dbassRequestInfo.ResetHost("o11yRegional", provider, region)

	// query metrics.
	param := map[string]string{"start": strconv.FormatInt(startTime, 10), "end": strconv.FormatInt(endTime, 10), "step": "30", "query": m.query}
	metricsResp := dbassRequestInfo.DbassRequest(t, "getO11yMetric", nil, pathParam, param, nil)
	l4g.Info("get metric pathparam is %s, status code is %s", param, metricsResp.Raw().StatusCode)
	l4g.Info("metric response is %s", metricsResp.JSON().Raw())
	if len(metricsResp.JSON().Path("$.data.result").Array().Raw()) == 0 {
		allure.Step(
			allure.Story("CheckQueryClusterMonitoring"),
			allure.Description(fmt.Sprintf("get metric pathparam is %s, status code is %d, result is empty", param, metricsResp.Raw().StatusCode)),
			allure.Action(func() {
				l4g.Info("this metric %s result is empty!", m.query)
			}))
		return
	}
	allure.Step(
		allure.Story("CheckQueryClusterMonitoring"),
		allure.Description(fmt.Sprintf("get metric pathparam is %s, status code is %d", param, metricsResp.Raw().StatusCode)),
		allure.Action(func() {
			metricsResp.Status(StatusOK)
			l4g.Info("To check metric data of query: %s ", m.query)
			values := metricsResp.JSON().Path("$.data.result").Array().First().Path("$.values").Array()
			if m.hasData == "1" {
				values.Length().Ge(datapoint)
			}

			flag := false
			for _, value := range values.Iter() {
				actualValue, err := strconv.ParseFloat(value.Array().Last().String().Raw(), 64)
				if err != nil {
					return
				}
				l4g.Info("expect value is %f, actual value is %f", expectValue, actualValue)
				if actualValue >= expectValue {
					flag = true
					break
				}
			}
			assert.True(t, flag)
		}))
}

type credential struct {
	AccessKeyId     string `json:"AccessKeyId"`
	SecretAccessKey string `json:"SecretAccessKey"`
	SessionToken    string `json:"SessionToken"`
	Expiration      string `json:"Expiration"`
}

type user struct {
	AssumedRoleId string `json:"AssumedRoleId"`
	Arn           string `json:"Arn"`
}

type awsToken struct {
	Credentials     credential `json:"Credentials"`
	AssumedRoleUser user       `json:"AssumedRoleUser"`
}

func SetupEksProfile(t assert.TestingT, awsAccount, role string) {
	if role == "" {
		role = "full-manager-service-role"
	}
	// get jump server config
	dbassRequestInfo = GetDbassRequestInfo(t)
	loginUser := dbassRequestInfo.CommonEnv.LoginUser
	ec2Host := dbassRequestInfo.CommonEnv.Ec2Host
	cerPath := dbassRequestInfo.CommonEnv.CerPath
	l4g.Info("jump server: %s, %s, %s", loginUser, ec2Host, cerPath)

	var cmd string
	if strings.Contains(awsAccount, "676546228356") {
		cmd = fmt.Sprintf("aws sts assume-role --role-arn arn:aws:iam::676546228356:role/%s --role-session-name eks", role)
	}
	if strings.Contains(awsAccount, "385595570414") {
		cmd = fmt.Sprintf("aws sts assume-role --role-arn arn:aws:iam::385595570414:role/%s --role-session-name eks", role)
	}
	result, isOK := ConnectInstance(ec2Host, cerPath, loginUser, cmd)
	if isOK {
		result = strings.Replace(result, "\n", "", -1)

	}
	var token awsToken
	err := json.Unmarshal([]byte(result), &token)
	if err != nil {
		return
	}

	// set aws credential
	cmd = fmt.Sprintf("echo \"[eks]\naws_access_key_id=%s\naws_secret_access_key=%s\naws_session_token=%s\" > ~/.aws/credentials", token.Credentials.AccessKeyId, token.Credentials.SecretAccessKey, token.Credentials.SessionToken)
	ConnectInstance(ec2Host, cerPath, loginUser, cmd)
}

type Auth0Resp struct {
	AccessToken string `json:"access_token" form:"access_token"`
}

func PrepareToken(t assert.TestingT) string {
	dbassRequestInfo = GetDbassRequestInfo(t)
	granType := dbassRequestInfo.CommonEnv.GrantType
	clientId := dbassRequestInfo.CommonEnv.O11yClientId
	clientSecret := dbassRequestInfo.CommonEnv.ClientSecret
	audience := dbassRequestInfo.CommonEnv.Audience

	authReq := map[string]string{"grant_type": granType, "client_id": clientId, "client_secret": clientSecret, "audience": audience}
	body, err := form.EncodeToString(authReq)

	if err != nil {
		l4g.Error("get token failed")
		return ""
	}
	resp, err := http.Post("https://tidb-soc2.us.auth0.com/oauth/token",
		"application/x-www-form-urlencoded",
		strings.NewReader(body))
	if err != nil {
		l4g.Error("get token failed")
		return ""
	}
	respContent, err := io.ReadAll(resp.Body)
	if err != nil {
		l4g.Error("get token failed")
		return ""
	}
	auth0Resp := &Auth0Resp{}
	err = json.Unmarshal(respContent, auth0Resp)
	if err != nil {
		l4g.Error("get token failed")
		return ""
	}
	token := fmt.Sprintf("Bearer %s", auth0Resp.AccessToken)
	l4g.Info("The auth token is:", token)
	return token
}

func CheckServerlessSlowQuery(t assert.TestingT, clusterId, sessionId string) {
	// get config
	if clusterId == "" {
		return
	}
	dbassRequestInfo = GetDbassRequestInfo(t)
	orgId := dbassRequestInfo.CommonEnv.OrgId
	projectId := dbassRequestInfo.CommonEnv.ProjectId
	region := dbassRequestInfo.CommonEnv.DevTierRegion
	header := dbassRequestInfo.Header
	header["x-provider"] = "aws"
	header["x-region"] = region
	header["x-deploy-type"] = "shared"
	header["Content-Type"] = "application/json"

	pathParam := map[string]string{"org_id": orgId, "project_id": projectId, "cluster_id": clusterId}

	// make slow query
	db, table := GetMostCountDBAndTable(t, clusterId, sessionId)
	sql := fmt.Sprintf("select * from %s.%s;", db, table)
	body := map[string]string{"sql": sql, "sessionid": sessionId}
	runSqlResp := dbassRequestInfo.DbassRequest(t, "executeSql", body, pathParam, nil, nil)
	l4g.Info("run sql command, path param is %s, body is %s, status code is %d, response is %s", pathParam, body, runSqlResp.Raw().StatusCode, runSqlResp.JSON())
	//allure.Step(
	//	allure.Story("CheckServerlessSlowQuery"),
	//	allure.Description(fmt.Sprintf("run sql, path param is %s, body is"+
	//		" %s, status code is %d", pathParam, body, runSqlResp.Raw().StatusCode)),
	//	allure.Action(func() {
	//		runSqlResp.Status(StatusOK)
	//		runSqlResp.JSON().Path("$.code").Equal(200)
	//	}))

	// check slow query
	time.Sleep(time.Minute * 4)
	endTime := time.Now().Unix()
	startTime := endTime - 30*60
	pathParam = map[string]string{"org_id": orgId, "project_id": projectId, "cluster_id": clusterId}
	queryParam := map[string]string{"begin_time": strconv.FormatInt(startTime, 10), "end_time": strconv.FormatInt(endTime, 10), "desc": "true", "fields": "query,timestamp,query_time,memory_max", "limit": "100", "orderBy": "timestamp"}
	slowQueryResp := dbassRequestInfo.DbassRequest(t, "getServerlessSlowQuery", nil, pathParam, queryParam, header)
	l4g.Info("get slow query response is: %s", slowQueryResp.Body())
	flag := false
	checksql := fmt.Sprintf("select * from `%s` . `%s` ;", db, table)
	for _, digest := range slowQueryResp.JSON().Array().Iter() {
		l4g.Info("get slow query: %s", digest.Object().Value("query").Raw())
		if digest.Object().Value("query").Raw() == checksql {
			flag = true
		}
	}
	allure.Step(
		allure.Story("CheckSlowQuery"),
		allure.Description(fmt.Sprintf("check sql: %s, flag is %t", checksql, flag)),
		allure.Action(func() {
			l4g.Info("check sql: %s, flag is %t", checksql, flag)
		}))
	allure.Step(
		allure.Story("CheckSlowQuery"),
		allure.Description(fmt.Sprintf("get slow query param is %s, status code is %d", queryParam, slowQueryResp.Raw().StatusCode)),
		allure.Action(func() {
			slowQueryResp.Status(StatusOK)
			slowQueryResp.JSON().Array().Length().Ge(0)
			//		assert.Equal(t, true, flag)
		}))
}
