package fsregion

import (
	"context"
	"strings"

	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/repository"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func NewServer(
	fsRegionRepo repository.FsRegionRepository,
) tidbmgmtservicev1.FsRegionServiceServer {
	return &serverImpl{
		fsRegionRepo: fsRegionRepo,
	}
}

type serverImpl struct {
	fsRegionRepo repository.FsRegionRepository
}

// ListFsRegions returns all fs regions, including INACTIVE ones.
func (s *serverImpl) ListFsRegions(
	ctx context.Context,
	req *tidbmgmtservicev1.ListFsRegionsRequest,
) (*tidbmgmtservicev1.ListFsRegionsResponse, error) {
	entities, err := s.fsRegionRepo.List(ctx, "", "")
	if err != nil {
		return nil, err
	}

	regions := make([]*tidbmgmtservicev1.FsRegion, 0, len(entities))
	for _, en := range entities {
		region, err := toProto(en)
		if err != nil {
			return nil, err
		}
		regions = append(regions, region)
	}
	return &tidbmgmtservicev1.ListFsRegionsResponse{Regions: regions}, nil
}

// GetFsRegion returns the fs region regardless of its status.
func (s *serverImpl) GetFsRegion(
	ctx context.Context,
	req *tidbmgmtservicev1.GetFsRegionRequest,
) (*tidbmgmtservicev1.FsRegion, error) {
	provider, regionCode, err := common.ParseOpenApiRegionNameOrId(req.GetRegionId())
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid region id")
	}

	en, err := s.fsRegionRepo.Get(ctx, provider, regionCode)
	if err != nil {
		return nil, err
	}
	return toProto(en)
}

func (s *serverImpl) CreateFsRegion(
	ctx context.Context,
	req *tidbmgmtservicev1.CreateFsRegionRequest,
) (*tidbmgmtservicev1.FsRegion, error) {
	en, err := toEntity(req.GetRegion())
	if err != nil {
		return nil, err
	}

	created, err := s.fsRegionRepo.Create(ctx, en)
	if err != nil {
		return nil, err
	}
	return toProto(created)
}

func (s *serverImpl) UpdateFsRegion(
	ctx context.Context,
	req *tidbmgmtservicev1.UpdateFsRegionRequest,
) (*tidbmgmtservicev1.FsRegion, error) {
	provider, regionCode, err := common.ParseOpenApiRegionNameOrId(req.GetRegionId())
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid region id")
	}

	region := req.GetRegion()
	if region == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "region is required")
	}
	status, err := normalizeStatus(region.GetStatus())
	if err != nil {
		return nil, err
	}
	en := &entity.FsRegion{
		CloudProvider:  provider,
		Region:         regionCode,
		DisplayName:    region.GetDisplayName(),
		ServiceAddress: region.GetServiceAddress(),
		Status:         status,
	}

	updated, err := s.fsRegionRepo.Update(ctx, en)
	if err != nil {
		return nil, err
	}
	return toProto(updated)
}

func toEntity(region *tidbmgmtservicev1.FsRegion) (*entity.FsRegion, error) {
	if region == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "region is required")
	}
	provider, regionCode, err := common.ParseOpenApiRegionNameOrId(region.GetRegionId())
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid region id")
	}
	status, err := normalizeStatus(region.GetStatus())
	if err != nil {
		return nil, err
	}
	// Empty status means ACTIVE on create.
	if status == "" {
		status = entity.FsRegionStatusActive
	}
	return &entity.FsRegion{
		CloudProvider:  provider,
		Region:         regionCode,
		DisplayName:    region.GetDisplayName(),
		ServiceAddress: region.GetServiceAddress(),
		Status:         status,
	}, nil
}

// normalizeStatus validates and upper-cases the status. An empty status is
// returned as-is: on create the caller defaults it to ACTIVE, on update an
// empty status leaves the stored value unchanged.
func normalizeStatus(status string) (string, error) {
	status = strings.ToUpper(status)
	switch status {
	case "", "ACTIVE", "INACTIVE":
		return status, nil
	default:
		return "", bizerr.New(bizerr.ErrCodeParams, "invalid status %q, must be ACTIVE or INACTIVE", status)
	}
}

func toProto(en *entity.FsRegion) (*tidbmgmtservicev1.FsRegion, error) {
	_, id, _, err := common.BuildOpenApiRegionName(en.CloudProvider, en.Region)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid cloud provider")
	}
	return &tidbmgmtservicev1.FsRegion{
		RegionId:       id,
		DisplayName:    en.DisplayName,
		ServiceAddress: en.ServiceAddress,
		Status:         en.Status,
	}, nil
}
