package tidbgroup

import (
	"context"
	"fmt"
	"strconv"

	"github.com/samber/lo"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	openapidedicatedv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/dedicated/v1beta1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/apimiddleware/bizerror"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func (s serverImpl) GetTidbNodeGroup(
	ctx context.Context,
	req *openapidedicatedv1beta1.GetTidbNodeGroupRequest,
) (*openapidedicatedv1beta1.TidbNodeGroup, error) {
	ctx, span := tracer.Start(ctx, "GetTidbNodeGroup")
	defer span.End()

	// Extract request meta.
	_, err := metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	// Validate request.
	clusterId, err := strconv.ParseUint(req.GetClusterId(), 10, 64)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid cluster id")
	}

	tidbGroupIds, err := strconv.ParseUint(req.GetTidbNodeGroupId(), 10, 64)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid tidb node group id")
	}

	// get cluster info from domain
	tidbGroups, err := s.dedicatedCli.ListTidbNodeGroups(ctx, &globaldedicatedv1.ListTidbNodeGroupsRequest{
		ClusterIds:  []uint64{clusterId},
		Ids:         []uint64{tidbGroupIds},
		ShowDetails: true,
	})
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to get cluster tidb node group")
	}

	if len(tidbGroups.GetTidbNodeGroups()) == 0 {
		return nil, bizerror.NewNotFound(nil, "tidb node group not found")
	}

	if len(tidbGroups.GetTidbNodeGroups()) > 1 {
		return nil, bizerror.NewInternal(nil, "multiple tidb node groups found")
	}

	return cvtGlobalToTidbNodeGroup(tidbGroups.GetTidbNodeGroups()[0]), nil
}

func cvtGlobalToTidbNodeGroup(globalGroup *globaldedicatedv1.TidbNodeGroup) *openapidedicatedv1beta1.TidbNodeGroup {
	group := &openapidedicatedv1beta1.TidbNodeGroup{
		Name:                fmt.Sprintf("tidbNodeGroups/%d", globalGroup.GetId()),
		NodeCount:           globalGroup.GetCount(),
		TidbNodeGroupId:     strconv.FormatUint(globalGroup.GetId(), 10),
		IsDefaultGroup:      globalGroup.GetIsDefault(),
		DisplayName:         globalGroup.GetDisplayName(),
		ClusterId:           strconv.FormatUint(globalGroup.GetClusterId(), 10),
		NodeSpecKey:         globalGroup.GetSpuName(),
		NodeSpecDisplayName: globalGroup.GetSpuDisplayName(),
		NodeSpecVersion:     globalGroup.GetSpuVersion(),
	}

	switch globalGroup.GetState() {
	case globaldedicatedv1.TidbNodeGroup_ACTIVE:
		group.State = openapidedicatedv1beta1.TidbNodeGroup_ACTIVE
	case globaldedicatedv1.TidbNodeGroup_MODIFYING:
		group.State = openapidedicatedv1beta1.TidbNodeGroup_MODIFYING
	case globaldedicatedv1.TidbNodeGroup_PAUSED:
		group.State = openapidedicatedv1beta1.TidbNodeGroup_PAUSED
	default:
		group.State = openapidedicatedv1beta1.TidbNodeGroup_STATE_UNSPECIFIED
	}

	if globalGroup.GetEndpoint().GetPublicEndpoint() != nil {
		group.Endpoints = append(group.Endpoints, &openapidedicatedv1beta1.TidbNodeGroup_Endpoint{
			ConnectionType: openapidedicatedv1beta1.TidbNodeGroup_Endpoint_PUBLIC,
			Host:           globalGroup.GetEndpoint().GetPublicEndpoint().GetHost(),
			Port:           int32(globalGroup.GetEndpoint().GetPublicEndpoint().GetPort()),
		})
	}

	if globalGroup.GetEndpoint().GetVpcPeeringEndpoint() != nil {
		group.Endpoints = append(group.Endpoints, &openapidedicatedv1beta1.TidbNodeGroup_Endpoint{
			ConnectionType: openapidedicatedv1beta1.TidbNodeGroup_Endpoint_VPC_PEERING,
			Host:           globalGroup.GetEndpoint().GetVpcPeeringEndpoint().GetHost(),
			Port:           int32(globalGroup.GetEndpoint().GetVpcPeeringEndpoint().GetPort()),
		})
	}

	edps := lo.Map(
		globalGroup.GetEndpoint().GetPrivateLinkEndpoints(),
		func(item *globaldedicatedv1.TidbNodeGroup_Endpoint_PrivateLinkEndpoint, _ int) *openapidedicatedv1beta1.TidbNodeGroup_Endpoint {
			return &openapidedicatedv1beta1.TidbNodeGroup_Endpoint{
				ConnectionType: openapidedicatedv1beta1.TidbNodeGroup_Endpoint_PRIVATE_ENDPOINT,
				Host:           item.GetHost(),
				Port:           int32(item.GetPort()),
			}
		},
	)
	if globalGroup.GetTiproxySetting() != nil {
		group.TiproxySetting = &openapidedicatedv1beta1.TidbNodeGroup_TiProxySetting{
			NodeSpecKey:         globalGroup.GetTiproxySetting().GetSpuName(),
			NodeCount:           lo.ToPtr(globalGroup.GetTiproxySetting().GetCount()),
			NodeSpecVersion:     globalGroup.GetTiproxySetting().GetSpuVersion(),
			NodeSpecDisplayName: globalGroup.GetTiproxySetting().GetSpuDisplayName(),
		}
		if globalGroup.GetTiproxyModifyProgress() != nil {
			group.TiproxyNodeChangingProgress = &openapidedicatedv1beta1.Cluster_NodeChangingProgress{
				MatchingNodeSpecNodeCount:  globalGroup.GetTiproxyModifyProgress().GetUptodateQuantity(),
				RemainingDeletionNodeCount: globalGroup.GetTiproxyModifyProgress().GetRemainingDeletionQuantity(),
			}
			group.State = openapidedicatedv1beta1.TidbNodeGroup_MODIFYING
		}
	}

	group.Endpoints = append(group.Endpoints, edps...)

	if globalGroup.ModifyProgress != nil {
		group.NodeChangingProgress = &openapidedicatedv1beta1.Cluster_NodeChangingProgress{
			MatchingNodeSpecNodeCount:  globalGroup.ModifyProgress.GetUptodateQuantity(),
			RemainingDeletionNodeCount: globalGroup.ModifyProgress.GetRemainingDeletionQuantity(),
		}
		globalGroup.State = globaldedicatedv1.TidbNodeGroup_MODIFYING
	}
	return group
}
