package region

import (
	"cmp"
	"context"
	"slices"
	"strings"

	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	openapifsv1beta2 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/fs/v1beta2"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"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) ListRegions(
	ctx context.Context,
	req *openapifsv1beta2.ListRegionsRequest,
) (*openapifsv1beta2.ListRegionsResponse, error) {
	ctx, span := tracer.Start(ctx, "ListRegions")
	defer span.End()

	// Extract request meta to make sure the permission check has been done.
	_, 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")
	}

	cloudProvider := ""
	if pName != 0 {
		cloudProvider = openapicommonv1beta1.Region_CloudProvider(pName).String()
	}
	// The public OpenAPI only returns ACTIVE regions, filtered at the DB layer
	// so that totalSize and pagination are computed on the visible set.
	entities, err := t.fsRegionRepo.List(ctx, cloudProvider, entity.FsRegionStatusActive)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to list fs regions")
	}

	regions := make([]*openapifsv1beta2.Region, 0, len(entities))
	for _, en := range entities {
		region, err := cvtFsRegion(en)
		if err != nil {
			return nil, bizerror.NewInternal(err, "failed to convert fs region")
		}
		regions = append(regions, region)
	}

	// Sort regions by region.cloud_provider first, then by region.name.
	slices.SortFunc(regions, func(a, b *openapifsv1beta2.Region) int {
		if c := cmp.Compare(a.CloudProvider, b.CloudProvider); c != 0 {
			return c
		}
		return cmp.Compare(a.Name, b.Name)
	})

	total := int32(len(regions))
	pagedRegions, nextToken, err := common.LocalPagination(regions, req, common.ListDefaultPageSize, common.ListMaxPageSize)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to paginate regions")
	}

	return &openapifsv1beta2.ListRegionsResponse{
		Regions:       pagedRegions,
		TotalSize:     total,
		NextPageToken: nextToken,
	}, nil
}

func (t serverImpl) GetRegion(
	ctx context.Context,
	req *openapifsv1beta2.GetRegionRequest,
) (*openapifsv1beta2.Region, error) {
	ctx, span := tracer.Start(ctx, "GetRegion")
	defer span.End()

	// Extract request meta to make sure the permission check has been done.
	_, err := metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	provider, regionCode, err := common.ParseOpenApiRegionNameOrId(req.GetRegionId())
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid region id")
	}

	en, err := t.fsRegionRepo.Get(ctx, provider, regionCode)
	if err != nil {
		return nil, err
	}
	// The public OpenAPI only returns ACTIVE regions.
	if !strings.EqualFold(en.Status, entity.FsRegionStatusActive) {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "region %s not found", req.GetRegionId())
	}

	region, err := cvtFsRegion(en)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to convert fs region")
	}
	return region, nil
}

func cvtFsRegion(en *entity.FsRegion) (*openapifsv1beta2.Region, error) {
	name, id, cloudProvider, err := common.BuildOpenApiRegionName(en.CloudProvider, en.Region)
	if err != nil {
		return nil, err
	}
	return &openapifsv1beta2.Region{
		Name:           name,
		RegionId:       id,
		CloudProvider:  cloudProvider,
		DisplayName:    en.DisplayName,
		ServiceAddress: en.ServiceAddress,
		State:          cvtFsRegionState(en.Status),
	}, nil
}

func cvtFsRegionState(status string) openapifsv1beta2.Region_State {
	switch strings.ToUpper(status) {
	case entity.FsRegionStatusActive:
		return openapifsv1beta2.Region_ACTIVE
	case entity.FsRegionStatusInactive:
		return openapifsv1beta2.Region_INACTIVE
	default:
		return openapifsv1beta2.Region_STATE_UNSPECIFIED
	}
}
