// Package controller add by blacknc <blacknc@163.com> at 2023/03/21 16:51:34
package controller

import (
	"context"
	"dataservices/commons/grpc"
	"dataservices/commons/ginparser"
	"dataservices/dbhubsdk"
	dto "dataservices/internal/dto/dataapi"
	service "dataservices/internal/service/central"
	"net/http"
	"strconv"
	"time"

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

// Create APP Cluster godoc
// @Summary create app cluster
// @Description create app cluster
// @Tags dataapi
// @Param org_id   path string true "org ID"
// @Param project_id path string true "project ID"
// @Param app_id path string true "dataapp ID"
// @Param request_body body dto.CreateAppClusterReq true "request_body"
// @Accept json
// @Produce json
// @Success 200 {object} dto.CommonApiResp
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/clusters [post]
func CreateAppCluster(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}/clusters[post]-CreateAppCluster param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		commonError(c, validateErr)
		return
	}

	var input dto.CreateAppClusterReq
	err := params.BindParam(&input)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/clusters[post]-CreateAppCluster param bind failed!", zap.Error(err), zap.String("user", createdBy))
		commonError(c, errs.NewBadRequest("Bind Params Failed", err))
		return
	}
	tilog.Info(ctx, "CreateAppCluster payload", zap.Any("input", input), zap.String("user", createdBy))
	for _, clusterIDStr := range input.ClusterIDStrs {
		clusterID, err := strconv.ParseUint(clusterIDStr, 10, 64)
		if err != nil || clusterID == 0 {
			tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/clusters[post]-CreateAppCluster Invalid params cluster_ids", zap.Error(err), zap.String("user", createdBy))
			c.Error(errs.NewBadRequest("Invalid params cluster_ids", err))
			return
		}
		input.ClusterIDs = append(input.ClusterIDs, clusterID)
	}

	input.OrgID = params.OrgID
	input.ProjectID = params.ProjectID
	input.DataappID = params.AppID
	input.CreatedBy = createdBy
	if err = guardCreateAppClusterProvider(ctx, input.OrgID, input.ProjectID, input.ClusterIDs); err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/clusters[post]-CreateAppCluster execution failed!", zap.Error(err), zap.String("user", createdBy))
		commonError(c, err)
		return
	}
	err = service.CreateAppCluster(ctx, input)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/clusters[post]-CreateAppCluster execution failed!", zap.Error(err), zap.String("user", createdBy))
		commonError(c, err)
		return
	}
	c.JSON(http.StatusOK, dto.SuccessV2())
}

func guardCreateAppClusterProvider(ctx context.Context, orgID, projectID uint64, clusterIDs []uint64) error {
	if len(clusterIDs) == 0 {
		return nil
	}

	clusterMap, err := grpc.ListClusterByIds(ctx, orgID, projectID, clusterIDs)
	if err != nil {
		return err
	}

	for _, clusterID := range clusterIDs {
		clusterInfo := clusterMap[clusterID]
		if clusterInfo == nil {
			continue
		}
		if err := service.GuardRegionalCapabilityForCluster(clusterInfo, service.RegionalCapabilityDataSourceBinding); err != nil {
			return err
		}
	}
	return nil
}

// Delete APP Cluster godoc
// @Summary delete app cluster
// @Description delete app cluster
// @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"
// @Accept json
// @Produce json
// @Success 200 {object} dto.CommonApiResp
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/clusters/{cluster_id} [delete]
func DeleteAppCluster(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}[delete]-DeleteAppCluster param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		commonError(c, validateErr)
		return
	}

	req := dto.DeleteAppClusterReq{
		ClusterID: params.ClusterID,
		DataappID: params.AppID,
		CreatedBy: createdBy,
	}
	err := service.DeleteAppCluster(ctx, req)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/clusters/{cluster_id}[delete]-DeleteAppCluster execution failed!", zap.Error(err), zap.String("user", createdBy))
		commonError(c, err)
		return
	}
	c.JSON(http.StatusOK, dto.SuccessV2())
}

// List APP Cluster godoc
// @Summary list app cluster
// @Description list app cluster
// @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.ListAppClusterResp
// @Router /orgs/{org_id}/projects/{project_id}/apps/{app_id}/clusters [get]
func ListAppCluster(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}/clusters[get]-ListAppCluster param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		commonError(c, validateErr)
		return
	}

	req := dto.ListAppClusterReq{
		DataappID: params.AppID,
	}
	clusterList, err := service.ListAppCluster(ctx, req)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/clusters[get]-ListAppCluster execution failed!", zap.Error(err), zap.String("user", createdBy))
		commonError(c, err)
		return
	}
	res := dto.ListAppClusterResp{
		BaseResp: dto.SuccessBaseResp(),
		Data:     clusterList,
	}
	c.JSON(http.StatusOK, res)
}

// Count APP Cluster godoc
// @Summary count app cluster
// @Description count app cluster
// @Tags dataapi
// @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.ClusterLinkedAPPResp
// @Router /orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/apps [get]
func GetClusterLinkedAPP(c *gin.Context) {
	ctx := c.Request.Context()
	validateKeys := &[]string{ginparser.PARAM_ORG_ID, ginparser.PARAM_PROJECT_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}/clusters/{cluster_id}/apps[get]-GetClusterLinkedAPP GetAppClusterCount param validate failed!", zap.Error(validateErr), zap.String("user", createdBy))
		commonError(c, validateErr)
		return
	}

	appList, err := service.ListClusterLinkedAPP(ctx, params.ProjectID, params.ClusterID)
	if err != nil {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/apps[get]-GetClusterLinkedAPP ListClusterLinkedAPP failed!", zap.Error(err), zap.String("user", createdBy))
		commonError(c, err)
		return
	}
	dataAppList := make([]*dto.DataAppInfo, 0, len(appList))
	for _, app := range appList {
		dataAppList = append(dataAppList, service.ConvertDataappInfo(app))
	}
	res := dto.ClusterLinkedAPPResp{
		BaseResp: dto.SuccessBaseResp(),
		Data:     dataAppList,
	}
	c.JSON(http.StatusOK, res)
}

func SyncCluster(c *gin.Context) {
	ctx := c.Request.Context()
	params := struct {
		ClusterIDs []uint64 `json:"cluster_ids"`
	}{}
	if err := c.ShouldBind(&params); err != nil {
		c.Error(errs.NewBadRequest("Bind params failed", err))
		return
	}
	start := time.Now()
	result := service.SyncClusterWithPrivateLink(ctx, params.ClusterIDs)
	tilog.Info(ctx, "SyncCluster complete",
		zap.Any("result", result),
		zap.String("cost", time.Since(start).String()))
	c.JSON(http.StatusOK, result)
}

func commonError(c *gin.Context, err error) {
	c.Error(dto.NewCommonError(err))
}

func GetPlaygroundAccount(c *gin.Context) {
	ctx := c.Request.Context()
	params := ginparser.NewCommonParamsByGin(c)

	tilog.Info(ctx, "GetPlaygroundAccount begin!")
	connect, err := dbhubsdk.Serverless.InfraApi.GetUserMeta(ctx, params.ProjectID, params.ClusterID)
	if err != nil {
		tilog.Error(ctx, "GetPlaygroundAccount-get playground account failed!", zap.Error(err))
		c.JSON(http.StatusInternalServerError, &dto.GetPlaygroundAccountResp{Code: http.StatusInternalServerError, Message: "get plaground account failed!"})
		return
	}

	c.JSON(http.StatusOK, &dto.GetPlaygroundAccountResp{
		Code:             http.StatusOK,
		Message:          "ok",
		Host:             connect.Host,
		Port:             connect.Port,
		Token:            connect.Token,
		ServerlessUser:   connect.ServerlessUser,
		ServerlessPasswd: connect.ServerlessPasswd,
		CreateAt:         connect.CreateAt,
		ClusterId:        connect.ClusterId,
		IsDedicate:       connect.IsDedicate,
	})

}
