package webScene

import (
	"bufio"
	"encoding/csv"
	"fmt"
	l4g "github.com/alecthomas/log4go"
	"github.com/gavv/httpexpect/v2"
	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/qa-gourd/ProductModel"
	"io"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
)

const (
	globalServer   = "https://www.gs.us-west-2.aws.observability.tidbcloud.com"
	regionalServer = "https://www.ds.region.observability.tidbcloud.com"
)

func (w *WebE2ECase) CheckClusterStatement() {
	// init config
	orgId := w.WebRequestInfo.User.OrgId
	projectId := w.WebRequestInfo.User.ProjectId
	clusterId := w.WebRequestInfo.Cluster.ClusterId
	endTime := time.Now().Unix()
	startTime := endTime - 5*60
	param := map[string]string{
		"begin_time": strconv.FormatInt(startTime, 10),
		"end_time":   strconv.FormatInt(endTime, 10),
		"fields":     "digest_text,sum_latency,avg_latency,max_latency,min_latency,exec_count,plan_count"}

	var getClusterStatementResp *httpexpect.Response
	// get cluster statement
	if w.WebRequestInfo.Cluster.IsServerless {
		param["text"] = ""
		region := w.WebRequestInfo.Cluster.Region
		header := w.WebRequestInfo.Header
		header["X-Provider"] = "aws"
		header["X-Region"] = region
		header["X-Deploy-Type"] = "shared"
		clusterPathParam := map[string]string{"org_id": orgId, "project_id": projectId, "cluster_id": clusterId}
		urlInfo := w.URLInfoMap["getServerlessStatement"]
		getClusterStatementResp = w.WebRequestInfo.DoWebAPIRequest(urlInfo.UrlPath, urlInfo.UrlMethod, nil, clusterPathParam, param)
	} else {
		clusterPathParam := map[string]string{"org_id": orgId, "cluster_id": clusterId}
		urlInfo := w.URLInfoMap["getClusterStatement"]
		getClusterStatementResp = w.WebRequestInfo.DoWebAPIRequest(urlInfo.UrlPath, urlInfo.UrlMethod, nil, clusterPathParam, param)
	}
	l4g.Info("get cluster statement response is %s", getClusterStatementResp.JSON().Raw())
	for _, state := range getClusterStatementResp.JSON().Array().Iter() {
		w.Reporter.StepReport(fmt.Sprintf("get cluster statement status code is %d", getClusterStatementResp.Raw().StatusCode),
			func() {
				getClusterStatementResp.Status(http.StatusOK)
				state.Object().Path("$.digest_text").String().Length().Gt(0)
				state.Object().Path("$.digest").String().Length().Gt(0)
				state.Object().Path("$.exec_count").Number().Gt(0)
				state.Object().Path("$.sum_latency").Number().Gt(0)
				state.Object().Path("$.max_latency").Number().Gt(0)
				state.Object().Path("$.min_latency").Number().Gt(0)
			})
	}
}

func (w *WebE2ECase) CheckClusterKeyVisual() {
	// init config
	orgId := w.WebRequestInfo.User.OrgId
	clusterId := w.WebRequestInfo.Cluster.ClusterId

	clusterPathParam := map[string]string{"org_id": orgId, "cluster_id": clusterId}
	endTime := time.Now().Unix()
	startTime := endTime - 5*60
	param := map[string]string{"starttime": strconv.FormatInt(startTime, 10), "type": "written_bytes"}

	// get cluster statement
	urlInfo := w.URLInfoMap["getClusterKeyVisual"]

	getClusterStatementResp := w.WebRequestInfo.DoWebAPIRequest(urlInfo.UrlPath, urlInfo.UrlMethod, nil, clusterPathParam, param)
	l4g.Info("get cluster key visual response is %s", getClusterStatementResp.JSON().Raw())

	w.Reporter.StepReport(fmt.Sprintf("get cluster key visual status code is %d", getClusterStatementResp.Raw().StatusCode),
		func() {
			getClusterStatementResp.Status(http.StatusOK)
			getClusterStatementResp.JSON().Path("$.timeAxis").Array().Length().Ge(5)
			getClusterStatementResp.JSON().Path("$.keyAxis").Array().Length().Ge(5)
			getClusterStatementResp.JSON().Path("$.data.written_bytes").Array().Length().Ge(5)
		})
}

func (w *WebE2ECase) CheckAlerts(timeRange int64) {
	// init config
	l4g.Info("Try to check alerts data from o11y.")
	orgId := w.WebRequestInfo.User.OrgId
	projectId := w.WebRequestInfo.User.ProjectId
	clusterId := w.WebRequestInfo.Cluster.ClusterId
	region := w.WebRequestInfo.Cluster.Region
	provider := w.WebRequestInfo.Cluster.Provider
	baseUrl := w.WebRequestInfo.BaseUrl
	// set bizType
	var bizType string
	if w.WebRequestInfo.Env == ProductModel.Staging {
		if w.WebRequestInfo.Cluster.IsServerless {
			bizType = "staging-devtier"
		} else {
			bizType = "staging-tidbcloud"
		}
	} else if w.WebRequestInfo.Env == ProductModel.Dev {
		if w.WebRequestInfo.Cluster.IsServerless {
			bizType = "dev-devtier"
		} else {
			bizType = "dev-tidbcloud"
		}
	} else {
		if w.WebRequestInfo.Cluster.IsServerless {
			bizType = "prod-devtier"
		} else {
			bizType = "tidbcloud"
		}
	}
	// 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
	w.WebRequestInfo.BaseUrl = globalServer
	l4g.Info("Reset host %s", w.WebRequestInfo.BaseUrl)

	// query alerts.
	param := map[string]string{
		"region": region,
		"vendor": provider,
		"start":  strconv.FormatInt(startTime, 10),
		"end":    strconv.FormatInt(endTime, 10),
		"limit":  "20",
		"page":   "1"}
	urlInfo := w.URLInfoMap["getAlerts"]
	alertsResp := w.WebRequestInfo.DoWebAPIRequest(urlInfo.UrlPath, urlInfo.UrlMethod, nil, pathParam, param)
	l4g.Info("get alert pathparam is %s, status code is %s", param, alertsResp.Raw().StatusCode)
	l4g.Info("metric response is %s", alertsResp.JSON().Raw())
	w.Reporter.StepReport(fmt.Sprintf("get alert pathparam is %s, status code is %d, result is empty", param, alertsResp.Raw().StatusCode),
		func() {
			alertsResp.Status(http.StatusOK)
			alertsResp.JSON().Path("$.code").Equal(0)
		})

	w.WebRequestInfo.BaseUrl = baseUrl
}

func (w *WebE2ECase) CheckEvents(timeRange int64) {
	// init config
	orgId := w.WebRequestInfo.User.OrgId
	projectId := w.WebRequestInfo.User.ProjectId
	clusterId := w.WebRequestInfo.Cluster.ClusterId

	clusterPathParam := map[string]string{"org_id": orgId, "project_id": projectId, "cluster_id": clusterId}
	clusterType := "dedicated"
	if w.WebRequestInfo.Cluster.IsServerless {
		clusterType = "serverless"
	}
	endTime := time.Now().Unix()
	startTime := endTime - timeRange - 30

	// query events.
	param := map[string]string{
		"begin_ts":     strconv.FormatInt(startTime, 10),
		"end_ts":       strconv.FormatInt(endTime, 10),
		"cluster_type": clusterType}

	// get cluster statement
	urlInfo := w.URLInfoMap["getEvents"]
	getClusterEventsResp := w.WebRequestInfo.DoWebAPIRequest(urlInfo.UrlPath, urlInfo.UrlMethod, nil, clusterPathParam, param)
	l4g.Info("get cluster event response is %s", getClusterEventsResp.JSON().Raw())

	w.Reporter.StepReport(fmt.Sprintf("get cluster events status code is %d", getClusterEventsResp.Raw().StatusCode),
		func() {
			getClusterEventsResp.Status(http.StatusOK)
			getClusterEventsResp.JSON().Path("$.base_resp.err_code").Equal("")
			getClusterEventsResp.JSON().Path("$.events").Array().Length().Gt(0)
		})
}

func (w *WebE2ECase) CheckMonitoringMetric(timeRange int64) {
	// init config
	l4g.Info("Try to check monitoring data from o11y.")
	orgId := w.WebRequestInfo.User.OrgId
	projectId := w.WebRequestInfo.User.ProjectId
	clusterId := w.WebRequestInfo.Cluster.ClusterId
	region := w.WebRequestInfo.Cluster.Region
	provider := w.WebRequestInfo.Cluster.Provider
	baseUrl := w.WebRequestInfo.BaseUrl
	var metrics []MetricQuery
	if w.WebRequestInfo.Cluster.IsServerless {
		metrics = GetMetricQuery("serverlessClusterMetrics.csv", true)
	} else {
		metrics = GetMetricQuery("dedicatedClusterMetrics.csv", false)
	}
	// set bizType
	var bizType string
	if w.WebRequestInfo.Env == ProductModel.Staging {
		if w.WebRequestInfo.Cluster.IsServerless {
			bizType = "staging-devtier"
		} else {
			bizType = "staging-tidbcloud"
		}
	} else if w.WebRequestInfo.Env == ProductModel.Dev {
		if w.WebRequestInfo.Cluster.IsServerless {
			bizType = "dev-devtier"
		} else {
			bizType = "dev-tidbcloud"
		}
	} else {
		if w.WebRequestInfo.Cluster.IsServerless {
			bizType = "prod-devtier"
		} else {
			bizType = "tidbcloud"
		}
	}
	// 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
	w.WebRequestInfo.BaseUrl = strings.Replace(regionalServer, "region", region+"."+provider, -1)
	l4g.Info("Reset host %s", w.WebRequestInfo.BaseUrl)

	urlInfo := w.URLInfoMap["getO11yMetric"]
	for _, m := range metrics {
		if strings.Contains(m.query, "tiflash") && w.WebRequestInfo.Cluster.TiFlashNodeCount == 0 {
			l4g.Info(fmt.Sprintf("The cluster has no tiflash, ignore metric %s", m.query))
			continue
		}

		// query metrics.
		param := map[string]string{"start": strconv.FormatInt(startTime, 10), "end": strconv.FormatInt(endTime, 10), "step": "30", "query": m.query}
		metricsResp := w.WebRequestInfo.DoWebAPIRequest(urlInfo.UrlPath, urlInfo.UrlMethod, nil, pathParam, param)
		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 {
			w.Reporter.StepReport(fmt.Sprintf("get metric pathparam is %s, status code is %d, result is empty", param, metricsResp.Raw().StatusCode),
				func() {
					l4g.Info("this metric %s result is empty!", m.query)
				})
			continue
		}
		w.Reporter.StepReport(fmt.Sprintf("get metric pathparam is %s, status code is %d", param, metricsResp.Raw().StatusCode),
			func() {
				metricsResp.Status(http.StatusOK)
				l4g.Info("To check metric data of query: %s ", m.query)
				values := metricsResp.JSON().Path("$.data.result").Array().First().Path("$.values").Array()
				expectValue, err := strconv.ParseFloat(m.Value, 64)
				if err != nil {
					return
				}
				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 %s, actual value is %s", expectValue, actualValue)
					if actualValue >= expectValue {
						flag = true
						break
					}
				}
				assert.True(w.Reporter.T, flag)
			})
	}
	w.WebRequestInfo.BaseUrl = baseUrl
}

type MetricQuery struct {
	Desc    string
	Key     string
	query   string
	hasData string
	Value   string
}

func GetMetricQuery(fileName string, isServerless bool) []MetricQuery {
	var Queries []MetricQuery
	filePath := fmt.Sprintf("../conf/%s", fileName)
	csvFile, _ := os.Open(filePath)
	reader := csv.NewReader(bufio.NewReader(csvFile))
	reader.Comma = ';'

	for {
		line, error := reader.Read()
		if error == io.EOF {
			break
		} else if error != nil {
			l4g.Error(error)
			return Queries
		}
		if isServerless {
			Queries = append(Queries, MetricQuery{
				Desc:    line[0],
				Key:     line[1],
				query:   line[2],
				hasData: line[3],
				Value:   line[4]})
		} else {
			Queries = append(Queries, MetricQuery{
				Desc:    "",
				Key:     "",
				query:   line[0],
				hasData: line[1],
				Value:   line[2]})
		}

	}
	l4g.Info("Has load metrics success!")

	return Queries
}
