package grpcli

import (
	"context"
	"strings"

	"github.com/tidbcloud/dataflow-service-ng/app/repo/entity"
	"github.com/tidbcloud/dataflow-service-ng/pkg/log"
	"github.com/tidbcloud/pkgv2/bizerr"
	datatypev1 "github.com/tidbcloud/tidb-management-service/api/spec/datatype/v1"
	global_commonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	v1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	tidb_mgmt_servicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"go.uber.org/zap"
	"google.golang.org/protobuf/proto"
)

type RegionGrpcClient interface {
	GetRegionByTenantID(
		ctx context.Context, tenantID uint64, cloudProvider string, regionName string,
	) (*global_commonv1.Region, bizerr.BizError)
	GetRegionByResourcePoolID(ctx context.Context, sharedPoolID uint64) (*global_commonv1.Region, bizerr.BizError)
}

type regionGrpcClientImpl struct {
	client             tidb_mgmt_servicev1.RegionServiceClient
	resourcePoolClient tidb_mgmt_servicev1.ResourcePoolServiceClient
}

func NewRegionGrpcClient(ctx context.Context, addr string) RegionGrpcClient {
	logger := log.GetLogger(ctx)
	logger.Info("initializing region service client", zap.String("addr", addr))

	conn := MustBuildGrpcClientConnection(ctx, addr)
	client := tidb_mgmt_servicev1.NewRegionServiceClient(conn)
	return &regionGrpcClientImpl{
		client:             client,
		resourcePoolClient: tidb_mgmt_servicev1.NewResourcePoolServiceClient(conn),
	}
}

// GetRegionByTenantID get the logical region by tenant id.
// With logical region, there may be multiple logical regions in a physical region.
// But we ensure there is only one logical region for a tenant in a physical region,
// so tenant id and physical region name can determine one logical region.
func (m *regionGrpcClientImpl) GetRegionByTenantID(
	ctx context.Context, tenantID uint64, cloudProvider string, regionName string,
) (*global_commonv1.Region, bizerr.BizError) {
	regionID := RegionName2ID(regionName)
	if regionID == global_commonv1.Region_NAME_UNSPECIFIED {
		return nil, bizerr.New(bizerr.ErrCodeParams, "invalid region name %s", regionName)
	}

	req := &tidb_mgmt_servicev1.SelectRegionByOrganizationIdRequest{
		DeployType:      tidb_mgmt_servicev1.DeployType_DEPLOY_TYPE_NEXTGEN,
		OrganizationId:  tenantID,
		RegionId:        regionID,
		CloudProviderId: ToCloudProviderName(cloudProvider),
	}
	region, err := m.client.SelectRegionByOrganizationId(ctx, req)
	if err != nil {
		return nil, bizerr.WrapError(err, "can't get region by tenant id %d and region name %s on %s",
			tenantID, regionName, cloudProvider)
	}
	return region.Region, nil
}

func (m *regionGrpcClientImpl) GetRegionByResourcePoolID(ctx context.Context, sharedPoolID uint64) (*global_commonv1.Region, bizerr.BizError) {
	pool, err := m.resourcePoolClient.GetResourcePool(ctx, &v1.GetResourcePoolRequest{
		ResourcePoolId: sharedPoolID,
		ShowDeleted:    false,
	})
	if err != nil {
		return nil, bizerr.WrapError(err, "get resource pool %d failed", sharedPoolID)
	}
	return pool.GetRegion(), nil
}

func ToCloudProviderName(provider string) global_commonv1.CloudProvider_Name {
	switch entity.CloudProviderType(strings.ToLower(provider)) {
	case entity.CloudProviderTypeAWS:
		return global_commonv1.CloudProvider_AWS
	case entity.CloudProviderTypeGCP:
		return global_commonv1.CloudProvider_GCP
	case entity.CloudProviderTypeAzure:
		return global_commonv1.CloudProvider_AZURE
	case entity.CloudProviderTypeAliCloud:
		return global_commonv1.CloudProvider_ALICLOUD
	}
	return global_commonv1.CloudProvider_NAME_UNSPECIFIED
}

// RegionName2ID get enum value by corresponding "datatype.v1.name" tag
// it is mainly copied from https://github.com/tidbcloud/tidb-management-service/blob/main/utilities/enum/strconv.go#L42
func RegionName2ID(regionName string) global_commonv1.Region_Name {
	if regionName == "" {
		return global_commonv1.Region_NAME_UNSPECIFIED
	}

	var r global_commonv1.Region_Name
	d := r.Descriptor()

	// Iterate over all enum values.
	for i := 0; i < d.Values().Len(); i++ {
		valueDesc := d.Values().Get(i)
		var name string
		if proto.HasExtension(valueDesc.Options(), datatypev1.E_Name) {
			ext := proto.GetExtension(valueDesc.Options(), datatypev1.E_Name)
			if extString, ok := ext.(string); ok && extString != "" {
				name = extString
			}
		}

		if name != "" && strings.EqualFold(name, regionName) {
			return global_commonv1.Region_Name(valueDesc.Number())
		}
	}

	return global_commonv1.Region_NAME_UNSPECIFIED
}
