package controller

import (
	"dataservices/commons/ginparser"
	"dataservices/commons/utils"
	dto "dataservices/internal/dto/dataapi"
	dto2 "dataservices/internal/dto/dataapi_gateway"
	"dataservices/internal/entity"
	service "dataservices/internal/service/central"
	"net/http"
	"regexp"
	"strconv"
	"strings"

	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	"github.com/gin-gonic/gin"
)

// List Endpoints godoc
// @Summary list endpoints
// @Description list endpoints
// @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"
// @Accept json
// @Produce json
// @Success 200 {object} dto.ListEndpointRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints [get]
func ListEndpoints(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)
	createdBy, _ := params.GetUserEmail()
	validateErr := params.ValidateUrl(validateKeys)
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints[get]-ListEndpoints param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}
	resp, err := service.ListEndpoints(ctx, params.AppID)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints[get]-ListEndpoints execution failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}
	resJson := &dto.ListEndpointRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     dto.ConvertEndpointList(resp),
	}

	c.JSON(http.StatusOK, resJson)
}

// Create Endpoints godoc
// @Summary create endpoints
// @Description create endpoints
// @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 request_body body entity.CreateEndpointParam true "request_body"
// @Accept json
// @Produce json
// @Success 200 {object} dto.CreateEndpointRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints [post]
func CreateEndpoint(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)
	createdBy, _ := params.GetUserEmail()
	validateErr := params.ValidateUrl(validateKeys)
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints[post]-CreateEndpoint param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}

	input := entity.CreateEndpointParam{}
	err := params.BindParam(&input)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints[post]-CreateEndpoint param bind failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	} else if err = input.Parse(); err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints[post]-CreateEndpoint param valid failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}
	tilog.Info(ctx, "CreateEndpoint payload", zap.Any("input", input), zap.String("user", createdBy))

	resp, err := service.SaveEndpoint(ctx, params.AppID, input, createdBy, nil)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints[post]-CreateEndpoint execution failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}

	data := service.ConvertEndpoint(ctx, resp, &createdBy)
	regionSuccessfulDomainsMap, err := service.GetRegionSuccessfulDomainsMap(ctx, resp.DataappId)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints[post]-CreateEndpoint get custom domain failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}
	serviceUrl, err := service.GetAPPServiceUrl(ctx, resp.DataappId, input.ClusterId, regionSuccessfulDomainsMap)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints[post]-CreateEndpoint GetAPPServiceUrl failed!", zap.Error(err), zap.Uint64("app_id", resp.DataappId), zap.String("user", createdBy))
	}
	data.ServiceUrl = serviceUrl
	data.HasModified = true

	resJson := &dto.CreateEndpointRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Endpoint: data,
	}

	c.JSON(http.StatusOK, resJson)
}

// Update Endpoints godoc
// @Summary update endpoints
// @Description update endpoints
// @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 endpoint_id path string true "endpoint ID"
// @Param request_body body entity.CreateEndpointParam true "request_body"
// @Accept json
// @Produce json
// @Success 200 {object} dto.UpdateEndpointRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id} [put]
func UpdateEndpoint(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_APP_ID, ginparser.PARAM_ENDPOINT_ID}
	params := ginparser.NewCommonParamsByGin(c)
	createdBy, _ := params.GetUserEmail()
	validateErr := params.ValidateUrl(validateKeys)
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}[put]-UpdateEndpoint param validate failed", zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}
	input := entity.CreateEndpointParam{}
	err := params.BindParam(&input)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}[put]-UpdateEndpoint param bind failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	} else if err = input.Parse(); err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}[put]-UpdateEndpoint param valid failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}
	input.EndpointID = params.EndpointID
	tilog.Info(ctx, "UpdateEndpoint payload", zap.Any("input", input), zap.String("user", createdBy))

	var ferrs []dto.FieldError
	endpoint, err := service.SaveEndpoint(ctx, params.AppID, input, createdBy, nil)
	if errs.IsError(err, service.ErrInvalidEndpointPathOrMethod) {
		ferrs = append(ferrs, dto.FieldError{
			Field: "path",
			Error: err.Error(),
		})
	} else if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}[put]-UpdateEndpoint execution failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}

	if input.EndpointID > 0 && input.Vid == 0 {
		endpoint, err = service.GetEndpoint(ctx, input.EndpointID)
		if err != nil {
			tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}[put]-UpdateEndpoint failed to get endpoint", zap.Error(err), zap.Uint64("endpoint_id", input.EndpointID), zap.String("user", createdBy))
			c.Error(err)
			return
		}
	}
	formatedEndpoint := service.ConvertEndpoint(ctx, endpoint, &createdBy)
	hasModified, err := service.HasEndpointModified(ctx, endpoint)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}[put]-UpdateEndpoint HasEndpointModified failed!", zap.Error(err), zap.Uint64("endpoint_id", endpoint.ID), zap.String("user", createdBy))
		formatedEndpoint.HasModified = true
	} else {
		formatedEndpoint.HasModified = hasModified
	}
	resJson := &dto.UpdateEndpointRes{
		BasicRes:    dto.DefaultSuccessBasicRes(),
		Endpoint:    formatedEndpoint,
		FieldErrors: ferrs,
	}
	c.JSON(http.StatusOK, resJson)
}

// Patch Endpoints godoc
// @Summary patch endpoints
// @Description patch endpoints
// @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 endpoint_id path string true "endpoint ID"
// @Param request_body body entity.CreateEndpointParam true "request_body"
// @Accept json
// @Produce json
// @Success 200 {object} dto.UpdateEndpointRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id} [patch]
func PatchEndpoint(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_APP_ID, ginparser.PARAM_ENDPOINT_ID}
	params := ginparser.NewCommonParamsByGin(c)
	createdBy, _ := params.GetUserEmail()
	validateErr := params.ValidateUrl(validateKeys)
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}[patch]-PatchEndpoint param validate failed", zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}
	input := entity.CreateEndpointParam{}
	err := params.BindParam(&input)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}[patch]-PatchEndpoint param bind failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	} else if err = input.Parse(); err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}[patch]-PatchEndpoint param valid failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}
	input.EndpointID = params.EndpointID
	tilog.Info(ctx, "PatchEndpoint payload", zap.Any("input", input), zap.String("user", createdBy))

	_, err = service.SaveEndpoint(ctx, params.AppID, input, createdBy, params.JsonParams)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}[patch]-PatchEndpoint execution failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}
	endpoint, err := service.GetEndpoint(ctx, input.EndpointID)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}[patch]-PatchEndpoint failed to get endpoint", zap.Error(err), zap.Uint64("endpoint_id", input.EndpointID), zap.String("user", createdBy))
		c.Error(err)
		return
	}
	formatedEndpoint := service.ConvertEndpoint(ctx, endpoint, &createdBy)
	hasModified, err := service.HasEndpointModified(ctx, endpoint)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}[patch]-PatchEndpoint HasEndpointModified failed!", zap.Error(err), zap.Uint64("endpoint_id", endpoint.ID), zap.String("user", createdBy))
		formatedEndpoint.HasModified = true
	} else {
		formatedEndpoint.HasModified = hasModified
	}
	resJson := &dto.UpdateEndpointRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Endpoint: formatedEndpoint,
	}
	c.JSON(http.StatusOK, resJson)
}

// Dump Endpoints godoc
// @Summary dump endpoints
// @Description dump endpoints
// @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 endpoint_id path string true "endpoint ID"
// @Accept json
// @Produce json
// @Success 200 {object} dto.DumpEndpointRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/dump [post]
func DumpEndpoint(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_APP_ID, ginparser.PARAM_ENDPOINT_ID}
	params := ginparser.NewCommonParamsByGin(c)
	createdBy, _ := params.GetUserEmail()
	validateErr := params.ValidateUrl(validateKeys)
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/dump[post]-DumpEndpoint param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}

	endpoint, err := service.GetEndpoint(ctx, params.EndpointID)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/dump[post]-DumpEndpoint verify online endpoint failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}
	if endpoint.Endpoint != "" && endpoint.Method != "" {
		if strings.HasSuffix(endpoint.Endpoint, "}") {
			endpoint.Endpoint += "/dump_" + utils.RandStr(6)
		} else {
			endpoint.Endpoint += "_dump_" + utils.RandStr(6)
		}
	}
	endpoint.Name = endpoint.Name + "_copy"
	resp, err := service.DumpEndpoint(ctx, endpoint, createdBy)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/dump[post]-DumpEndpoint execution failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}

	data := service.ConvertEndpoint(ctx, resp, nil)
	regionSuccessfulDomainsMap, err := service.GetRegionSuccessfulDomainsMap(ctx, resp.DataappId)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/dump[post]-DumpEndpoint get custom domain failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}
	serviceUrl, err := service.GetAPPServiceUrl(ctx, resp.DataappId, endpoint.ClusterId, regionSuccessfulDomainsMap)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/dump[post]-DumpEndpoint GetAPPServiceUrl failed!", zap.Error(err), zap.Uint64("app_id", resp.DataappId), zap.String("user", createdBy))
	}
	data.ServiceUrl = serviceUrl

	resJson := &dto.DumpEndpointRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Endpoint: data,
	}
	c.JSON(http.StatusOK, resJson)
}

// Delete Endpoints godoc
// @Summary delete endpoints
// @Description delete endpoints
// @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 endpoint_id path string true "endpoint ID"
// @Accept json
// @Produce json
// @Success 200 {object} dto.GenericSuccessApiResponse
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id} [delete]
func DeleteEndpoint(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_APP_ID, ginparser.PARAM_ENDPOINT_ID}
	params := ginparser.NewCommonParamsByGin(c)
	createdBy, _ := params.GetUserEmail()
	validateErr := params.ValidateUrl(validateKeys)
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}[delete]-DeleteEndpoint param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}

	err := service.DeleteEndpoint(ctx, params.AppID, params.EndpointID, createdBy)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}[delete]-DeleteEndpoint execution failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}
	c.JSON(http.StatusOK, dto.Success())
}

// Get Endpoint godoc
// @Summary get endpoint by id
// @Description get endpoint by id
// @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 endpoint_id path string true "endpoint ID"
// @Accept json
// @Produce json
// @Success 200 {object} dto.GetEndpointRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id} [get]
func GetEndpoint(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_APP_ID, ginparser.PARAM_ENDPOINT_ID}
	params := ginparser.NewCommonParamsByGin(c)
	createdBy, _ := params.GetUserEmail()
	validateErr := params.ValidateUrl(validateKeys)
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}[get]-GetEndpoint param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}

	resp, err := service.GetEndpoint(ctx, params.EndpointID)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}[get]-GetEndpoint execution failed!", zap.Error(err), zap.Uint64("endpoint_id", params.EndpointID), zap.String("user", createdBy))
		c.Error(err)
		return
	}

	data := service.ConvertEndpoint(ctx, resp, &createdBy)
	regionSuccessfulDomainsMap, err := service.GetRegionSuccessfulDomainsMap(ctx, resp.DataappId)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}[get]-GetEndpoint get custom domain failed!", zap.Error(err), zap.Uint64("endpoint_id", params.EndpointID), zap.String("user", createdBy))
		c.Error(err)
		return
	}
	serviceUrl, err := service.GetAPPServiceUrl(ctx, resp.DataappId, resp.ClusterId, regionSuccessfulDomainsMap)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}[get]-GetEndpoint GetAPPServiceUrl failed!", zap.Error(err), zap.Uint64("app_id", resp.DataappId), zap.String("user", createdBy))
	} else {
		data.ServiceUrl = serviceUrl
	}

	// check endpoint has been modified, used to control deploy button status, default is true
	hasModified, err := service.HasEndpointModified(ctx, resp)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}[get]-GetEndpoint HasEndpointModified failed!", zap.Error(err), zap.Uint64("app_id", resp.DataappId), zap.Uint64("endpoint_id", resp.ID), zap.String("user", createdBy))
		data.HasModified = true
	} else {
		data.HasModified = hasModified
	}

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

// Test Endpoint godoc
// @Summary test endpoint
// @Description test endpoint
// @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 endpoint_id path string true "endpoint ID"
// @Param request_body body entity.TestEndpointParamV2 true "request_body"
// @Accept json
// @Produce json
// @Success 200 {object} dto.TestEndpointRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/test [post]
func PublishTest(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)
	createdBy, _ := params.GetUserEmail()
	validateErr := params.ValidateUrl(validateKeys)
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/test[post]-PublishTest param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}

	// Get user auth0 token
	auth0Token, ok := getAuth0Token(c, ctx)
	if !ok {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/test[post]-PublishTest getAuth0Token failed!", zap.String("user", createdBy))
		c.Error(errs.ErrUnauthorized)
		return
	}

	var input entity.TestEndpointParamV2
	err := params.BindParam(&input)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/test[post]-PublishTest param bind failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}
	input.CredKey = auth0Token
	input.CredType = dto2.TypeCredentialAuth0
	input.ProjectId = params.ProjectID
	input.AppId = params.AppID
	input.EndpointId = params.EndpointID
	input.Operator = createdBy

	res, err := service.PublishTest(ctx, input)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/test[post]-PublishTest execution failed!", zap.Error(err), zap.Uint64("endpoint_id", params.EndpointID), zap.String("user", createdBy))
		c.Error(err)
		return
	}
	c.JSON(http.StatusOK, res)
}

// Get Endpoint Code Example godoc
// @Summary get endpoint code example
// @Description get endpoint code example
// @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 endpoint_id path string true "endpoint ID"
// @Param auth_type query string false "enum: basic/digest, default value is digest"
// @Param language query string false "enum: curl/js, default value is curl"
// @Accept json
// @Produce json
// @Success 200 {object} dto.TestEndpointRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/example [get]
func CodeExample(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_APP_ID, ginparser.PARAM_ENDPOINT_ID}
	params := ginparser.NewCommonParamsByGin(c)
	createdBy, _ := params.GetUserEmail()
	validateErr := params.ValidateUrl(validateKeys)
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/example[get]-CodeExample param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}

	clusterID, _ := c.GetQuery("cluster_id")
	database, _ := c.GetQuery("database")
	authType := c.DefaultQuery("auth_type", "digest")
	if authType != "basic" && authType != "digest" {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/example[get]-CodeExample invalid auth type", zap.String("authType", authType), zap.String("user", createdBy))
		c.Error(errs.NewBadRequest("invalid auth_type", nil))
		return
	}
	language := c.DefaultQuery("language", "curl")
	if language != "curl" && language != "js" {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/example[get]-CodeExample invalid language", zap.String("language", language), zap.String("user", createdBy))
		c.Error(errs.NewBadRequest("invalid language", nil))
		return
	}
	res, err := service.CodeExample(ctx, params.EndpointID, entity.CodeExampleInput{
		ClusterID: clusterID,
		Database:  database,
		AuthType:  authType,
		Language:  language,
		Operator:  createdBy,
	})
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/example[get]-CodeExample execution failed!", zap.Error(err), zap.Uint64("endpoint_id", params.EndpointID), zap.String("user", createdBy))
		c.Error(err)
		return
	}

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

// Get Endpoint Base Url godoc
// @Summary get endpoint base url
// @Description get endpoint base url
// @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 endpoint_id path string true "endpoint ID"
// @Param cluster_id query string true "cluster ID"
// @Accept json
// @Produce json
// @Success 200 {object} dto.EndpointBaseUrlRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/baseurl [get]
func EndpointBaseUrl(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_APP_ID, ginparser.PARAM_ENDPOINT_ID}
	params := ginparser.NewCommonParamsByGin(c)
	createdBy, _ := params.GetUserEmail()
	validateErr := params.ValidateUrl(validateKeys)
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/baseurl[get]-EndpointBaseUrl param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}

	var clusterID uint64
	var err error
	clusterIDStr, _ := c.GetQuery("cluster_id")
	if clusterIDStr != "" {
		clusterID, err = strconv.ParseUint(clusterIDStr, 10, 64)
		if err != nil {
			err = errs.NewBadRequest("invalid cluster_id", err)
			tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/baseurl[get]-EndpointBaseUrl got invalid arguments", zap.String("cluster_id", clusterIDStr), zap.Error(err), zap.String("user", createdBy))
			return
		}
	}
	regionSuccessfulDomainsMap, err := service.GetRegionSuccessfulDomainsMap(ctx, params.AppID)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/baseurl[get]-EndpointBaseUrl get custom domain failed", zap.String("cluster_id", clusterIDStr), zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}
	serviceUrl, err := service.GetAPPServiceUrl(ctx, params.AppID, clusterID, regionSuccessfulDomainsMap)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/baseurl[get]-EndpointBaseUrl execution failed", zap.String("cluster_id", clusterIDStr), zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}

	respJson := &dto.EndpointBaseUrlRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     serviceUrl,
	}
	c.JSON(http.StatusOK, respJson)
}

// Create Auto-Generate Endpoints godoc
// @Summary create auto generate endpoints
// @Description create auto generate endpoints
// @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 request_body body entity.AutoGenerateEndpoint true "request_body"
// @Accept json
// @Produce json
// @Success 200 {object} dto.CreateAutoGenerateEndpointRes
// @Router /orgs/:org_id/projects/:project_id/apps/:app_id/auto/endpoints [post]
func CreateAutoGenerateEndpoints(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)
	createdBy, _ := params.GetUserEmail()
	validateErr := params.ValidateUrl(validateKeys)
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/:org_id/projects/:project_id/apps/:app_id/auto/endpoints[post]-CreateAutoGenerateEndpoints param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}

	input := entity.AutoGenerateEndpoint{}
	err := params.BindParam(&input)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/:org_id/projects/:project_id/apps/:app_id/auto/endpoints[post]-CreateAutoGenerateEndpoints param bind failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}
	tilog.Info(ctx, "CreateAutoGenerateEndpoints payload", zap.Any("input", input), zap.String("user", createdBy))

	// Get user auth0 token
	auth0Token, ok := getAuth0Token(c, ctx)
	if !ok {
		tilog.Error(ctx, "DS-Central-/orgs/:org_id/projects/:project_id/apps/:app_id/auto/endpoints[post]-CreateAutoGenerateEndpoints getAuth0Token failed", zap.String("user", createdBy))
		c.Error(errs.ErrUnauthorized)
		return
	}
	// region regexp
	reg := regexp.MustCompile("\\((.*?)\\)")
	tempData := reg.FindAllStringSubmatch(input.Region, -1)
	if len(tempData) > 0 {
		if len(tempData[len(tempData)-1]) > 1 {
			input.Region = tempData[len(tempData)-1][1]
			tilog.Info(ctx, "region:"+input.Region)
		} else {
			tilog.Error(ctx, "DS-Central-/orgs/:org_id/projects/:project_id/apps/:app_id/auto/endpoints[post]-CreateAutoGenerateEndpoints region parse failed", zap.Any("app_id", params.AppID), zap.Any("region", input.Region), zap.String("user", createdBy))
			c.Error(errs.ErrInternalServerError)
			return
		}
	} else {
		tilog.Error(ctx, "DS-Central-/orgs/:org_id/projects/:project_id/apps/:app_id/auto/endpoints[post]-CreateAutoGenerateEndpoints region parse failed", zap.Any("app_id", params.AppID), zap.Any("region", input.Region), zap.String("user", createdBy))
		c.Error(errs.ErrInternalServerError)
		return
	}

	res, err := service.CreateAutoGenerateEndpoints(ctx, params.AppID, params.ProjectID, input.ClusterId, auth0Token, createdBy, &input)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/:org_id/projects/:project_id/apps/:app_id/auto/endpoints[post]-CreateAutoGenerateEndpoints execution failed", zap.Any("app_id", params.AppID), zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}

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

// Get endpoint draft diff
// @Summary Get endpoint draft diff
// @Description Get endpoint draft diff
// @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 endpoint_id path string true "endpoint ID"
// @Param diff_type query string false "options: struct/text. default is struct"
// @Param request_body body entity.CreateEndpointParam true "request_body"
// @Accept json
// @Produce json
// @Success 200 {object} dto.GetDataAppDeployDiffRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/draft/diff [post]
func GetEndpointDraftDiff(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_APP_ID, ginparser.PARAM_ENDPOINT_ID}
	params := ginparser.NewCommonParamsByGin(c)
	createdBy, _ := params.GetUserEmail()
	validateErr := params.ValidateUrl(validateKeys)
	if validateErr != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/draft/diff[post]-GetEndpointDraftDiff param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}

	diffType := c.DefaultQuery("diff_type", "struct")
	input := entity.CreateEndpointParam{}
	err := params.BindParam(&input)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/draft/diff[post]-GetEndpointDraftDiff param bind failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	} else if err = input.Parse(); err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/draft/diff[post]-GetEndpointDraftDiff param valid failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}
	input.EndpointID = params.EndpointID

	tilog.Info(ctx, "GetEndpointDraftDiff payload", zap.Any("input", input), zap.String("user", createdBy))

	diff, err := service.GetDiffBetweenDraftEndpointAndSubmitData(ctx, params.AppID, &input, entity.SnapshotDiffType(diffType))
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/draft/diff[post]-GetEndpointDraftDiff GetDiffBetweenDraftEndpointAndSubmitData failed", zap.Error(err), zap.String("user", createdBy))
		c.Error(errs.NewBadRequest("failed to get diff of draft endpoint.", err))
		return
	}
	resJson := &dto.GetDataAppDeployDiffRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     diff,
	}
	c.JSON(http.StatusOK, resJson)
}
