package adminapigrpc

import (
	"context"
	"dataservices/commons/constants"
	"dataservices/commons/ginparser"
	"dataservices/commons/pagetoken"
	adminapiv1beta1 "dataservices/idl/pbgen/proto/v1beta1"
	dto "dataservices/internal/dto/dataapi"
	service "dataservices/internal/service/central"
	"fmt"
	"sort"
	"strconv"
	"time"

	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/emptypb"
)

type DataSourceService struct {
	adminapiv1beta1.UnimplementedDataSourcesServiceServer
}

func (s *DataSourceService) CreateDataSource(ctx context.Context, req *adminapiv1beta1.CreateDataSourceRequest) (*adminapiv1beta1.DataSource, error) {
	params := ginparser.ParseAdminApiPathParameters(ctx, req.Parent)
	_, createdBy, err := ginparser.GetCreatedByFromGrpc(ctx)
	if err != nil {
		return nil, errs.NewBadRequest("invalid request public key", err)
	}
	appInfo, err := service.GetDataAppByExtId(ctx, params.ExternalAppId)
	if err != nil {
		return nil, errs.NewBadRequest("get appInfo failed", err)
	}
	clusterID, err := strconv.ParseUint(req.DataSource.ClusterId, 10, 64)
	if err != nil || clusterID == 0 {
		tilog.Error(ctx, "DS-Central-/orgs/{org_id}/projects/{project_id}/apps/{app_id}/clusters[post]-CreateDataSource Invalid params cluster_ids", zap.Error(err), zap.String("user", createdBy))
		return nil, errs.NewBadRequest("CreateDataSource failed. ClusterID ("+req.DataSource.ClusterId+") is invalid.", err)
	}

	var input dto.CreateAppClusterReq

	input.OrgID = appInfo.OrgId
	input.ProjectID = appInfo.ProjectId
	input.DataappID = appInfo.ID
	input.ClusterIDs = []uint64{clusterID}
	input.CreatedBy = createdBy
	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("public_key", createdBy))
		return nil, errs.NewBadRequest("CreateDataSource failed. "+err.Error(), err)
	}
	clusterInfo, err := service.GetAppCluster(ctx, appInfo.ID, clusterID)
	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("public_key", createdBy))
		return nil, errs.NewBadRequest("CreateDataSource failed. "+err.Error(), err)
	}

	return appCluster2GrpcDataSource(params.ExternalAppId, clusterInfo), nil
}

func convertTierType2ClusterType(tierType string) string {
	if tierType == "DEPLOY_TYPE_SHARED" {
		return "Serverless"
	}
	return "Dedicated"
}

func appCluster2GrpcDataSource(externalAppId string, appCluster *dto.AppClusterInfo) *adminapiv1beta1.DataSource {
	return &adminapiv1beta1.DataSource{
		Name:                 fmt.Sprintf("dataApps/%s/dataSources/%d", externalAppId, appCluster.ClusterID),
		ClusterId:            strconv.FormatUint(appCluster.ClusterID, 10),
		ClusterType:          convertTierType2ClusterType(appCluster.TierType),
		ClusterProvider:      appCluster.Cloud,
		ClusterRegion:        appCluster.RegionCode,
		ClusterRegionDisplay: appCluster.Region,
		ClusterDisplay:       appCluster.ClusterName,
		CreatedAt:            appCluster.CreatedAt.Format(time.RFC3339),
	}
}

func (s *DataSourceService) GetDataSource(ctx context.Context, req *adminapiv1beta1.GetDataSourceRequest) (*adminapiv1beta1.DataSource, error) {
	params := ginparser.ParseAdminApiPathParameters(ctx, req.Name)
	if params.ClusterId <= 0 {
		return nil, errs.NewBadRequest("invalid ClusterId resource name", nil)
	}
	innerAppId, err := ginparser.GetRequestInnerAppIdGrpc(ctx)
	if err != nil {
		return nil, errs.NewBadRequest("get innerAppId failed", err)
	}
	appCluster, err := service.GetAppCluster(ctx, innerAppId, params.ClusterId)
	if err != nil {
		return nil, err
	}
	return appCluster2GrpcDataSource(params.ExternalAppId, appCluster), nil
}

func (s *DataSourceService) ListDataSources(ctx context.Context, req *adminapiv1beta1.ListDataSourcesRequest) (*adminapiv1beta1.ListDataSourcesResponse, error) {
	params := ginparser.ParseAdminApiPathParameters(ctx, req.Parent)
	_, createdBy, err := ginparser.GetCreatedByFromGrpc(ctx)
	if err != nil {
		return nil, errs.NewBadRequest("invalid request public key", err)
	}
	token := pagetoken.PageToken[uint64]{}
	if req.PageToken != "" {
		if err := token.DecodeFromString(req.PageToken); err != nil {
			return nil, errs.NewBadRequest("invalid page token", err)
		}
	}
	pageSize := req.PageSize
	if req.PageSize == 0 {
		pageSize = constants.AdminApiDefaultPageSize
	}
	appInfo, err := service.GetDataAppByExtId(ctx, params.ExternalAppId)
	if err != nil {
		return nil, errs.NewBadRequest("get appInfo failed", err)
	}
	inputReq := dto.ListAppClusterReq{DataappID: appInfo.ID}
	clusterList, err := service.ListAppCluster(ctx, inputReq)
	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))
		return nil, errs.NewBadRequest("ListDataSource failed. "+err.Error(), err)
	}
	tilog.Info(ctx, "cluster list", zap.Any("list", clusterList))

	sort.Slice(clusterList, func(i, j int) bool {
		return clusterList[i].ClusterID < clusterList[j].ClusterID
	})

	count := 0
	total := len(clusterList)
	nextToken := pagetoken.PageToken[uint64]{}
	dataSources := make([]*adminapiv1beta1.DataSource, 0, req.PageSize)
	for i, cluster := range clusterList {
		keyId := cluster.ID
		if keyId > token.Prev {
			dataSources = append(dataSources, appCluster2GrpcDataSource(params.ExternalAppId, &cluster))
			count++
			if count >= int(pageSize) && i < total-1 {
				nextToken.Prev = keyId
				break
			}
		}
	}
	resp := adminapiv1beta1.ListDataSourcesResponse{
		DataSources: dataSources,
	}
	if nextToken.Prev > 0 {
		nextPageToken, err := nextToken.EncodeToString()
		if err != nil {
			tilog.Info(ctx, "next page token encode failed", zap.Error(err))
		} else {
			resp.NextPageToken = nextPageToken
		}
	}

	return &resp, nil
}

func (s *DataSourceService) DeleteDataSource(ctx context.Context, req *adminapiv1beta1.DeleteDataSourceRequest) (*emptypb.Empty, error) {
	params := ginparser.ParseAdminApiPathParameters(ctx, req.Name)
	innerAppId, err := ginparser.GetRequestInnerAppIdGrpc(ctx)
	_, createdBy, err := ginparser.GetCreatedByFromGrpc(ctx)
	if err != nil {
		return nil, errs.NewBadRequest("invalid request public key", err)
	}
	tilog.Info(ctx, "cluster id is ", zap.Any("clusterid", params))
	deleteReq := dto.DeleteAppClusterReq{
		ClusterID: params.ClusterId,
		DataappID: innerAppId,
		CreatedBy: createdBy,
	}
	err = service.DeleteAppCluster(ctx, deleteReq)
	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))
		return nil, errs.NewBadRequest("DeleteDataSource failed. "+err.Error(), err)
	}

	return &emptypb.Empty{}, nil
}
