package region

import (
	"cmp"
	"context"
	"slices"

	"github.com/samber/lo"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	openapingv1beta2 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/nextgen/v1beta2"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/apimiddleware/bizerror"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/enum"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func (t serverImpl) ListRegionCapacities(
	ctx context.Context,
	req *openapingv1beta2.ListRegionCapacitiesRequest,
) (*openapingv1beta2.ListRegionCapacitiesResponse, error) {
	ctx, span := tracer.Start(ctx, "ListRegionCapacities")
	defer span.End()

	// Extract request meta.
	reqMeta, err := metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	pName, err := enum.FromString[openapicommonv1beta1.Region_CloudProvider](req.GetCloudProvider())
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid cloud provider")
	}

	if req.ServicePlan != openapicommonv1beta1.ServicePlan_BYOC {
		return nil, bizerr.New(bizerr.ErrCodeParams, "only BYOC is supported")
	}

	orgId := reqMeta.OrgID
	globalReq := &globalngv1.ListRegionCapacitiesRequest{
		OrganizationId:  orgId,
		ServicePlan:     common.CvtGlobalServicePlan(req.GetServicePlan()),
		CloudProviderId: globalcommonv1.CloudProvider_Name(pName),
	}

	// Convert usage request if present
	if req.GetUsage() != nil {
		globalReq.Usage = cvtOpenAPIUsageRequestToGlobal(req.GetUsage())
	}

	resp, err := t.nextgenCli.ListRegionCapacities(ctx, globalReq)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to list region capacities")
	}

	capacities := make([]*openapingv1beta2.ListRegionCapacitiesResponse_RegionCapacity, 0, len(resp.Capacities))
	for _, capacity := range resp.Capacities {
		openapiName, openapiId, cloudProvider, err := common.BuildOpenApiRegionName(
			capacity.Region.CloudProviderId.String(),
			capacity.Region.Id.String(),
		)
		if err != nil {
			return nil, bizerror.NewInternal(err, "failed to build openapi region name")
		}

		openApiRegion := &openapicommonv1beta1.Region{
			Name:          openapiName,
			RegionId:      openapiId,
			CloudProvider: cloudProvider,
			DisplayName:   capacity.Region.GetDisplayName(),
			ServicePlans:  []openapicommonv1beta1.ServicePlan{common.CvtOpenAPIServicePlan(capacity.Region.ServicePlan)},
		}

		capacities = append(capacities, &openapingv1beta2.ListRegionCapacitiesResponse_RegionCapacity{
			Region:          openApiRegion,
			TotalVcpusLimit: capacity.TotalVcpusLimit,
			UsedVcpus:       capacity.UsedVcpus,
			HistoryUsages:   cvtGlobalUsageMetricsToOpenAPI(capacity.HistoryUsages),
		})
	}

	thirdProviders, err := t.accountCli.GetTenantThirdPartyAccountProviders(ctx, orgId)
	if err != nil {
		return nil, bizerr.WrapGrpcError(err, "failed to get third party account providers")
	}
	if len(thirdProviders) > 0 {
		capacities = lo.Filter(capacities, func(c *openapingv1beta2.ListRegionCapacitiesResponse_RegionCapacity, _ int) bool {
			return lo.Contains(thirdProviders, c.GetRegion().GetCloudProvider())
		})
	}

	// Sort capacities by region.name.
	slices.SortFunc(capacities, func(a, b *openapingv1beta2.ListRegionCapacitiesResponse_RegionCapacity) int {
		return cmp.Compare(a.GetRegion().Name, b.GetRegion().Name)
	})

	// pagination
	total := int32(len(capacities))
	pagedCapacities, nextToken, err := common.LocalPagination(capacities, req, ListRegionsPageSize, common.ListMaxPageSize)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to paginate capacities")
	}

	return &openapingv1beta2.ListRegionCapacitiesResponse{
		Capacities:    pagedCapacities,
		TotalSize:     total,
		NextPageToken: nextToken,
	}, nil
}

// cvtOpenAPIUsageRequestToGlobal converts OpenAPI UsageRequest to global service UsageRequest.
func cvtOpenAPIUsageRequestToGlobal(
	usage *openapingv1beta2.ListRegionCapacitiesRequest_UsageRequest,
) *globalngv1.ListRegionCapacitiesRequest_UsageRequest {
	if usage == nil {
		return nil
	}
	return &globalngv1.ListRegionCapacitiesRequest_UsageRequest{
		StartTime: usage.GetStartTime(),
		EndTime:   usage.GetEndTime(),
	}
}

// cvtGlobalUsageMetricsToOpenAPI converts global service UsageMetrics to OpenAPI UsageMetrics.
func cvtGlobalUsageMetricsToOpenAPI(
	metrics []*globalngv1.ListRegionCapacitiesResponse_UsageMetrics,
) []*openapingv1beta2.ListRegionCapacitiesResponse_UsageMetrics {
	if metrics == nil {
		return nil
	}
	return lo.Map(
		metrics,
		func(metric *globalngv1.ListRegionCapacitiesResponse_UsageMetrics, _ int) *openapingv1beta2.ListRegionCapacitiesResponse_UsageMetrics {
			return &openapingv1beta2.ListRegionCapacitiesResponse_UsageMetrics{
				MetricsTime: metric.GetMetricsTime(),
				UsedVcpus:   metric.GetUsedVcpus(),
			}
		},
	)
}
