package sqleditor

import (
	"context"
	"dataapps/commons/ginparser"
	"dataapps/commons/logger"
	"dataapps/consts"
	"dataapps/errs"
	"dataapps/internal/controller/eda"
	dto "dataapps/internal/dto/sqleditor"
	entity "dataapps/internal/entity/sqleditor"
	service "dataapps/internal/service/sqleditor"
	"dataapps/internal/service/util"
	"fmt"
	"net/http"
	"strconv"

	"github.com/tidbcloud/dataservices/commons/bizerr"
	"github.com/tidbcloud/tiinf/sdk/auditlog"

	"strings"
	"time"

	"github.com/gin-gonic/gin"
	tierrs "github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

// @Time    : 2022/11/14 10:48 AM
// @Author  : lvhongmeng

const (
	ERRORID = 0
)

// @BasePath /api/v1/dataapps/sqleditor

// UpdateSettings godoc
// @Summary update settings
// @Schemes
// @Description update settings
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Param request_body body entity.Settings true "request_body"
// @Accept json
// @Produce json
// @Success 200 {object} dto.UpdateSettingsRes
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/settings [put]
func UpdateSettings(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}
	param := entity.Settings{}
	if err := bindJsonParam(c, &param); err != nil {
		c.Error(err)
		return
	}
	orgId, projectId, clusterId, err := getParam(c)
	if err != nil {
		tilog.Error(ctx, "ERROR: UpdateUserSettings-get orgId,projectId,clusterId failed! user:"+createdBy)
		c.Error(err)
		return
	}
	limit := param.Limit
	if limit > 2000 {
		tilog.Warn(ctx, "ERROR: UpdateUserSettings-setting exceed the limit! user:"+createdBy)
		c.Error(tierrs.NewBadRequest("invalid limit param", nil))
		return
	}
	settings := entity.SQLEditorSettings{
		CreatedBy:     createdBy,
		OrgID:         orgId,
		ProjectID:     projectId,
		ClusterID:     clusterId,
		BasicSettings: fmt.Sprintf("{\"limit\": %d,\"show_system_db_schema\":%t}", limit, param.ShowSystemDbSchema),
	}
	tilog.Info(ctx, fmt.Sprintf("UpdateSettings payload:%+v,user:%s", settings, createdBy))
	err = service.UpdateUserSettings(settings)
	if err != nil {
		c.Error(err)
		return
	}
	res := &dto.UpdateSettingsRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     "success",
	}
	c.JSON(http.StatusOK, res)
}

// ShowSettings godoc
// @Summary show settings
// @Schemes
// @Description show settings
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Accept json
// @Produce json
// @Success 200 {object} dto.ShowSettingsRes
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/settings [get]
func ShowSettings(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}
	orgId, projectId, clusterId, err := getParam(c)
	if err != nil {
		tilog.Error(ctx, "ERROR: ShowSettings-get orgId,projectId,clusterId failed! user:"+createdBy)
		c.Error(err)
		return
	}

	settings := entity.SQLEditorSettings{
		CreatedBy: createdBy,
		OrgID:     orgId,
		ProjectID: projectId,
		ClusterID: clusterId,
	}
	tilog.Info(ctx, fmt.Sprintf("ShowSettings payload:%+v,user:%s", settings, createdBy))
	settingsRes, err := service.ShowSettings(ctx, settings)
	if err != nil {
		c.Error(err)
		tilog.Error(ctx, fmt.Sprintf("ERROR: ShowSettings failed! clusterid: %v, param:%+v, user: %s", clusterId, settings, createdBy))
		return
	}
	res := &dto.ShowSettingsRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     settingsRes,
	}
	c.JSON(http.StatusOK, res)
}

// ShowUserInfo godoc
// @Summary show user info
// @Schemes
// @Description show user info
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Accept json
// @Produce json
// @Success 200 {object} dto.ShowUserInfoRes
// @Router /orgs/{org_id}/projects/{project_id}/users [get]
func ShowUserInfo(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}

	tilog.Info(ctx, fmt.Sprintf("ShowUserInfo payload:%+v,user:%s", createdBy, createdBy))
	userInfo, err := service.ShowUserInfo(createdBy)
	if err != nil {
		c.Error(err)
		tilog.Error(ctx, fmt.Sprintf("ERROR: ShowUserInfo failed! user: %s,err:%v", createdBy, err))
		return
	}

	res := &dto.ShowUserInfoRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     userInfo,
	}
	c.JSON(http.StatusOK, res)
}

// UpdateUserInfo godoc
// @Summary update user info
// @Schemes
// @Description update user info
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Param request_body body entity.UserInfoParam true "request_body"
// @Accept json
// @Produce json
// @Success 200 {object} dto.UpdateUserInfoRes
// @Router /orgs/{org_id}/projects/{project_id}/users [put]
func UpdateUserInfo(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}
	param := map[string]interface{}{}
	if err := bindJsonParam(c, &param); err != nil {
		c.Error(err)
		return
	}
	tilog.Info(ctx, fmt.Sprintf("UpdateUserInfo payload:%v,user:%s", param, createdBy))

	param["created_by"] = createdBy
	tilog.Info(ctx, fmt.Sprintf("UpdateUserInfo payload:%+v,user:%s", param, createdBy))
	err := service.UpdateUserInfo(param)
	if err != nil {
		c.Error(err)
		tilog.Error(ctx, fmt.Sprintf("ERROR: UpdateUserInfo failed! param: %v,err:%v,user:%s", param, err, createdBy))
		return
	}

	res := &dto.UpdateUserInfoRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     "success",
	}
	c.JSON(http.StatusOK, res)

}

// UpdateDataApp godoc
// @Summary update dataapp info
// @Schemes
// @Description update dataapp info
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param request_body body entity.DataAppSettingsFormat true "request_body"
// @Accept json
// @Produce json
// @Success 200 {object} dto.GenericSuccessApiResponse
// @Router /orgs/{org_id}/projects/{project_id}/settings [put]
func UpdateDataAppSettings(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}
	param := entity.DataAppSettingsFormat{}
	if err := bindJsonParam(c, &param); err != nil {
		c.Error(err)
		return
	}
	orgId, projectId, _, err := getParam(c)
	if err != nil {
		logger.Warn(ctx, "ERROR: UpdateDataAppSettings-get orgId,projectId,clusterId failed! user:"+createdBy, zap.Error(err))
		c.Error(err)
		return
	}

	settings := entity.DataAppSettings{
		OrgID:         orgId,
		ProjectID:     projectId,
		EnableDataAPI: param.EnableDataAPI,
		CreatedBy:     createdBy,
		UpdatedBy:     createdBy,
	}
	logger.Info(ctx, fmt.Sprintf("UpdateDataAppSettings payload:%+v,user:%s", settings, createdBy))
	err = service.UpdateDataAppSettings(ctx, settings)
	if err != nil {
		logger.LogWithError(ctx, err, "UpdateDataAppSettings failed")
		c.Error(err)
		return
	}

	c.JSON(http.StatusOK, dto.Success())

}

// ShowChat2DataURL godoc
// @Summary show chat2data sample url
// @Schemes
// @Description show chat2data sample url
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Accept json
// @Produce json
// @Success 200 {object} dto.GenericSuccessApiResponse
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/dataapi/sample [get]
func ShowChat2DataURL(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}
	_, projectId, clusterId, err := getParam(c)
	if err != nil {
		tilog.Error(ctx, "ERROR: ShowChat2DataURL-get orgId,projectId,clusterId failed! user:"+createdBy)
		c.Error(err)
		return
	}
	// verify enable chat2data
	settings, err := service.ShowDataAppSettings(ctx, projectId)
	if err != nil {
		tilog.Error(ctx, "ERROR: ShowChat2DataURL-get data app settings failed! user:"+createdBy)
		c.Error(err)
		return
	}
	if !settings.EnableDataAPI {
		tilog.Error(ctx, "ERROR: ShowChat2DataURL-enable for chat2data! user:"+createdBy)
		c.Error(tierrs.NewBadRequest("turn on chat2data settings first", nil))
		return
	}
	// show url
	tilog.Info(ctx, fmt.Sprintf("ShowChat2DataURL payload:%+v%+v,user:%s", projectId, clusterId, createdBy))
	sampleCurl, err := service.ShowChat2DataURL(ctx, projectId, clusterId)
	if err != nil {
		c.Error(err)
		return
	}
	res := &dto.GenericSuccessApiResponse{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     sampleCurl,
	}
	c.JSON(http.StatusOK, res)

}

// Create Session godoc
// @Summary create session
// @Schemes
// @Description create session result
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Param request_body body entity.SessionParam true "request_body"
// @Accept json
// @Produce json
// @Success 200 {object} dto.SessionRes
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/session [post]
func CreateSession(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}

	// Get user auth0 token
	auth0Token, ok := getAuth0Token(c, ctx)
	if !ok {
		c.Error(errs.ErrUnauthorized)
		return
	}

	_, projectId, clusterId, err := getParam(c)
	if err != nil {
		c.Error(err)
		logger.LogWithError(ctx, err, "ERROR: get orgId,projectId,clusterId failed! user:"+createdBy)
		return
	}

	param := entity.SessionParam{}
	if err := bindJsonParam(c, &param); err != nil {
		c.Error(err)
		return
	}
	logger.Info(ctx, fmt.Sprintf("CreateSession payload:%+v,user:%s", param, createdBy))
	sessionId, bizErr := service.CreateSession(ctx, entity.CreateSessionInput{
		CreatedBy:  createdBy,
		Projectid:  projectId,
		Clusterid:  clusterId,
		Database:   param.Database,
		Auth0Token: auth0Token,
	})
	if bizErr != nil {
		logger.LogWithError(ctx, err, "region loki link:"+util.GenerateRegionLokiLink(ctx, clusterId))
		c.Error(bizErr)
		return
	}
	resJson := &dto.SessionRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     sessionId,
	}
	logger.Info(ctx, fmt.Sprintf("CreateSession result:%+v,user:%s", resJson, createdBy))
	c.JSON(http.StatusOK, resJson)
}

// Close Session godoc
// @Summary close session
// @Schemes
// @Description close session result
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Param session_id path string true "session ID"
// @Accept json
// @Produce json
// @Success 200 {object} dto.SessionRes
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/session/{session_id} [delete]
func CloseSession(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}

	// Get user auth0 token
	auth0Token, ok := getAuth0Token(c, ctx)
	if !ok {
		c.Error(errs.ErrUnauthorized)
		return
	}

	_, _, clusterId, err := getParam(c)
	if err != nil {
		c.Error(err)
		logger.LogWithError(ctx, err, "ERROR: get orgId,projectId,clusterId failed! user:"+createdBy)
		return
	}

	sessionId := c.Param("session_id")
	logger.Info(ctx, fmt.Sprintf("CloseSession payload:%+v,user:%s", sessionId, createdBy))
	bizErr := service.CloseSession(ctx, createdBy, clusterId, sessionId, auth0Token)
	if bizErr != nil {
		logger.LogWithError(ctx, err, "region loki link:"+util.GenerateRegionLokiLink(ctx, clusterId))
		c.Error(bizErr)
		return
	}
	resJson := &dto.SessionRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     "success",
	}
	logger.Info(ctx, fmt.Sprintf("CloseSession result:%+v,user:%s", resJson, createdBy))
	c.JSON(http.StatusOK, resJson)
}

// Execute SQL godoc
// @Summary execute sql
// @Schemes
// @Description execute sql result
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Param request_body body entity.StatementParam true "request_body"
// @Accept json
// @Produce json
// @Success 200 {object} dto.StatementsRes
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/statements [post]
func Statements(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}
	orgId, projectId, clusterId, err := getParam(c)
	if err != nil {
		c.Error(err)
		logger.LogWithError(ctx, err, "ERROR: get orgId,projectId,clusterId failed!")
		return
	}

	// Get user auth0 token
	auth0Token, ok := getAuth0Token(c, ctx)
	if !ok {
		c.Error(errs.ErrUnauthorized)
		return
	}

	param := entity.StatementParam{}
	if err := bindJsonParam(c, &param); err != nil {
		c.Error(err)
		return
	}
	logger.Info(ctx, "Statements begin", zap.Any("params", param))

	message := entity.SqlEditorMessage{
		OrgId:     orgId,
		ProjectId: projectId,
		ClusterId: clusterId,
		SessionId: param.SessionId,
		Sql:       param.Sql,
		CreatedBy: createdBy,
		StartTime: time.Unix(time.Now().Unix(), 0).Format("2006-01-02 15:04:05"),
	}
	res, err := service.Statements(ctx, createdBy, orgId, projectId, clusterId, param.Sql, param.SessionId, param.Database, auth0Token)
	// add exec_sql querylog
	go func(ctx context.Context) {
		defer func() {
			if err := recover(); err != nil {
				logger.Error(ctx, "Statements add exec_sql querylog panic", zap.Any("err", err))
			}
		}()
		var (
			status       int8 = 1
			errorMessage string
		)
		if err != nil {
			status = 2
			errorMessage = err.Error()
		}
		executeTime, _ := strconv.ParseInt(res.ExecuteTime, 10, 64)
		if err := service.CreateOplog(ctx, entity.SqlEditorOplog{
			OrgId:           orgId,
			ProjectId:       projectId,
			ClusterId:       clusterId,
			SessionId:       param.SessionId,
			Chat2QuerySId:   param.Chat2QuerySid,
			SqlFileId:       param.SqlFileID,
			EventType:       string(entity.EventTypeExecSql),
			SqlContent:      param.Sql,
			StartTime:       res.StartTime,
			EndTime:         res.EndTime,
			Duration:        executeTime,
			Status:          status,
			ErrorMessage:    errorMessage,
			CreatedBy:       createdBy,
			CreatedAt:       time.Now(),
			Databases:       strings.Join(res.Databases, ","),
			AffectedRows:    res.RowCount,
			AffectedColumns: int64(len(res.Columns)),
			Channel:         "chat2sql",
		}); err != nil {
			logger.LogWithError(ctx, err, "CreateOplog failed", zap.String("event_type", string(entity.EventTypeExecSql)))
		}
	}(util.NewContext(ctx))

	auditDetails := auditlog.Details{
		auditlog.KeyDescription: "query sql in chat2query console",
		auditlog.KeyError:       err,
	}
	auditlog.NewEvent(ctx, auditlog.EventConsoleQuerySQLFromChat2Query).
		SetOperatorType(auditlog.OperatorTypeUser).
		ParseGIN(c).
		SetOperatorType(auditlog.OperatorTypeUser).
		SetOrgID(orgId).SetProjectID(projectId).SetClusterID(clusterId).
		SetResult(err == nil).
		SetDetails(auditDetails.Build()).
		Submit()

	if err != nil {
		logger.LogWithError(ctx, err, fmt.Sprintf("ERROR: sql execute fail. sessionId:%s,sql:%s,user:%s", param.SessionId, param.Sql, createdBy))
		message.Status = "failure"
		message.ErrorMessage = err.Error()
		message.EndTime = time.Unix(time.Now().Unix(), 0).Format("2006-01-02 15:04:05")
		loki := util.GenerateRegionLokiLink(ctx, clusterId)
		logger.LogWithError(ctx, err, "region loki link:"+loki)
		message.Loki = loki
		logErr := createLog(ctx, message)
		if logErr != nil {
			logger.LogWithError(ctx, logErr, fmt.Sprintf("ERROR: create log failed! err:%v, message:%+v, user: %s", logErr, message, createdBy))
		}
		if terr, ok := tierrs.AsTiError(err); ok {
			if terr.BizCode() == bizerr.ErrInvalidUserSql.BizCode() {
				resJson := &dto.StatementsRes{
					BasicRes: dto.BasicRes{
						Code:    int(terr.BizCode()),
						Message: terr.ErrMsg(),
					},
				}
				c.JSON(http.StatusOK, resJson)
				return
			}
		}
		c.Error(err)
		return
	}

	message.Status = "success"
	message.StartTime = res.StartTime
	message.EndTime = res.EndTime
	message.Duration = res.ExecuteTime
	// create log
	err = createLog(ctx, message)
	if err != nil {
		c.Error(err)
		return
	}

	resJson := &dto.StatementsRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     res,
	}
	c.JSON(http.StatusOK, resJson)
}

// Show All DB Meta godoc
// @Summary show all db meta
// @Schemes
// @Description show all db meta
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Param isbrief query string true "value: true or false"
// @Accept json
// @Produce json
// @Success 200 {object} dto.ClusterMetaRes
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/meta [get]
func ShowClusterMeta(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}

	// Get user auth0 token
	auth0Token, ok := getAuth0Token(c, ctx)
	if !ok {
		c.Error(errs.ErrUnauthorized)
		return
	}

	_, projectId, clusterId, err := getParam(c)
	if err != nil {
		logger.Warn(ctx, "ERROR: get orgId,projectId,clusterId failed! user:"+createdBy, zap.Error(err))
		c.Error(err)
		return
	}
	isBrief := c.Query("isbrief")
	logger.Info(ctx, fmt.Sprintf("ShowClusterMeta payload: isBrief: %+v, user:%s", isBrief, createdBy))
	res, bizErr := service.ShowClusterMeta(ctx, entity.ShowClusterMetaInput{
		CreatedBy:  createdBy,
		Projectid:  projectId,
		Clusterid:  clusterId,
		IsBrief:    isBrief,
		Auth0Token: auth0Token,
	})
	if bizErr != nil {
		logger.LogWithError(ctx, err, fmt.Sprintf("ERROR: ShowClusterMeta failed. clusterid: %d, user: %s", clusterId, createdBy))
		logger.Warn(ctx, "region loki link:"+util.GenerateRegionLokiLink(ctx, clusterId))
		c.Error(bizErr)
		return
	}

	if res == nil {
		res = make([]*entity.DBMeta, 0)
	}
	resJson := &dto.ClusterMetaRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     res,
	}
	c.JSON(http.StatusOK, resJson)
}

// Show Table Meta godoc
// @Summary show table meta
// @Description table meta
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Param db_name path string true "db name"
// @Param table_name path string true "table_name"
// @Accept json
// @Produce json
// @Success 200 {object} dto.ShowTableMetaRes
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/dbs/{db_name}/{table_name}/meta [get]
func ShowTableMeta(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}

	// Get user auth0 token
	auth0Token, ok := getAuth0Token(c, ctx)
	if !ok {
		c.Error(errs.ErrUnauthorized)
		return
	}

	_, projectId, clusterId, err := getParam(c)
	if err != nil {
		tilog.Error(ctx, "ERROR: get orgId,projectId,clusterId failed!"+createdBy)
		c.Error(err)
		return
	}

	dbName := c.Param("db_name")
	tableName := c.Param("table_name")

	tilog.Info(ctx, fmt.Sprintf("ShowTableMeta payload:%+v%+v,user:%s", dbName, tableName, createdBy))
	meta, err := service.ShowTableMeta(ctx, entity.SqlExecInput{
		CreatedBy:  createdBy,
		Projectid:  projectId,
		Clusterid:  clusterId,
		DbName:     dbName,
		TableName:  tableName,
		Auth0Token: auth0Token,
	})
	if err != nil {
		tilog.Error(ctx, fmt.Sprintf("ERROR: ShowTableMeta failed. clusterid: %d,dbname: %s, table_name: %s, user: %s", clusterId, dbName, tableName, createdBy))
		tilog.Error(ctx, "region loki link:"+util.GenerateRegionLokiLink(ctx, clusterId))
		c.Error(err)
		return
	}
	resJson := &dto.ShowTableMetaRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     meta,
	}
	c.JSON(http.StatusOK, resJson)
}

// Is Table Exist godoc
// @Summary is table exist
// @Description is table exist
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Param db_name path string true "db name"
// @Param table_name path string true "table_name"
// @Accept json
// @Produce json
// @Success 200 {object} dto.IsDBTableExistRes
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/dbs/{db_name}/{table_name}/exist [get]
func IsDBTableExist(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}
	// Get user auth0 token
	auth0Token, ok := getAuth0Token(c, ctx)
	if !ok {
		c.Error(errs.ErrUnauthorized)
		return
	}
	_, projectId, clusterId, err := getParam(c)
	if err != nil {
		tilog.Error(ctx, "ERROR: IsDBTableExist-get orgId,projectId,clusterId failed!"+createdBy)
		c.Error(err)
		return
	}

	dbName := c.Param("db_name")
	tableName := c.Param("table_name")
	tilog.Info(ctx, fmt.Sprintf("IsDBTableExist payload:%+v%+v,user:%s", dbName, tableName, createdBy))
	sessionId, isExist, err := service.IsDBTableExist(ctx, entity.IsDBTableExistInput{
		CreatedBy:  createdBy,
		ProjectId:  projectId,
		ClusterId:  clusterId,
		DbName:     dbName,
		TableName:  tableName,
		Auth0Token: auth0Token,
	})
	if err != nil {
		tilog.Error(ctx, fmt.Sprintf("ERROR: IsDBTableExist failed. sessionId: %s,dbname: %s, table_name: %s, user: %s", sessionId, dbName, tableName, createdBy))
		tilog.Error(ctx, "region loki link:"+util.GenerateRegionLokiLink(ctx, clusterId))
		c.Error(err)
		return
	}
	resJson := &dto.IsDBTableExistRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data: &entity.IsDBTableExist{
			IsExist:   isExist,
			SessionId: sessionId,
		},
	}
	tilog.Info(ctx, fmt.Sprintf("IsDBTableExist result:%+v,user:%s", resJson, createdBy))
	c.JSON(http.StatusOK, resJson)
}

// Create SQL File godoc
// @Summary create sql file
// @Description create sql file
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Param request_body body entity.SqlEditorFile true "request_body"
// @Accept json
// @Produce json
// @Success 200 {object} dto.CreateSQLFileRes
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/sqlfiles [post]
func CreateSQLFile(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}
	orgId, projectId, clusterId, err := getParam(c)
	if err != nil {
		tilog.Error(ctx, "ERROR: get orgId,projectId,clusterId failed! user:"+createdBy)
		c.Error(err)
		return
	}

	param := entity.SQLFileReq{}
	if err := bindJsonParam(c, &param); err != nil {
		c.Error(err)
		return
	}

	createEntity := entity.SqlEditorFile{
		OrgId:     orgId,
		ProjectId: projectId,
		ClusterId: clusterId,
		Database:  param.Database,
		Name:      param.Name,
		Content:   param.Content,
		IsDeleted: 0,
		CreatedBy: createdBy,
		UpdatedBy: createdBy,
	}
	tilog.Info(ctx, fmt.Sprintf("CreateSQLFile payload:%+v,user:%s", createEntity, createdBy))
	id, err := service.CreateSQLFile(ctx, createdBy, createEntity)
	auditDetails := auditlog.Details{
		auditlog.KeyDescription: "create sql file",
		auditlog.KeyError:       err,
	}
	auditlog.NewEvent(ctx, auditlog.EventCreateSQLFileFromChat2Query).
		SetOperatorType(auditlog.OperatorTypeUser).
		ParseGIN(c).
		SetOperatorType(auditlog.OperatorTypeUser).
		SetOrgID(orgId).SetProjectID(projectId).SetClusterID(clusterId).
		SetResult(err == nil).
		SetDetails(auditDetails.Build()).
		Submit()
	if err != nil {
		tilog.Error(ctx, fmt.Sprintf("ERROR: CreateSQLFile failed! param: %v,error:%v", createEntity, err))
		//if errors.Cause(err) == errs.ErrFilesInvalidSize {
		//	failRes(c, err)
		//} else {
		//	c.Error(err)
		//}
		c.Error(err)
		return
	}
	resJson := &dto.CreateSQLFileRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     id,
	}
	c.JSON(http.StatusOK, resJson)
}

// Show SQL File godoc
// @Summary show sql file
// @Description show sql file
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Param sql_file_id path string true "sql file ID"
// @Accept json
// @Produce json
// @Success 200 {object} dto.ShowSQLFileRes
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/sqlfiles/{sql_file_id} [get]
func ShowSQLFile(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}
	sqlfileIdStr := c.Param("sql_file_id")
	sqlfileId, err := strconv.ParseUint(sqlfileIdStr, 10, 64)
	if err != nil {
		tilog.Error(ctx, fmt.Sprintf("ERROR: ShowSQLFile-parse sql file id failed!error:%v", err))
		c.Error(err)
		return
	}

	tilog.Info(ctx, fmt.Sprintf("ShowSQLFile payload:%+v,user:%s", sqlfileId, createdBy))
	res, err := service.ShowSQLFile(ctx, sqlfileId, createdBy)
	if err != nil {
		c.Error(err)
		return
	}

	resJson := &dto.ShowSQLFileRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     res,
	}
	c.JSON(http.StatusOK, resJson)

}

// Update SQL File godoc
// @Summary update sql file
// @Description update sql file
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Param sql_file_id path string true "sql file ID"
// @Param request_body body entity.SqlEditorFile true "request_body"
// @Accept json
// @Produce json
// @Success 200 {object} dto.UpdateSQLFileRes
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/sqlfiles/{sql_file_id} [put]
func UpdateSQLFile(c *gin.Context) {
	ctx := c.Request.Context()
	updatedBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}
	sqlfileIdStr := c.Param("sql_file_id")
	sqlfileId, err := strconv.ParseUint(sqlfileIdStr, 10, 64)
	if err != nil {
		logger.Warn(ctx, fmt.Sprintf("ERROR: UpdateSQLFile-parse sql file id failed!error:%v,user:%s", err, updatedBy))
		c.Error(err)
		return
	}
	param := entity.SQLFileReq{}
	if err := bindJsonParam(c, &param); err != nil {
		c.Error(err)
		return
	}

	updateEntity := entity.SqlEditorFile{
		ID:        sqlfileId,
		Name:      param.Name,
		Content:   param.Content,
		UpdatedBy: updatedBy,
	}

	err = service.UpdateSQLFile(ctx, updatedBy, updateEntity)
	if err != nil {
		c.Error(err)
		return
	}
	resJson := &dto.UpdateSQLFileRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     "success",
	}
	c.JSON(http.StatusOK, resJson)
}

// Delete SQL File godoc
// @Summary delete sql file
// @Description delete sql file
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Param sql_file_id path string true "sql file ID"
// @Accept json
// @Produce json
// @Success 200 {object} dto.DeleteSQLFileRes
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/sqlfiles/{sql_file_id} [delete]
func DeleteSQLFile(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}
	orgId, projectId, clusterId, err := getParam(c)
	if err != nil {
		tilog.Error(ctx, "ERROR: get orgId,projectId,clusterId failed! user:"+createdBy)
		c.Error(err)
		return
	}
	sqlfileIdStr := c.Param("sql_file_id")
	sqlfileId, err := strconv.ParseUint(sqlfileIdStr, 10, 64)
	if err != nil {
		tilog.Error(ctx, fmt.Sprintf("ERROR: DeleteSQLFile-parse sql file id failed!error:%v,user:%s", err, createdBy))
		c.Error(err)
		return
	}
	tilog.Info(ctx, fmt.Sprintf("DeleteSQLFile payload:%+v,user:%s", sqlfileId, createdBy))
	err = service.DeleteSQLFile(ctx, sqlfileId, createdBy)
	auditDetails := auditlog.Details{
		auditlog.KeyDescription: "delete sql file",
		auditlog.KeyError:       err,
	}
	auditlog.NewEvent(ctx, auditlog.EventDeleteSQLFileFromChat2Query).
		SetOperatorType(auditlog.OperatorTypeUser).
		ParseGIN(c).
		SetOperatorType(auditlog.OperatorTypeUser).
		SetOrgID(orgId).SetProjectID(projectId).SetClusterID(clusterId).
		SetResult(err == nil).
		SetDetails(auditDetails.Build()).
		Submit()
	if err != nil {
		c.Error(err)
		return
	}
	resJson := &dto.DeleteSQLFileRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     "success",
	}
	c.JSON(http.StatusOK, resJson)
}

// List SQL File godoc
// @Summary list sql file
// @Description list sql file
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Accept json
// @Produce json
// @Success 200 {object} dto.ListSQLFileRes
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/sqlfiles [get]
func ListSQLFile(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}
	_, _, clusterId, err := getParam(c)
	if err != nil {
		tilog.Error(ctx, "ERROR:get orgId,projectId,clusterId failed!user"+createdBy)
		c.Error(err)
		return
	}

	tilog.Info(ctx, fmt.Sprintf("ListSQLFile payload:%+v,user:%s", clusterId, createdBy))
	res, err := service.ListSQLFile(ctx, clusterId, createdBy)
	if err != nil {
		c.Error(err)
		return
	}
	resJson := &dto.ListSQLFileRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     res,
	}
	c.JSON(http.StatusOK, resJson)

}

// Chat with Bot godoc
// @Summary chat with bot
// @Description chat with bot
// @Tags message
// @Param org_id path string true "org ID"
// @Param project_id path string true "project ID"
// @Param request_body body dto.InteractRequest true "request_body"
// @Accept json
// @Produce json
// @Success 200 {object} dto.InteractResponse
// @Router /orgs/{org_id}/projects/{project_id}/bot [post]
func InteractBot(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID}
	params := ginparser.NewCommonParamsByGin(c)
	createdBy, ok := params.GetUserEmail()
	if !ok {
		tilog.Error(ctx, "user not found!")
		c.Error(errs.ErrUnauthorized)
		return
	}
	validateErr := params.ValidateUrl(validateKeys)
	if validateErr != nil {
		tilog.Error(ctx, "InteractBot param validate failed", zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}
	req := &dto.InteractRequest{}
	err := params.BindParam(req)
	if err != nil {
		tilog.Error(ctx, "InteractBot param bind failed", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}
	// Set projectid
	req.OrgID = params.OrgID
	req.ProjectID = params.ProjectID
	tilog.Info(ctx, "InteractBot request", zap.Any("req", req), zap.String("user", createdBy))

	if invalidErr := service.CheckInteractReqeustValid(req); invalidErr != nil {
		tilog.Error(ctx, "InteractBot.CheckInteractReqeustValid failed", zap.Any("err", invalidErr))
		c.Error(invalidErr)
		return
	}
	if req.BotType == "" {
		req.BotType = "chat2sql"
	}

	// request eda to evaluate the EDA Chat2SQL
	go eda.EDAChat2SQL(context.TODO(), req.Question, req.Database, req.ClusterID, req.SessionID, c)

	rsp, err := service.InteractBot(ctx, req, createdBy)
	auditDetails := auditlog.Details{
		auditlog.KeyDescription: "ai in chat2query console",
		auditlog.KeyError:       err,
	}
	auditlog.NewEvent(ctx, auditlog.EventConsoleChat2SQLFromChat2Query).
		SetOperatorType(auditlog.OperatorTypeUser).
		ParseGIN(c).
		SetOperatorType(auditlog.OperatorTypeUser).
		SetOrgID(params.OrgID).SetProjectID(params.ProjectID).
		SetResult(err == nil).
		SetDetails(auditDetails.Build()).
		Submit()
	tilog.Info(ctx, "interact bot result", zap.Any("result", rsp), zap.Any("err", err))

	if err != nil {
		tilog.Error(ctx, "interact bot error", zap.Any("req", req), zap.Any("err", err))
		c.Error(err)
		return
	}
	if rsp.Code == 0 {
		rsp.Code = 200
	}

	c.JSON(http.StatusOK, rsp)
}

func getParam(c *gin.Context) (uint64, uint64, uint64, error) {
	var (
		orgId     uint64
		projectId uint64
		clusterId uint64
	)
	orgIdStr := c.Param("org_id")
	orgId, err := strconv.ParseUint(orgIdStr, 10, 64)
	if err != nil {
		return ERRORID, ERRORID, ERRORID, err
	}

	projectIdStr := c.Param("project_id")
	projectId, err = strconv.ParseUint(projectIdStr, 10, 64)
	if err != nil {
		return ERRORID, ERRORID, ERRORID, err
	}

	clusterIdStr := c.Param("cluster_id")
	if clusterIdStr != "" {
		clusterId, err = strconv.ParseUint(clusterIdStr, 10, 64)
		if err != nil {
			return ERRORID, ERRORID, ERRORID, err
		}
	}
	return orgId, projectId, clusterId, nil
}

func createLog(ctx context.Context, message entity.SqlEditorMessage) error {
	err := service.CreateMessage(message)
	if err != nil {
		tilog.Error(ctx, fmt.Sprintf("create sql file failed.%v+", message))
		return err
	}
	return nil
}

func getUserEmail(c *gin.Context, ctx context.Context) (string, bool) {
	user, ok := c.Get(consts.CONTEXT_CURRENT_USER)
	if !ok {
		tilog.Error(ctx, "user not found!")
		return "", true
	}
	createdBy := user.(*dto.User).Email
	return createdBy, false
}

func getAuth0Token(c *gin.Context, ctx context.Context) (string, bool) {
	token, ok := c.Get(consts.CONTEXT_AUTH0_TOKEN)
	if !ok {
		tilog.Error(ctx, "token not found!")
		return "", false
	}
	return token.(string), true
}

// Check cluster status godoc
// @Summary check cluster status
// @Description check cluster status
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Accept json
// @Produce json
// @Success 200 {object} dto.ClusterStatusRes
// @Router /orgs/{org_id}/projects/{project_id}/clusters/status [get]
func CheckClusterStatus(c *gin.Context) {
	ctx := c.Request.Context()

	clusterIDStr := c.Query("cluster_ids")
	if clusterIDStr == "" {
		c.Error(tierrs.NewBadRequest("cluster_ids is required.", nil))
		return
	}

	var clusterIDs []uint64
	for _, clusterID := range strings.Split(clusterIDStr, ",") {
		clusterIDInt, err := strconv.ParseUint(clusterID, 10, 64)
		if err != nil || clusterIDInt == 0 {
			c.Error(tierrs.NewBadRequest("cluster_id is illegal", err))
			return
		}
		clusterIDs = append(clusterIDs, clusterIDInt)
	}
	result, err := service.GetClusterStatus(ctx, clusterIDs)
	if err != nil {
		logger.LogWithError(ctx, err, "GetClusterStatus failed")
		c.Error(err)
		return
	}

	resJson := &dto.ClusterStatusRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     result,
	}
	c.JSON(http.StatusOK, resJson)
}

// EnterChat2QueryAuditLog godoc
// @Summary enter chat2query audit log
// @Schemes
// @Description enter chat2query audit log
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Accept json
// @Produce json
// @Success 200 {object} dto.GenericSuccessApiResponse
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster}/enter/audit [get]
func AuditLog4EnterChat2Query(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}
	orgId, projectId, clusterId, err := getParam(c)
	if err != nil {
		tilog.Error(ctx, "ERROR: AuditLog4EnterChat2Query-get orgId,projectId,clusterId failed! user:"+createdBy)
		c.Error(err)
		return
	}
	auditDetails := auditlog.Details{
		auditlog.KeyDescription: "enter chat2query",
	}
	auditlog.NewEvent(ctx, auditlog.EventEnterChat2Query).
		SetOperatorType(auditlog.OperatorTypeUser).
		ParseGIN(c).
		SetOperatorType(auditlog.OperatorTypeUser).
		SetOrgID(orgId).SetProjectID(projectId).SetClusterID(clusterId).
		SetResult(true).
		SetDetails(auditDetails.Build()).
		Submit()

	c.JSON(http.StatusOK, dto.Success())
}

// DownloadQueryResultAuditLog godoc
// @Summary download query result audit log
// @Schemes
// @Description download query result audit log
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Accept json
// @Produce json
// @Success 200 {object} dto.GenericSuccessApiResponse
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/download/audit [get]
func AuditLog4DownloadQueryResult(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}
	orgId, projectId, clusterId, err := getParam(c)
	if err != nil {
		tilog.Error(ctx, "ERROR: AuditLog4DownloadQueryResult-get orgId,projectId,clusterId failed! user:"+createdBy)
		c.Error(err)
		return
	}
	auditDetails := auditlog.Details{
		auditlog.KeyDescription: "download query result in chat2query",
	}
	auditlog.NewEvent(ctx, auditlog.EventConsoleQuerySQLFromChat2Query).
		SetOperatorType(auditlog.OperatorTypeUser).
		ParseGIN(c).
		SetOperatorType(auditlog.OperatorTypeUser).
		SetOrgID(orgId).SetProjectID(projectId).SetClusterID(clusterId).
		SetResult(true).
		SetDetails(auditDetails.Build()).
		Submit()

	c.JSON(http.StatusOK, dto.Success())
}

// ServerlessPermission2Entry godoc
// @Summary serverless permission to entrance
// @Schemes
// @Description serverless permission to entrance
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Accept json
// @Produce json
// @Success 200 {object} dto.ServerlessPermission2EntryRes
// @Router /orgs/{org_id}/projects/{project_id}/permission/entry [get]
func ServerlessPermission2Entry(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}

	clusterIDStr := c.Query("cluster_ids")
	if clusterIDStr == "" {
		c.Error(tierrs.NewBadRequest("cluster_ids is required.", nil))
		return
	}

	var clusterIDs []uint64
	for _, clusterID := range strings.Split(clusterIDStr, ",") {
		clusterIDInt, err := strconv.ParseUint(clusterID, 10, 64)
		if err != nil || clusterIDInt == 0 {
			c.Error(tierrs.NewBadRequest("cluster_id is illegal", err))
			return
		}
		clusterIDs = append(clusterIDs, clusterIDInt)
	}

	result, err := service.ServerlessPermission2Entry(ctx, clusterIDs)
	if err != nil {
		tilog.Error(ctx, "ServerlessPermission2Entry failed", zap.String("user", createdBy), zap.Error(err))
		c.Error(err)
		return
	}

	resJson := &dto.ServerlessPermission2EntryRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     result,
	}

	c.JSON(http.StatusOK, resJson)
}

// Sample Data Fortune500 impoty godoc
// @Summary import fortune500
// @Description import fortune500
// @Tags sqleditor
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param cluster_id path string true "cluster ID"
// @Accept json
// @Produce json
// @Success 200 {object} dto.SampleInitializeRes
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/sample/initialize [post]
func SampleDataInitialize(c *gin.Context) {
	ctx := c.Request.Context()
	createdBy, authErr := getUserEmail(c, ctx)
	if authErr {
		c.Error(errs.ErrUnauthorized)
		return
	}
	_, projectId, clusterId, err := getParam(c)
	if err != nil {
		tilog.Error(ctx, "ERROR:get orgId,projectId,clusterId failed!user"+createdBy)
		c.Error(err)
		return
	}
	// Get user auth0 token
	auth0Token, ok := getAuth0Token(c, ctx)
	if !ok {
		c.Error(errs.ErrUnauthorized)
		return
	}

	tilog.Info(ctx, fmt.Sprintf("SampleDataInitialize payload:%+v,user:%s", clusterId, createdBy))

	res, bizErr := service.SampleDataInitialize(ctx, projectId, clusterId, createdBy, auth0Token)
	if bizErr != nil {
		tilog.Error(ctx, "SampleDataInitialize failed!", zap.Error(bizErr))
		if strings.Contains(bizErr.Error(), "Access denied") {
			c.JSON(http.StatusBadRequest, &dto.BasicRes{
				Code:    http.StatusBadRequest,
				Message: fmt.Sprintf("%s", errs.ErrUnauthorizedImportSampleData),
			})
		} else {
			c.Error(bizErr)
		}
		return
	}

	resJson := &dto.SampleInitializeRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     res,
	}

	c.JSON(http.StatusOK, resJson)
}

func bindJsonParam(ctx *gin.Context, input interface{}) error {
	err := ctx.ShouldBindJSON(input)
	if err != nil {
		err = tierrs.NewBadRequest("invalid parameter", err)
	}
	return err
}
