// Package adminapigrpc add by blacknc <blacknc@163.com> at 2024/01/05 15:34:29
package adminapigrpc

import (
	"context"
	"dataservices/commons/constants"
	"dataservices/commons/ginparser"
	"dataservices/commons/pagetoken"
	adminapiv1beta1 "dataservices/idl/pbgen/proto/v1beta1"
	"dataservices/internal/entity"
	service "dataservices/internal/service/central"
	"fmt"
	"math"
	"strconv"

	"github.com/tidbcloud/tiinf/errs"
)

type DeploymentsService struct {
	adminapiv1beta1.UnimplementedDeploymentsServiceServer
}

func (s *DeploymentsService) CreateDeployment(ctx context.Context, req *adminapiv1beta1.CreateDeploymentRequest) (*adminapiv1beta1.Deployment, error) {
	innerAppId, err := ginparser.GetRequestInnerAppIdGrpc(ctx)
	if err != nil {
		return nil, errs.NewBadRequest("get innerAppId failed", err)
	}

	_, createdBy, err := ginparser.GetCreatedByFromGrpc(ctx)
	if err != nil {
		return nil, errs.NewBadRequest("invalid request public key", err)
	}

	dataapp, err := service.GetDataApp(ctx, innerAppId)
	if err != nil {
		return nil, err
	}
	if dataapp.AppType == entity.DataAppTypeChat2Query {
		return nil, errs.NewBadRequest("Chat2Query app does not support deployment operations", nil)
	}

	res, err := service.TriggerDataAppDeployFromUI(ctx, innerAppId, createdBy, req.Deployment.Description)
	if err != nil {
		return nil, err
	}
	return convertDeploymentLogToProtoFormat(dataapp, res), nil
}

func (s *DeploymentsService) ListDeployments(ctx context.Context, req *adminapiv1beta1.ListDeploymentsRequest) (*adminapiv1beta1.ListDeploymentsResponse, error) {
	innerAppId, err := ginparser.GetRequestInnerAppIdGrpc(ctx)
	if err != nil {
		return nil, errs.NewBadRequest("get innerAppId failed", err)
	}

	token := pagetoken.PageToken[uint64]{}
	if req.PageToken != "" {
		if err := token.DecodeFromString(req.PageToken); err != nil {
			return nil, errs.NewBadRequest("invalid page token", err)
		}
	} else {
		token.Prev = math.MaxUint64
	}

	pageSize := req.PageSize
	if req.PageSize == 0 {
		pageSize = constants.AdminApiDefaultPageSize
	} else if req.PageSize < 0 || req.PageSize > constants.AdminApiDefaultPageSize {
		return nil, errs.NewBadRequest(fmt.Sprintf("invalid page size, the effective range is: [0,%d]", constants.AdminApiDefaultPageSize), nil)
	}

	dataapp, err := service.GetDataApp(ctx, innerAppId)
	if err != nil {
		return nil, err
	}

	deploymentLogs, err := service.ListDataAppDeployLogsWithPager(ctx, innerAppId, token.Prev, int(pageSize)+1)
	if err != nil {
		return nil, err
	}

	resp := new(adminapiv1beta1.ListDeploymentsResponse)
	if len(deploymentLogs) == int(pageSize)+1 {
		deploymentLogs = deploymentLogs[:pageSize]
		token.Prev = deploymentLogs[pageSize-1].ID
		resp.NextPageToken, _ = token.EncodeToString()
	}

	for _, deploymentLog := range deploymentLogs {
		resp.Deployments = append(resp.Deployments, convertDeploymentLogToProtoFormat(dataapp, deploymentLog))
	}
	return resp, nil
}

func (s *DeploymentsService) GetDeployment(ctx context.Context, req *adminapiv1beta1.GetDeploymentRequest) (*adminapiv1beta1.Deployment, error) {
	innerAppId, err := ginparser.GetRequestInnerAppIdGrpc(ctx)
	if err != nil {
		return nil, errs.NewBadRequest("get innerAppId failed", err)
	}
	params := ginparser.ParseAdminApiPathParameters(ctx, req.Name)
	if params.DeploymentId == 0 {
		return nil, errs.NewBadRequest("invalid deployment id", nil)
	}
	dataapp, err := service.GetDataApp(ctx, innerAppId)
	if err != nil {
		return nil, err
	}
	deploymentLog, err := service.GetDataAppDeployLogs(ctx, innerAppId, params.DeploymentId)
	if err != nil {
		return nil, err
	}
	return convertDeploymentLogToProtoFormat(dataapp, deploymentLog), nil
}

func convertDeploymentLogToProtoFormat(dataapp *entity.DataApp, deploymentLog *entity.DataAppDeploymentLog) *adminapiv1beta1.Deployment {
	result := &adminapiv1beta1.Deployment{
		DeploymentId: strconv.FormatUint(deploymentLog.ID, 10),
		// Unique name of deployment, format: dataApps/{data_app}/deployments/{endpoint}
		Name: fmt.Sprintf("dataApps/%s/deployments/%d", dataapp.ExternalAppID, deploymentLog.ID),
		// deployment description
		Description: deploymentLog.Name,
		// deployment origin
		Origin: string(deploymentLog.Origin),
		// deployment creator
		CreatedBy: deploymentLog.CreatedBy,
		// deployment status
		Status: string(deploymentLog.Status),
		// deployment error message
		StatusErrorMessage: deploymentLog.StatusErrorMessage,
		// deployment create time
		CreatedAt: deploymentLog.CreatedAt.FormatToStr(),
		// deployment launch time
		DeployedAt: deploymentLog.RanAt.FormatToStr(),
		// deployment finished time
		FinishedAt: deploymentLog.FinishedAt.FormatToStr(),
	}
	if deploymentLog.Origin != entity.DataAppDeploymentLogOriginUI {
		result.OriginInfo = &adminapiv1beta1.Deployment_OriginInfo{
			Commit:  deploymentLog.CommitID,
			DiffUrl: deploymentLog.DiffUrl,
		}
	}
	return result
}
