package controller

import (
	"context"
	"dataservices/commons/errs"
	"dataservices/commons/ginparser"
	"dataservices/commons/logger"
	dto "dataservices/internal/dto/dataapi"
	dto2 "dataservices/internal/dto/dataapi_gateway"
	"dataservices/internal/entity"
	service "dataservices/internal/service/central"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/tidbcloud/dataservices/idl/pbgen/proto/dataapi"
	tierr "github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"net/http"
	"strconv"
)

// @Time    : 2023/1/6 11:05 AM
// @Author  : lvhongmeng

// CreateSession godoc
// @Summary create session
// @Schemes
// @Description create session
// @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.CreateSessionParam true "request_body"
// @Accept json
// @Produce json
// @Success 200 {object} dto.CreateSessionRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/sessions [post]
func CreateSession(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}/sessions[post]-CreateSession 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}/sessions[post]-CreateSession getAuth0Token failed", zap.String("user", createdBy))
		c.Error(errs.ErrUnauthorized)
		return
	}

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

	res, err := service.CreateSession(ctx, dto.CreateSessionIn{
		ProjectId:  params.ProjectID,
		ClusterId:  input.ClusterId,
		Database:   input.Database,
		Auth0Token: auth0Token,
		CreatedBy:  createdBy,
	})
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/sessions[post]-CreateSession execution failed", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}

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

// Statements godoc
// @Summary run endpoint sql
// @Schemes
// @Description run endpoint sql
// @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.StatementParamV2 true "request_body"
// @Accept json
// @Produce json
// @Success 200 {object} dto.StatementsRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/statements [post]
func Statements(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}/statements[post]-Statements 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}/statements[post]-Statements getAuth0Token failed", zap.String("user", createdBy))
		c.Error(errs.ErrUnauthorized)
		return
	}

	input := entity.StatementParamV2{}
	err := params.BindParam(&input)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/statements[post]-Statements param bind failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}
	clusterID, err := strconv.ParseUint(input.ClusterIdStr, 10, 64)
	if err != nil || clusterID == 0 {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/statements[post]-Statements failed to parse cluster id", zap.Error(err), zap.String("cluster_id", input.ClusterIdStr), zap.String("user", createdBy))
		c.Error(tierr.NewBadRequest("Invalid cluster id", err))
		return
	}
	input.ClusterId = clusterID
	input.ProjectId = params.ProjectID
	input.OrgId = params.OrgID
	input.DataappId = params.AppID
	input.Auth0Token = auth0Token
	input.CreatedBy = createdBy
	input.Settings.CacheEnabled = 0
	tilog.Info(ctx, "Statements payload", zap.Any("input", input), zap.String("user", createdBy))

	res, err := service.Statements(ctx, createdBy, input)
	if err != nil {
		logger.LogWithError(ctx, err, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/endpoints/{endpoint_id}/statements[post]-Statements exec failed!", zap.String("user", createdBy))
		c.Error(err)
		return
	}

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

// ShowDBs godoc
// @Summary show db list
// @Schemes
// @Description show db list
// @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 cluster_id path string true "cluster ID"
// @Param show_system_db query string false "value: true or false"
// @Accept json
// @Produce json
// @Success 200 {object} dto.ShowDBsRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/clusters/{cluster_id}/dbs [get]
func ShowDBs(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_APP_ID, ginparser.PARAM_CLUSTER_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}/clusters/{cluster_id}/dbs[get]-ShowDBs 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}/clusters/{cluster_id}/dbs[get]-ShowDBs-Statements getAuth0Token failed", zap.String("user", createdBy))
		c.Error(errs.ErrUnauthorized)
		return
	}

	showSystemDB := c.Query("show_system_db")
	tilog.Info(ctx, fmt.Sprintf("ShowDBs payload: show_system_db: %+v, user:%s", showSystemDB, createdBy))

	input := entity.ShowDBParam{
		ProjectId:  params.ProjectID,
		DataappId:  params.AppID,
		ClusterId:  params.ClusterID,
		Auth0Token: auth0Token,
		ShowSysDB:  showSystemDB,
	}

	credential := dto2.Credential{
		Type: dataapi.CrtType_AUTH0.String(),
		Key:  auth0Token,
	}
	res, err := service.ShowDBs(ctx, input, credential)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/clusters/{cluster_id}/dbs[get]-ShowDBs exec failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}

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

}

// ShowTables godoc
// @Summary show table list
// @Schemes
// @Description show table list
// @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 cluster_id path string true "cluster ID"
// @Param db_name path string true "db name"
// @Accept json
// @Produce json
// @Success 200 {object} dto.ShowTablesRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/clusters/{cluster_id}/dbs/{db_name}/tables [get]
func ShowTables(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_APP_ID, ginparser.PARAM_CLUSTER_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}/clusters/{cluster_id}/dbs/{db_name}/tables[get]-ShowTables param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}
	dbName := c.Param("db_name")
	// 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}/clusters/{cluster_id}/dbs/{db_name}/tables[get]-ShowTables getAuth0Token failed", zap.String("user", createdBy))
		c.Error(errs.ErrUnauthorized)
		return
	}

	input := entity.ShowTableParam{
		ProjectId:  params.ProjectID,
		DataappId:  params.AppID,
		ClusterId:  params.ClusterID,
		Auth0Token: auth0Token,
		DBName:     dbName,
	}

	res, err := service.ShowTables(ctx, input)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/clusters/{cluster_id}/dbs/{db_name}/tables[get]-ShowTables exec failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}

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

// ShowVectorFields godoc
// @Summary show vector field list
// @Schemes
// @Description show vector field list
// @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 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.ShowVectorFieldsRes
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/clusters/{cluster_id}/dbs/{db_name}/tables/{table_name}/vector/fields [get]
func ShowVectorFields(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_ID, ginparser.PARAM_APP_ID, ginparser.PARAM_CLUSTER_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}/clusters/{cluster_id}/dbs/{db_name}/tables/{table_name}/vector/fields[get]-ShowVectorFields param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		c.Error(validateErr)
		return
	}
	dbName := c.Param("db_name")
	tableName := c.Param("table_name")
	// 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}/clusters/{cluster_id}/dbs/{db_name}/tables/{table_name}/vector/fields[get]-ShowVectorFields getAuth0Token failed", zap.String("user", createdBy))
		c.Error(errs.ErrUnauthorized)
		return
	}

	input := entity.ShowVectorFieldParam{
		ProjectId:  params.ProjectID,
		DataappId:  params.AppID,
		ClusterId:  params.ClusterID,
		Auth0Token: auth0Token,
		DBName:     dbName,
		TableName:  tableName,
	}

	res, err := service.ShowVectorFields(ctx, input)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/clusters/{cluster_id}/dbs/{db_name}/tables/{table_name}/vector/fields[get]-ShowVectorFields exec failed!", zap.Error(err), zap.String("user", createdBy))
		c.Error(err)
		return
	}
	respJson := &dto.ShowVectorFieldsRes{
		BasicRes: dto.DefaultSuccessBasicRes(),
		Data:     res,
	}
	c.JSON(http.StatusOK, respJson)
}

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