package region

import (
	"context"
	"errors"
	"strconv"

	"github.com/samber/lo"
	"google.golang.org/protobuf/proto"
	"gorm.io/gorm"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	openapingv1beta2 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/nextgen/v1beta2"
	appcommon "github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/app/openapi/nextgen/common"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/apimiddleware/bizerror"
	commonpkg "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) ListByocVcpuPools(
	ctx context.Context,
	req *openapingv1beta2.ListByocVcpuPoolsRequest,
) (*openapingv1beta2.ListByocVcpuPoolsResponse, error) {
	ctx, span := tracer.Start(ctx, "ListByocVcpuPools")
	defer span.End()

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

	cloudProviderID, regionName, err := parseByocVcpuPoolsFilter(req.GetCloudProvider(), req.GetRegionId())
	if err != nil {
		return nil, err
	}
	if req.GetRegionId() != "" && regionName == globalcommonv1.Region_NAME_UNSPECIFIED {
		return &openapingv1beta2.ListByocVcpuPoolsResponse{
			Groups: []*openapingv1beta2.ListByocVcpuPoolsResponse_Group{},
		}, nil
	}

	resp, err := t.nextgenCli.ListByocVcpuPools(ctx, &globalngv1.ListByocVcpuPoolsRequest{
		OrganizationId:  reqMeta.OrgID,
		CloudProviderId: cloudProviderID,
		RegionId:        regionName,
	})
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to list byoc vcpu pools")
	}

	customStatusMap, err := t.listByocVcpuPoolCustomStatuses(ctx, resp)
	if err != nil {
		return nil, err
	}

	groups := make([]*openapingv1beta2.ListByocVcpuPoolsResponse_Group, 0, len(resp.GetGroups()))
	for _, group := range resp.GetGroups() {
		openapiName, openapiID, cloudProvider, buildErr := commonpkg.BuildOpenApiRegionName(
			group.GetRegion().GetCloudProviderId().String(),
			group.GetRegion().GetId().String(),
		)
		if buildErr != nil {
			return nil, bizerror.NewInternal(buildErr, "failed to build openapi region name")
		}

		pools := lo.Map(
			group.GetPools(),
			func(pool *globalngv1.ListByocVcpuPoolsResponse_Pool, _ int) *openapingv1beta2.ByocVcpuPool {
				return convertByocVcpuPool(pool, customStatusMap)
			},
		)

		groups = append(groups, &openapingv1beta2.ListByocVcpuPoolsResponse_Group{
			Region: &openapicommonv1beta1.Region{
				Name:          openapiName,
				RegionId:      openapiID,
				CloudProvider: cloudProvider,
				DisplayName:   group.GetRegion().GetDisplayName(),
				ServicePlans:  []openapicommonv1beta1.ServicePlan{openapicommonv1beta1.ServicePlan_BYOC},
			},
			TotalVcpusLimit: group.GetTotalVcpusLimit(),
			Pools:           pools,
		})
	}

	thirdProviders, err := t.accountCli.GetTenantThirdPartyAccountProviders(ctx, reqMeta.OrgID)
	if err != nil {
		return nil, bizerr.WrapGrpcError(err, "failed to get third party account providers")
	}
	if len(thirdProviders) > 0 {
		groups = lo.Filter(groups, func(group *openapingv1beta2.ListByocVcpuPoolsResponse_Group, _ int) bool {
			return lo.Contains(thirdProviders, group.GetRegion().GetCloudProvider())
		})
	}

	return &openapingv1beta2.ListByocVcpuPoolsResponse{
		Groups: groups,
	}, nil
}

func parseByocVcpuPoolsFilter(
	cloudProvider string,
	regionID string,
) (globalcommonv1.CloudProvider_Name, globalcommonv1.Region_Name, error) {
	providerName, err := enum.FromString[openapicommonv1beta1.Region_CloudProvider](cloudProvider)
	if err != nil {
		return globalcommonv1.CloudProvider_NAME_UNSPECIFIED, globalcommonv1.Region_NAME_UNSPECIFIED,
			bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid cloud provider")
	}

	globalProvider := globalcommonv1.CloudProvider_Name(providerName)
	globalRegion := globalcommonv1.Region_NAME_UNSPECIFIED
	if regionID == "" {
		return globalProvider, globalRegion, nil
	}

	regionProvider, regionCode, err := commonpkg.ParseOpenApiRegionNameOrId(regionID)
	if err != nil {
		return globalcommonv1.CloudProvider_NAME_UNSPECIFIED, globalcommonv1.Region_NAME_UNSPECIFIED,
			bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid region_id")
	}

	if cloudProvider != "" && regionProvider != cloudProvider {
		return globalcommonv1.CloudProvider_NAME_UNSPECIFIED, globalcommonv1.Region_NAME_UNSPECIFIED,
			bizerr.New(bizerr.ErrCodeParams, "cloud_provider and region_id are inconsistent")
	}

	if cloudProvider == "" {
		derivedProvider, providerErr := enum.FromString[globalcommonv1.CloudProvider_Name](regionProvider)
		if providerErr != nil {
			return globalcommonv1.CloudProvider_NAME_UNSPECIFIED, globalcommonv1.Region_NAME_UNSPECIFIED,
				bizerr.WrapErrorWithCode(providerErr, bizerr.ErrCodeParams, "invalid region_id")
		}
		globalProvider = globalcommonv1.CloudProvider_Name(derivedProvider)
	}

	parsedRegion, regionErr := enum.FromString[globalcommonv1.Region_Name](regionCode)
	if regionErr != nil {
		return globalProvider, globalcommonv1.Region_NAME_UNSPECIFIED, nil
	}
	globalRegion = globalcommonv1.Region_Name(parsedRegion)
	return globalProvider, globalRegion, nil
}

func (t serverImpl) listByocVcpuPoolCustomStatuses(
	ctx context.Context,
	resp *globalngv1.ListByocVcpuPoolsResponse,
) (map[uint64][]*entity.ClusterCustomStatus, error) {
	clusterIDs := make([]uint64, 0)
	seen := make(map[uint64]struct{})
	for _, group := range resp.GetGroups() {
		for _, pool := range group.GetPools() {
			for _, instance := range pool.GetTidbInstances() {
				if instance.GetDeleting() {
					continue
				}
				if _, ok := seen[instance.GetClusterId()]; ok {
					continue
				}
				seen[instance.GetClusterId()] = struct{}{}
				clusterIDs = append(clusterIDs, instance.GetClusterId())
			}
		}
	}
	if len(clusterIDs) == 0 {
		return map[uint64][]*entity.ClusterCustomStatus{}, nil
	}
	if t.customStatusSvc == nil {
		return nil, bizerror.NewInternal(nil, "custom status service is not configured")
	}
	statusMap, err := t.customStatusSvc.ListClusterCustomStatus(ctx, clusterIDs, nil, false)
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, bizerror.NewInternal(err, "failed to get cluster custom status record")
	}
	return statusMap, nil
}

func convertByocVcpuPool(
	pool *globalngv1.ListByocVcpuPoolsResponse_Pool,
	customStatusMap map[uint64][]*entity.ClusterCustomStatus,
) *openapingv1beta2.ByocVcpuPool {
	return &openapingv1beta2.ByocVcpuPool{
		ResourcePoolId:       strconv.FormatUint(pool.GetResourcePoolId(), 10),
		Name:                 pool.GetName(),
		DataplaneId:          pool.GetDataplaneId(),
		VcpusLimit:           proto.Int64(pool.GetVcpusLimit()),
		VcpusUsed:            pool.GetVcpusUsed(),
		IsDefault:            pool.GetIsDefault(),
		HighAvailabilityType: convertByocVcpuPoolHighAvailabilityType(pool.GetHighAvailabilityType()),
		ZoneConfig:           convertByocVcpuPoolZoneConfig(pool.GetZoneConfig()),
		State:                convertByocVcpuPoolState(pool.GetState()),
		TidbInstancesState:   aggregateByocVcpuPoolTidbInstancesState(pool.GetTidbInstances(), customStatusMap),
	}
}

func convertByocVcpuPoolState(
	state globalngv1.ListByocVcpuPoolsResponse_PoolState,
) openapingv1beta2.ByocVcpuPool_State {
	switch state {
	case globalngv1.ListByocVcpuPoolsResponse_AVAILABLE:
		return openapingv1beta2.ByocVcpuPool_AVAILABLE
	case globalngv1.ListByocVcpuPoolsResponse_MODIFYING:
		return openapingv1beta2.ByocVcpuPool_MODIFYING
	default:
		return openapingv1beta2.ByocVcpuPool_STATE_UNSPECIFIED
	}
}

func aggregateByocVcpuPoolTidbInstancesState(
	instances []*globalngv1.ListByocVcpuPoolsResponse_TidbInstance,
	customStatusMap map[uint64][]*entity.ClusterCustomStatus,
) *openapingv1beta2.ByocVcpuPool_TidbInstancesState {
	state := &openapingv1beta2.ByocVcpuPool_TidbInstancesState{}
	for _, instance := range instances {
		if instance.GetDeleting() {
			state.Deleting++
			continue
		}

		baseState := appcommon.ConvertClusterState(
			instance.GetBaseState(),
			globalngv1.Cluster_HEALTH_STATE_UNSPECIFIED,
		)
		finalState := commonpkg.BuildClusterState(baseState, customStatusMap[instance.GetClusterId()])
		switch finalState {
		case openapicommonv1beta1.ClusterState_ACTIVE:
			state.Active++
		case openapicommonv1beta1.ClusterState_CREATING:
			state.Creating++
		default:
			state.Modifying++
		}
	}
	return state
}

func convertByocVcpuPoolHighAvailabilityType(
	highAvailabilityType globalngv1.ResourcePool_HighAvailabilityType,
) openapingv1beta2.ByocVcpuPool_HighAvailabilityType {
	switch highAvailabilityType {
	case globalngv1.ResourcePool_REGIONAL:
		return openapingv1beta2.ByocVcpuPool_REGIONAL
	case globalngv1.ResourcePool_ZONAL:
		return openapingv1beta2.ByocVcpuPool_ZONAL
	default:
		return openapingv1beta2.ByocVcpuPool_HIGH_AVAILABILITY_TYPE_UNSPECIFIED
	}
}

func convertByocVcpuPoolZoneConfig(
	zoneConfig *globalngv1.ResourcePool_ZoneConfig,
) *openapingv1beta2.ByocVcpuPool_ZoneConfig {
	if zoneConfig == nil {
		return nil
	}
	return &openapingv1beta2.ByocVcpuPool_ZoneConfig{
		NodeZones: append([]string(nil), zoneConfig.GetNodeZones()...),
	}
}
