package privatelink

import (
	"context"
	"strconv"

	regionaldedicatedv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func (svc *service) CreateService(
	ctx context.Context,
	req *globaldedicatedv1.CreatePrivateLinkServiceRequest,
) (*globaldedicatedv1.PrivateLinkService, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "Domain.CreatePrivateLinkServiceSvc")
	defer span.End()

	// Check
	cluster, err := svc.clusterRepo.Get(ctx, req.ClusterId, false)
	if err != nil {
		return nil, err
	}
	cloudProvider := cluster.CloudProviderId

	if cloudProvider == globalcommonv1.CloudProvider_GCP && !cluster.OnNewArch {
		return nil, bizerr.New(bizerr.ErrCodeParams, "only new-arch GCP clusters can create PrivateLink service")
	}

	if err := svc.ensureStatus(cluster); err != nil {
		return nil, err
	}

	// Create
	service := &globaldedicatedv1.PrivateLinkService{}

	region, err := svc.regionRepo.SelectRegionById(ctx, cluster.RegionId, cluster.OrganizationId)
	if err != nil {
		return nil, err
	}

	tidbGroup, err := svc.tidbGroupRepo.LoadDefault(ctx, req.ClusterId)
	if err != nil {
		return nil, err
	}

	// In Dedicated -> Premium migration scenarios, Dedicated clusters need to rebuild endpoints to avoid conflicts
	// with previous ones. When the caller sets generate_unique_service_id, generate a new unique id via the ID generator instead.
	privateLinkServiceID := strconv.FormatUint(tidbGroup.Id, 10)
	if req.GetGenerateUniqueServiceId() {
		privateLinkServiceID = strconv.FormatUint(svc.idGeneratorFacade.MustNew(), 10)
	}
	tilog.Info(ctx, "creating private link service",
		zap.Uint64("cluster_id", cluster.ClusterId),
		zap.Uint64("tidb_group_id", tidbGroup.Id),
		zap.String("private_link_service_id", privateLinkServiceID),
		zap.Bool("generate_unique_service_id", req.GetGenerateUniqueServiceId()),
	)

	createPrivateLinkServiceReq := &regionaldedicatedv1.CreatePrivateLinkServiceRequest{
		PrivateLinkService: &regionaldedicatedv1.PrivateLinkService{
			Id:             privateLinkServiceID,
			ClusterId:      strconv.FormatUint(cluster.ClusterId, 10),
			TidbGroupId:    strconv.FormatUint(tidbGroup.Id, 10),
			ServiceDnsName: "",  // leave it empty
			Info:           nil, // leave it empty
			Connections:    nil, // leave it empty
			Status:         0,   // leave it empty
		},
	}

	_, err = svc.regionServerCli.CreatePrivateLinkService(ctx, region, createPrivateLinkServiceReq)
	if err != nil {
		tilog.Error(ctx, "create private link service failed",
			zap.Uint64("cluster_id", cluster.ClusterId),
			zap.Uint64("tidb_group_id", tidbGroup.Id),
			zap.String("private_link_service_id", privateLinkServiceID),
			zap.Error(err),
		)
		return nil, err
	}
	service.Status = globaldedicatedv1.PrivateLinkService_CREATING

	return service, nil
}

func (svc *service) ensureStatus(cluster *entity.Cluster) bizerr.BizError {
	if cluster.CloudProviderId != globalcommonv1.CloudProvider_AWS &&
		cluster.CloudProviderId != globalcommonv1.CloudProvider_GCP &&
		cluster.CloudProviderId != globalcommonv1.CloudProvider_AZURE {
		return bizerr.New(bizerr.ErrCodePrivateLinkServiceNotAllowed,
			"not support to create PrivateLink service for cloud provider %s now", cluster.CloudProviderId.String())
	}

	if cluster.State != globaldedicatedv1.Cluster_AVAILABLE &&
		cluster.State != globaldedicatedv1.Cluster_SCALING {
		return bizerr.New(bizerr.ErrCodePrivateLinkServiceNotAllowed,
			"not support to create PrivateLink service when cluster status is %s", cluster.State.String())
	}

	return nil
}
