package controller

import (
	"context"
	"dataservices/commons/ginparser"
	"dataservices/commons/utils"
	dto "dataservices/internal/dto/dataapi"
	cdto "dataservices/internal/dto/dataapi_control"
	service "dataservices/internal/service/central"
	"fmt"
	"math"
	"sort"
	"strconv"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
	pmodel "github.com/prometheus/common/model"
	errors "github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

func ensureDataAppMetricsSupported(ctx context.Context, appID uint64) error {
	// Global fast-fail keeps unsupported AliCloud metrics requests out of regional control.
	return service.GuardRegionalCapabilityForDataApp(ctx, appID, service.RegionalCapabilityDataAppMetrics)
}

// Query DataApp misc statistic
// @Summary Query DataApp misc statistic
// @Description Query DataApp misc statistic
// @Tags dataapi
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param app_id path string true "app ID"
// @Param from query int true "query start timestamp"
// @Param to query int true "query end timestamp"
// @Produce json
// @Success 200 {object} dto.DataAppMiscStatusRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/metrics/misc_statistic [get]
func GetDataAppMiscStatistic(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_APP_ID}
	params := ginparser.NewCommonParamsByGin(c)
	validateErr := params.ValidateUrl(validateKeys)
	createdBy, _ := params.GetUserEmail()
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/metrics/misc_statistic[get]-GetDataAppMiscStatistic param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}
	if err := ensureDataAppMetricsSupported(ctx, params.AppID); err != nil {
		c.Error(err)
		return
	}
	extId, regions, err := service.GetDataAppExtIdAndRegions(ctx, params.AppID)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/metrics/misc_statistic[get]-GetDataAppMiscStatistic getDataAppExtIdAndRegions fail!", zap.Error(err), zap.String("user", createdBy))
		c.Error(errors.NewInternalServerError("internal error", nil))
		return
	}
	var wg sync.WaitGroup
	var (
		drafed, pubed int64
		err1          error
	)
	wg.Add(1)
	utils.GO(func() {
		defer wg.Done()
		drafed, pubed, err1 = service.GetDataAppEndpointsStatic(ctx, params.AppID)
	})
	wg.Add(1)
	var (
		errRatio float64
		err2     error
	)
	utils.GO(func() {
		defer wg.Done()
		errRatio, err2 = service.QueryDataappMetricsCurrentErrorRate(ctx, params.AppID, extId, regions)
	})

	wg.Wait()
	if err1 != nil || err2 != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/metrics/misc_statistic[get]-GetDataAppMiscStatistic fail!", zap.Error(err1), zap.Error(err2))
		c.Error(errors.NewInternalServerError("internal error", nil))
	}
	tilog.Info(ctx, "error rate", zap.Float64("rate", errRatio))

	resp := dto.DataAppMiscStatusRes{
		EndpointStat: dto.EndpointStatistic{
			Deloyed: uint64(pubed),
			Draft:   uint64(drafed),
		},
		DataAppAvailable: errRatio < 0.5,
		UniqueUser:       0,
	}
	c.JSON(200, &resp)
}

// Get DataApp average request rate
// @Summary get DataApp average request rate
// @Description get DataApp average request rate
// @Tags dataapi
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param app_id path string true "app ID"
// @Param start query int true "query start timestamp"
// @Param end query int true "query end timestamp"
// @Param step query int true "invertal duration sec"
// @Produce json
// @Success 200 {object} dto.CommonPromMetricsRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/metrics/avg_request_rate [get]
func GetDataAppAvgReqRate(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_APP_ID}
	params := ginparser.NewCommonParamsByGin(c)
	validateErr := params.ValidateUrl(validateKeys)
	createdBy, _ := params.GetUserEmail()
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}//metrics/avg_request_rate[get]-GetDataAppAvgReqRate param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}
	if err := ensureDataAppMetricsSupported(ctx, params.AppID); err != nil {
		c.Error(err)
		return
	}
	query := c.Request.URL.Query()
	req, err := fetchDataAppMetricsRangeQueryParam(query)
	if err != nil {
		tilog.Warn(ctx, "param invalid", zap.Error(err))
		c.Error(err)
		return
	}
	extId, regions, err := service.GetDataAppExtIdAndRegions(ctx, params.AppID)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}//metrics/avg_request_rate[get]-GetDataAppAvgReqRate getDataAppExtIdAndRegions fail!", zap.Error(err), zap.String("user", createdBy))
		c.Error(errors.NewInternalServerError("internal error", nil))
		return
	}
	var resp = &dto.CommonPromMetricsRes{
		Status: "success",
		Data: dto.PromData{
			ResultType: "matrix",
		},
	}
	if len(regions) == 0 {
		resp.Data.Result = []dto.PromResult{}
		c.JSON(200, resp)
		return
	}
	req.DataAppExtId = extId
	req.Org = strconv.FormatUint(params.OrgID, 10)
	metricsData, err := service.QueryDataappMetricsReqCount(ctx, *req, params.AppID, regions)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}//metrics/avg_request_rate[get]-GetDataAppAvgReqRate queryDataappMetricsReqCount failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(errors.NewInternalServerError("internal error", nil))
		return
	}
	if len(metricsData.Data.Result) > 0 {
		for i := range metricsData.Data.Result[0].Values {
			metricsData.Data.Result[0].Values[i].Value /= pmodel.SampleValue(req.RangeSelector) / pmodel.SampleValue(time.Minute)
		}
	}
	resp.Data.Result = make([]dto.PromResult, 0)
	for i := range metricsData.Data.Result {
		item := dto.PromResult{}
		for _, v := range metricsData.Data.Result[i].Values {
			item.Values = append(item.Values, []interface{}{v.Timestamp.Unix(), v.Value.String()})
		}
		resp.Data.Result = append(resp.Data.Result, item)
	}
	c.JSON(200, &resp)
}

// Get DataApp error rate
// @Summary get DataApp error rate
// @Description get DataApp error rate
// @Tags dataapi
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param app_id path string true "app ID"
// @Param start query int true "query start timestamp"
// @Param end query int true "query end timestamp"
// @Param step query int true "invertal duration sec"
// @Produce json
// @Success 200 {object} dto.CommonPromMetricsRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/metrics/error_rate [get]
func GetDataAppErrorRate(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_APP_ID}
	params := ginparser.NewCommonParamsByGin(c)
	validateErr := params.ValidateUrl(validateKeys)
	createdBy, _ := params.GetUserEmail()
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}//metrics/error_rate[get]-GetDataAppErrorRate param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}
	if err := ensureDataAppMetricsSupported(ctx, params.AppID); err != nil {
		c.Error(err)
		return
	}
	query := c.Request.URL.Query()
	req, err := fetchDataAppMetricsRangeQueryParam(query)
	if err != nil {
		tilog.Warn(ctx, "param invalid", zap.Error(err))
		c.Error(err)
		return
	}
	extId, regions, err := service.GetDataAppExtIdAndRegions(ctx, params.AppID)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}//metrics/error_rate[get]-GetDataAppErrorRate getDataAppExtIdAndRegions fail!", zap.Error(err), zap.String("user", createdBy))
		c.Error(errors.NewInternalServerError("internal error", nil))
		return
	}
	var resp = &dto.CommonPromMetricsRes{
		Status: "success",
		Data: dto.PromData{
			ResultType: "matrix",
		},
	}
	if len(regions) == 0 {
		resp.Data.Result = []dto.PromResult{}
		c.JSON(200, resp)
		return
	}
	req.DataAppExtId = extId
	req.Org = strconv.FormatUint(params.OrgID, 10)
	metricsReqData, err := service.QueryDataappMetricsReqCount(ctx, *req, params.AppID, regions)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}//metrics/error_rate[get]-GetDataAppErrorRate queryDataappMetricsReqCount failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(errors.NewInternalServerError("internal error", nil))
		return
	}
	metricsErrorData, err := service.QueryDataappMetricsErrorCount(ctx, *req, params.AppID, regions)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}//metrics/error_rate[get]-GetDataAppErrorRate queryDataappMetricsErrorCount failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(errors.NewInternalServerError("internal error", nil))
		return
	}
	metricsdata := service.ReduceMetricsDiv(metricsErrorData, metricsReqData)
	resp.Data.Result = make([]dto.PromResult, 0)
	for i := range metricsdata.Data.Result {
		item := dto.PromResult{}
		for _, v := range metricsdata.Data.Result[i].Values {
			item.Values = append(item.Values, []interface{}{v.Timestamp.Unix(), v.Value.String()})
		}
		resp.Data.Result = append(resp.Data.Result, item)
	}
	c.JSON(200, resp)
}

// Get DataApp avg response time
// @Summary get DataApp avg response time
// @Description get DataApp avg response time
// @Tags dataapi
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param app_id path string true "app ID"
// @Param start query int true "query start timestamp"
// @Param end query int true "query end timestamp"
// @Param step query int true "invertal duration sec"
// @Produce json
// @Success 200 {object} dto.CommonPromMetricsRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/metrics/avg_response_time [get]
func GetDataAppAvgResponseTime(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_APP_ID}
	params := ginparser.NewCommonParamsByGin(c)
	validateErr := params.ValidateUrl(validateKeys)
	createdBy, _ := params.GetUserEmail()
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}//metrics/avg_response_time[get] GetDataAppAvgResponseTime param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}
	if err := ensureDataAppMetricsSupported(ctx, params.AppID); err != nil {
		c.Error(err)
		return
	}
	query := c.Request.URL.Query()
	req, err := fetchDataAppMetricsRangeQueryParam(query)
	if err != nil {
		tilog.Warn(ctx, "param invalid", zap.Error(err))
		c.Error(err)
		return
	}
	extId, regions, err := service.GetDataAppExtIdAndRegions(ctx, params.AppID)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}//metrics/avg_response_time[get] GetDataAppAvgResponseTime getDataAppExtIdAndRegions fail!", zap.Error(err), zap.String("user", createdBy))
		c.Error(errors.NewInternalServerError("internal error", nil))
		return
	}
	var resp = &dto.CommonPromMetricsRes{
		Status: "success",
		Data: dto.PromData{
			ResultType: "matrix",
		},
	}
	if len(regions) == 0 {
		resp.Data.Result = []dto.PromResult{}
		c.JSON(200, resp)
		return
	}
	req.DataAppExtId = extId
	req.Org = strconv.FormatUint(params.OrgID, 10)
	var (
		tp99Metrics *cdto.PromQueryRes
		tp99err     error
	)
	var wg sync.WaitGroup
	wg.Add(1)
	utils.GO(func() {
		defer wg.Done()
		tp99Metrics, tp99err = service.QueryDataappMetricsLatencyPT99(ctx, *req, params.AppID, regions)
	})
	metricsReqData, err := service.QueryDataappMetricsReqCount(ctx, *req, params.AppID, regions)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}//metrics/avg_response_time[get]-GetDataAppAvgResponseTime queryDataappMetricsReqCount failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(errors.NewInternalServerError("internal error", nil))
		return
	}
	metricsLatencyData, err := service.QueryDataappMetricsLantencySum(ctx, *req, params.AppID, regions)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}//metrics/avg_response_time[get]-GetDataAppAvgResponseTime queryDataappMetricsLantencySum failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(errors.NewInternalServerError("internal error", nil))
		return
	}
	metricsdata := service.ReduceMetricsDiv(metricsLatencyData, metricsReqData)

	wg.Wait()
	if tp99err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/metrics/avg_response_time[get]-GetDataAppAvgResponseTime QueryDataappMetricsLatencyPT99 failed!", zap.Error(tp99err), zap.String("user", createdBy))
		c.Error(errors.NewInternalServerError("internal error", nil))
		return
	}
	resp.Data.Result = make([]dto.PromResult, 0)
	if len(metricsdata.Data.Result) > 0 {
		item := dto.PromResult{
			Metric: map[string]string{
				"lable":    "avg_latency",
				"__name__": "average_latency",
			},
		}
		for _, v := range metricsdata.Data.Result[0].Values {
			item.Values = append(item.Values, []interface{}{v.Timestamp.Unix(), v.Value.String()})
		}
		resp.Data.Result = append(resp.Data.Result, item)
	}
	if len(tp99Metrics.Data.Result) > 0 {
		item := dto.PromResult{
			Metric: map[string]string{
				"lable":    "tp99",
				"__name__": "tp99",
			},
		}
		for _, v := range tp99Metrics.Data.Result[0].Values {
			item.Values = append(item.Values, []interface{}{v.Timestamp.Unix(), v.Value.String()})
		}
		resp.Data.Result = append(resp.Data.Result, item)
	}
	c.JSON(200, resp)
}

// Get DataApp tp99 response time
// @Summary get DataApp tp99 response time
// @Description get DataApp tp99 response time
// @Tags dataapi
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param app_id path string true "app ID"
// @Param start query int true "query start timestamp"
// @Param end query int true "query end timestamp"
// @Param step query int true "invertal duration sec"
// @Produce json
// @Success 200 {object} dto.CommonPromMetricsRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/metrics/response_time_tp99 [get]
func GetDataAppTP99ResponseTime(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_APP_ID}
	params := ginparser.NewCommonParamsByGin(c)
	validateErr := params.ValidateUrl(validateKeys)
	createdBy, _ := params.GetUserEmail()
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}//metrics/response_time_tp99[get]-GetDataAppTP99ResponseTime param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}
	if err := ensureDataAppMetricsSupported(ctx, params.AppID); err != nil {
		c.Error(err)
		return
	}
	query := c.Request.URL.Query()
	req, err := fetchDataAppMetricsRangeQueryParam(query)
	if err != nil {
		tilog.Warn(ctx, "param invalid", zap.Error(err))
		c.Error(err)
		return
	}
	extId, regions, err := service.GetDataAppExtIdAndRegions(ctx, params.AppID)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}//metrics/response_time_tp99[get]-GetDataAppTP99ResponseTime getDataAppExtIdAndRegions fail!", zap.Error(err), zap.String("user", createdBy))
		c.Error(errors.NewInternalServerError("internal error", nil))
		return
	}
	var resp = &dto.CommonPromMetricsRes{
		Status: "success",
		Data: dto.PromData{
			ResultType: "matrix",
		},
	}
	if len(regions) == 0 {
		resp.Data.Result = []dto.PromResult{}
		c.JSON(200, resp)
		return
	}
	req.DataAppExtId = extId
	req.Org = strconv.FormatUint(params.OrgID, 10)
	metricsdata, err := service.QueryDataappMetricsLatencyPT99(ctx, *req, params.AppID, regions)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}//metrics/response_time_tp99[get]-GetDataAppTP99ResponseTime queryDataappMetricsLatencyPT99 failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(errors.NewInternalServerError("internal error", nil))
		return
	}
	resp.Data.Result = make([]dto.PromResult, 0)
	for i := range metricsdata.Data.Result {
		item := dto.PromResult{}
		for _, v := range metricsdata.Data.Result[i].Values {
			item.Values = append(item.Values, []interface{}{v.Timestamp.Unix(), v.Value.String()})
		}
		resp.Data.Result = append(resp.Data.Result, item)
	}
	c.JSON(200, resp)
}

// Get DataApp unauth req num
// @Summary get DataApp unauth req num
// @Description get DataApp unauth req num
// @Tags dataapi
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param app_id path string true "app ID"
// @Param start query int true "query start timestamp"
// @Param end query int true "query end timestamp"
// @Param step query int true "invertal duration sec"
// @Produce json
// @Success 200 {object} dto.CommonPromMetricsRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/metrics/unauth_req_num [get]
func GetDataAppUnauthRequestNum(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_APP_ID}
	params := ginparser.NewCommonParamsByGin(c)
	validateErr := params.ValidateUrl(validateKeys)
	createdBy, _ := params.GetUserEmail()
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}//metrics/response_time_tp99[get]-GetDataAppUnauthRequestNum param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}
	if err := ensureDataAppMetricsSupported(ctx, params.AppID); err != nil {
		c.Error(err)
		return
	}
	query := c.Request.URL.Query()
	req, err := fetchDataAppMetricsRangeQueryParam(query)
	if err != nil {
		tilog.Warn(ctx, "param invalid", zap.Error(err))
		c.Error(err)
		return
	}
	extId, regions, err := service.GetDataAppExtIdAndRegions(ctx, params.AppID)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}//metrics/response_time_tp99[get]-GetDataAppUnauthRequestNum getDataAppExtIdAndRegions fail!", zap.Error(err), zap.String("user", createdBy))
		c.Error(errors.NewInternalServerError("internal error", nil))
		return
	}
	var resp = &dto.CommonPromMetricsRes{
		Status: "success",
		Data: dto.PromData{
			ResultType: "matrix",
		},
	}
	if len(regions) == 0 {
		resp.Data.Result = []dto.PromResult{}
		c.JSON(200, resp)
		return
	}
	req.DataAppExtId = extId
	req.Org = strconv.FormatUint(params.OrgID, 10)
	metricsdata, err := service.QueryDataappMetricsUnauthCount(ctx, *req, params.AppID, regions)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}//metrics/response_time_tp99[get]-GetDataAppTP99ResponseTime queryDataappMetricsLatencyPT99 failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(errors.NewInternalServerError("internal error", nil))
		return
	}
	resp.Data.Result = make([]dto.PromResult, 0)
	for i := range metricsdata.Data.Result {
		item := dto.PromResult{}
		for _, v := range metricsdata.Data.Result[i].Values {
			item.Values = append(item.Values, []interface{}{v.Timestamp.Unix(), strconv.Itoa(int(v.Value))})
		}
		resp.Data.Result = append(resp.Data.Result, item)
	}
	c.JSON(200, resp)
}

// Get DataApp most request endpoints rank
// @Summary get DataApp most request endpoints rank
// @Description get DataApp most request endpoints rank
// @Tags dataapi
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param app_id path string true "app ID"
// @Param start query int true "query start timestamp"
// @Param end query int true "query end timestamp"
// @Param topK query int true "top k"
// @Produce json
// @Success 200 {object} dto.CommonEndpointsRankRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/metrics/endpoints_rank/most_request [get]
func GetDataAppMostReqEndpointRank(c *gin.Context) {
	topKQueryWrapper(c, true, service.QueryDataappMetricsTopKReq, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}//metrics/endpoints_rank/most_request[get]-GetDataAppMostReqEndpointRank")
}

// Get DataApp most error endpoints rank
// @Summary get DataApp most error endpoints rank
// @Description get DataApp most error endpoints rank
// @Tags dataapi
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param app_id path string true "app ID"
// @Param start query int true "query start timestamp"
// @Param end query int true "query end timestamp"
// @Param topK query int true "top k"
// @Produce json
// @Success 200 {object} dto.CommonEndpointsRankRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/metrics/endpoints_rank/most_error [get]
func GetDataAppMostErrorEndpointRank(c *gin.Context) {
	topKQueryWrapper(c, true, service.QueryDataappMetricsTopKError, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}//metrics/endpoints_rank/most_error[get]-GetDataAppMostErrorEndpointRank")
}

// Get DataApp high latency endpoints rank
// @Summary get DataApp high latency  endpoints rank
// @Description get DataApp high latency  endpoints rank
// @Tags dataapi
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param app_id path string true "app ID"
// @Param start query int true "query start timestamp"
// @Param end query int true "query end timestamp"
// @Param topK query int true "top k"
// @Produce json
// @Success 200 {object} dto.CommonEndpointsRankRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/metrics/endpoints_rank/high_latency [get]
func GetDataAppHighLatencyEndpointRank(c *gin.Context) {
	topKQueryWrapper(c, false, service.QueryDataappMetricsTopKLatency, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}//metrics/endpoints_rank/most_error[get]-GetDataAppHighLatencyEndpointRank")
}

func topKQueryWrapper(c *gin.Context, intVal bool, queryer func(context.Context, cdto.PromQueryReq, uint64, []string) (*cdto.PromQueryRes, error), logPrefix string) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_APP_ID}
	params := ginparser.NewCommonParamsByGin(c)
	validateErr := params.ValidateUrl(validateKeys)
	createdBy, _ := params.GetUserEmail()
	if validateErr != nil {
		tilog.Error(ctx, fmt.Sprintf("%s param validate failed!", logPrefix), zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}
	if err := ensureDataAppMetricsSupported(ctx, params.AppID); err != nil {
		c.Error(err)
		return
	}
	query := c.Request.URL.Query()
	req, err := fetchDataAppMetricsInstantQueryParam(query)
	if err != nil {
		tilog.Warn(ctx, "param invalid", zap.Error(err))
		c.Error(err)
		return
	}
	extId, regions, err := service.GetDataAppExtIdAndRegions(ctx, params.AppID)
	if err != nil {
		tilog.Error(ctx, fmt.Sprintf("%s getDataAppExtIdAndRegions fail!", logPrefix), zap.Error(err), zap.String("user", createdBy))
		c.Error(errors.NewInternalServerError("internal error", nil))
		return
	}
	var resp = &dto.CommonEndpointsRankRes{}
	if len(regions) == 0 {
		c.JSON(200, resp)
		return
	}
	req.DataAppExtId = extId
	req.Org = strconv.FormatUint(params.OrgID, 10)
	metricsdata, err := queryer(ctx, *req, params.AppID, regions)
	if err != nil {
		tilog.Error(ctx, fmt.Sprintf("%s query failed!", logPrefix), zap.Error(err), zap.String("user", createdBy))
		c.Error(errors.NewInternalServerError("internal error", nil))
		return
	}
	ids := make([]string, 0, 10)
	for i := range metricsdata.Data.Result {
		id, ok := metricsdata.Data.Result[i].Metric["endpoint_id"]
		if ok {
			ids = append(ids, id)
		}
	}
	endpointNames, err := service.GetEndpointsNameByIdStr(ctx, ids)
	if err != nil {
		tilog.Error(ctx, fmt.Sprintf("%s GetEndpointsNameByIdStr failed!", logPrefix), zap.Error(err), zap.String("user", createdBy))
		c.Error(errors.NewInternalDB("internal error", nil))
		return
	}
	for i := range metricsdata.Data.Result {
		id, ok := metricsdata.Data.Result[i].Metric["endpoint_id"]
		if ok {
			showVal := float64(metricsdata.Data.Result[i].Value.Value)
			showValStr := fmt.Sprintf("%.2f", showVal)
			if intVal {
				showVal = math.Round(showVal)
				showValStr = fmt.Sprintf("%d", int(showVal))
			}
			if showValStr == "0" {
				continue
			}
			resp.Rank = append(resp.Rank, dto.EndpointsRankItem{
				EndpointName: endpointNames[id],
				RankValueStr: showValStr,
				RankVal:      showVal,
				EndpointId:   id,
			})
		}
	}
	sort.Slice(resp.Rank, func(i, j int) bool {
		return resp.Rank[i].RankVal > resp.Rank[j].RankVal ||
			resp.Rank[i].RankVal == resp.Rank[j].RankVal && resp.Rank[i].EndpointName < resp.Rank[j].EndpointName
	})
	c.JSON(200, resp)
}

func fetchDataAppMetricsRangeQueryParam(query map[string][]string) (*cdto.PromQueryReq, error) {
	req := cdto.PromQueryReq{}
	var err error
	req.Start, err = strconv.ParseUint(queryGetOrEmpty(query, "start"), 10, 64)
	if err != nil {
		return nil, errors.NewBadRequest("invalid param start", nil)
	}
	req.End, err = strconv.ParseUint(queryGetOrEmpty(query, "end"), 10, 64)
	if err != nil {
		return nil, errors.NewBadRequest("invalid param end", nil)
	}
	if req.End < req.Start+60 {
		return nil, errors.NewBadRequest("query time range should be at least 1 minute", nil)
	}
	stepStr := queryGetOrEmpty(query, "step")
	step, err := strconv.Atoi(stepStr)
	if err != nil || step == 0 {
		return nil, errors.NewBadRequest("invalid param step", nil)
	}
	req.Step = pmodel.Duration(time.Duration(step) * time.Second)
	if req.Step < pmodel.Duration(30*time.Second) {
		req.Step = pmodel.Duration(30 * time.Second)
	}
	req.RangeSelector = req.Step
	if req.RangeSelector < pmodel.Duration(time.Minute) {
		req.RangeSelector = pmodel.Duration(time.Minute)
	}
	return &req, nil
}

func fetchDataAppMetricsInstantQueryParam(query map[string][]string) (*cdto.PromQueryReq, error) {
	req := cdto.PromQueryReq{}
	var err error
	req.Start, err = strconv.ParseUint(queryGetOrEmpty(query, "start"), 10, 64)
	if err != nil {
		return nil, errors.NewBadRequest("invalid param start", nil)
	}
	req.End, err = strconv.ParseUint(queryGetOrEmpty(query, "end"), 10, 64)
	if err != nil {
		return nil, errors.NewBadRequest("invalid param end", nil)
	}
	if req.End < req.Start+60 {
		return nil, errors.NewBadRequest("query time range should be at least 1 minute", nil)
	}

	req.TopK, err = strconv.ParseUint(queryGetOrEmpty(query, "topK"), 10, 64)
	if err != nil || req.TopK == 0 {
		return nil, errors.NewBadRequest("invalid param topK", nil)
	}
	req.RangeSelector = pmodel.Duration(time.Second * time.Duration(req.End-req.Start))
	return &req, nil
}
