package cluster

import (
	"context"
	"fmt"

	"github.com/samber/lo"

	globalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/service/serverless"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func (svc *service) BatchCreateClusters(ctx context.Context, clusters []*entity.Cluster) ([]*entity.Cluster, error) {
	spanName := "ClusterService.BatchCreateClusters"
	ctx, span := serverless.Tracer.Start(ctx, spanName)
	defer span.End()

	if len(clusters) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "no cluster to create")
	}

	// get third party account provider first so business restrictions win over
	// downstream RPC failures and stay consistent with the single-create path.
	thirdProviders, err := svc.accountCli.GetTenantThirdPartyAccountProviders(ctx, clusters[0].OrganizationId)
	if err != nil {
		return nil, bizerr.WrapGrpcError(err, "failed to get third party account providers")
	}
	var alibabaClusterToCheck *entity.Cluster
	for i := range clusters {
		if cerr := checkClusterCreateRestrictions(clusters[i], thirdProviders); cerr != nil {
			return nil, cerr
		}

		if alibabaClusterToCheck == nil &&
			clusters[i] != nil &&
			clusters[i].CloudProvider == openapicommonv1beta1.Region_alicloud &&
			lo.Contains(thirdProviders, openapicommonv1beta1.Region_alicloud) {
			alibabaClusterToCheck = clusters[i]
		}
	}
	if alibabaClusterToCheck != nil {
		if cerr := svc.checkAlibabaCloudMarketplaceServerlessCreate(ctx, alibabaClusterToCheck, thirdProviders); cerr != nil {
			return nil, cerr
		}
	}

	// check cloud providers before creating clusters with global serverless service
	providerResp, err := svc.serverlessCli.ListCloudProviders(ctx, &globalserverlessv1.CloudProviderServiceListRequest{
		OrganizationId: clusters[0].OrganizationId,
	})

	if err != nil {
		return clusters, bizerr.WrapGrpcError(err, "failed to get cloud providers")
	}

	// Key by cloud provider and region.
	providers := make(map[string]map[string]struct{}, len(providerResp.GetCloudProviders()))
	for _, provider := range providerResp.GetCloudProviders() {
		providers[provider.Id.String()] = make(map[string]struct{}, len(provider.GetRegions()))
		for _, region := range provider.GetRegions() {
			providers[provider.Id.String()][region.Id.String()] = struct{}{}
		}
	}

	// transaction
	if err := svc.transaction.Transaction(ctx, func(ctx context.Context) error {
		for i := range clusters {
			// check cloud provider and region
			if cerr := checkCloudProviderAndRegion(clusters[i].CloudProvider.String(), clusters[i].Region, providers); cerr != nil {
				return cerr
			}

			// check serverless restriction
			if cerr := checkServerlessRestriction(clusters[i]); cerr != nil {
				return cerr
			}

			// check backup policy
			serverlessCluster, cerr := clusters[i].GetServerlessCluster()
			if cerr != nil {
				return cerr
			}
			if cerr := CheckBackupPolicy(serverlessCluster); cerr != nil {
				return cerr
			}

			// only validate the first cluster
			skipValidate := i > 0

			var err bizerr.BizError
			clusters[i], err = svc.createSingleCluster(ctx, clusters[i], fmt.Sprintf("%s-%d", spanName, i), skipValidate)
			if err != nil {
				return err.Chain("failed to create the cluster: index=%d", i)
			}
		}
		return nil
	}); err != nil {
		return clusters, err
	}

	return clusters, nil
}

func checkCloudProviderAndRegion(
	cloudProvider, region string,
	providers map[string]map[string]struct{},
) bizerr.BizError {
	if _, ok := providers[cloudProvider]; !ok {
		return bizerr.New(bizerr.ErrCodeParams, "cloud provider %s not supported", cloudProvider)
	}

	if _, ok := providers[cloudProvider][region]; !ok {
		return bizerr.New(bizerr.ErrCodeParams, "region %s not supported for cloud provider %s", region, cloudProvider)
	}

	return nil
}
