package vpc

import (
	"context"
	"fmt"
	"strings"

	"github.com/pingcap/log"
	regionalapiv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/util"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	bizerr "github.com/tidbcloud/infra-provider/regional-server/pkg/common/errors"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/utils"
	rsconfig "github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/appbox"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/approute"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/internet"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/namespace"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/vpc"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/vpcpeering"
	svcaccount "github.com/tidbcloud/infra-provider/regional-server/pkg/service/account"
	"go.uber.org/zap"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

type VPCService struct {
	vpcrw       vpc.VPCReaderWriter
	approuter   approute.APPRouteReader
	accountsvc  svcaccount.ServiceInterface
	appboxrw    appbox.APPBoxReaderWriter
	vpcpeeringr vpcpeering.VPCPeeringReader
	internetrw  internet.InternetReaderWriter
	namespacerw namespace.NamespaceReaderWriter
	config      rsconfig.RootConfig
}

func NewVPCService(
	vpcrw vpc.VPCReaderWriter,
	approuter approute.APPRouteReader,
	appboxrw appbox.APPBoxReaderWriter,
	vpcpeeringr vpcpeering.VPCPeeringReader,
	internetrw internet.InternetReaderWriter,
	accountsvc svcaccount.ServiceInterface,
	namespacerw namespace.NamespaceReaderWriter,
	config rsconfig.RootConfig,
) *VPCService {
	return &VPCService{
		vpcrw:       vpcrw,
		approuter:   approuter,
		appboxrw:    appboxrw,
		vpcpeeringr: vpcpeeringr,
		internetrw:  internetrw,
		accountsvc:  accountsvc,
		namespacerw: namespacerw,
		config:      config,
	}
}

func (s *VPCService) CreateVPC(ctx context.Context, req *regionalapiv1.CreateVpcRequest) (*regionalapiv1.Vpc, error) {
	// TODO(cwen0): handle multi tenant and multi project
	namer := utils.NewNamer(req.Vpc.TenantIds[0], req.Vpc.ProjectIds[0], "", s.config.RegionConfig.Name, s.config.Env)
	name := namer.GetUniqueNameByInput(req.RequestId)

	if err := s.ensureNamespace(ctx, namer); err != nil {
		log.Error("failed to ensure namespace", zap.Error(err))
		return nil, bizerr.New(bizerr.K8SClientError, "failed to ensure namespace")
	}

	accountInfo, providerConfigName, err := s.accountsvc.AssignAccount(ctx, name, req.Vpc.TenantIds[0], req.Vpc.ProjectIds[0])
	if err != nil {
		log.Error("failed to assign account", zap.Error(err))
		return nil, bizerr.New(bizerr.InternalServerError, "failed to assign account")
	}
	accountRef := accountInfo.GetAccountReference(providerConfigName)
	vpc := &v1alpha1.VPC{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: namer.GetNamespace(),
			Name:      name,
			Labels: map[string]string{
				v1alpha1.RegionKey:         s.config.RegionConfig.Name,
				namer.GetProjectLabelKey(): "true",
				namer.GetTenantLabelKey():  "true",
				v1alpha1.AccountIDKey:      accountRef.AccountID,
				// add tags for billing
				v1alpha1.TagProjectKey: namer.GetProjectID(),
				v1alpha1.TagTenantKey:  namer.GetTenantID(),
			},
		},
		Spec: v1alpha1.VPCSpec{
			CIDR:       req.Vpc.CidrBlock,
			Region:     s.config.RegionConfig.Name,
			Provider:   s.config.Provider,
			AccountRef: accountRef,
		},
	}
	vpc.ObjectMeta.Labels = util.MergeMaps(vpc.ObjectMeta.Labels, s.config.ResourceLabels)
	vpcRes := &regionalapiv1.Vpc{
		TenantIds:  req.Vpc.TenantIds,
		ProjectIds: req.Vpc.ProjectIds,
		CidrBlock:  req.Vpc.CidrBlock,
		Zones:      req.Vpc.Zones,
	}
	if len(req.Vpc.GetZones()) > 0 {
		vpc.Spec.Zones = req.Vpc.GetZones()
	}
	if err := s.vpcrw.Create(ctx, vpc); err != nil {
		if apierrors.IsAlreadyExists(err) {
			log.Info("vpc already exists", zap.String("name", name))
			return vpcRes, bizerr.New(bizerr.ResourceConflict, "vpc already exists")
		}
		log.Error("failed to create vpc", zap.Error(err))
		return nil, bizerr.New(bizerr.K8SClientError, "failed to create vpc")
	}
	return vpcRes, nil
}

func (s *VPCService) ensureNamespace(ctx context.Context, namer *utils.Namer) error {
	var ns corev1.Namespace
	err := s.namespacerw.Get(ctx, client.ObjectKey{Name: namer.GetNamespace()}, &ns)
	if err == nil {
		return nil
	}
	// If the namespace does not exist, create it.
	if client.IgnoreNotFound(err) != nil {
		return err
	}

	err = s.namespacerw.Create(ctx, &corev1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: namer.GetNamespace(),
		},
	})
	// If the namespace already exists, do nothing
	if err != nil && apierrors.IsAlreadyExists(err) {
		return nil
	}

	return err
}

func (s *VPCService) QueryClusterIds(ctx context.Context, req *regionalapiv1.QueryVpcClusterIdsRequest) (*regionalapiv1.QueryVpcClusterIdsResponse, error) {
	opt := client.MatchingLabels{}
	if aws := req.GetAws(); aws != nil {
		opt[v1alpha1.VPCIDKey] = aws.VpcId
		opt[v1alpha1.AccountIDKey] = aws.AccountId
	}

	if gcp := req.GetGcp(); gcp != nil {
		opt[v1alpha1.VPCIDKey] = gcp.NetworkName
		opt[v1alpha1.AccountIDKey] = gcp.ProjectId
	}

	vpcs := &v1alpha1.VPCList{}
	if err := s.vpcrw.List(ctx, vpcs, opt); err != nil {
		log.Error("failed to list vpcs", zap.Error(err))
		return nil, bizerr.New(bizerr.K8SClientError, "failed to list vpcs")
	}

	resp := &regionalapiv1.QueryVpcClusterIdsResponse{}
	for _, vpc := range vpcs.Items {
		opt := client.MatchingLabels{
			v1alpha1.VPCNameKey:                   vpc.Name,
			utils.GenTenantLabelKey(req.TenantId): "true",
		}
		if req.ProjectId != "" {
			opt[utils.GenProjectLabelKey(req.ProjectId)] = "true"
		}
		approutes := &v1alpha1.APPRouteList{}
		if err := s.approuter.List(ctx, approutes, opt); err != nil {
			log.Error("failed to list approute", zap.Error(err))
			return nil, bizerr.New(bizerr.K8SClientError, "failed to list clusters")
		}

		for _, approute := range approutes.Items {
			if clusterId := approute.GetLabels()[v1alpha1.ClusterIDKey]; clusterId != "" {
				resp.ClusterIds = append(resp.ClusterIds, clusterId)
			}
		}
	}

	return resp, nil
}

func (s *VPCService) ListVPC(ctx context.Context, req *regionalapiv1.ListVpcsRequest) (*regionalapiv1.ListVpcsResponse, error) {
	if req.ClusterId != "" {
		return s.listVPCByAPPRoute(ctx, req)
	}

	vpcs := &v1alpha1.VPCList{}
	opt := client.MatchingLabels{
		utils.GenTenantLabelKey(req.TenantId): "true",
	}
	if req.ProjectId != "" {
		opt[utils.GenProjectLabelKey(req.ProjectId)] = "true"
	}

	resp := &regionalapiv1.ListVpcsResponse{}
	log.Info("list vpcs", zap.Any("opt", opt))
	if err := s.vpcrw.List(ctx, vpcs, opt); err != nil {
		return nil, bizerr.New(bizerr.K8SClientError, "failed to list vpcs")
	}
	for _, vpc := range vpcs.Items {
		vpc := vpc.DeepCopy()
		// if status filter is empty, represent all status
		if len(req.Statuses) > 0 {
			if !meetVPCStatus(vpc.Status.Phase, req.Statuses) {
				continue
			}
		}
		internet, err := s.getInternetByVPC(ctx, vpc)
		if err != nil {
			return nil, bizerr.New(bizerr.K8SClientError, "failed to get internet")
		}

		info, err := getVPCInfo(vpc, internet, s.config.Provider)
		if err != nil {
			// This is internal error, should not return to user
			log.Error("failed to get vpc info", zap.Error(err))
			continue
		}
		resp.Vpcs = append(resp.Vpcs, info)
	}
	return resp, nil
}

func meetVPCStatus(phase v1alpha1.VPCPhase, statusFilters []regionalapiv1.Vpc_Status) bool {
	sts := getVPCStatus(phase)
	for _, filter := range statusFilters {
		if sts == filter {
			return true
		}
	}
	return false
}

func (s *VPCService) listVPCByAPPRoute(ctx context.Context, req *regionalapiv1.ListVpcsRequest) (*regionalapiv1.ListVpcsResponse, error) {
	approutes := &v1alpha1.APPRouteList{}
	opt := client.MatchingLabels{
		utils.GenTenantLabelKey(req.TenantId):   "true",
		utils.GenClusterLabelKey(req.ClusterId): "true",
	}
	if req.ProjectId != "" {
		opt[utils.GenProjectLabelKey(req.ProjectId)] = "true"
	}
	log.Info("list approutes", zap.Any("opt", opt))
	if err := s.approuter.List(ctx, approutes, opt); err != nil {
		return nil, bizerr.New(bizerr.K8SClientError, "failed to list approutes")
	}
	if len(approutes.Items) == 0 {
		return nil, nil
	}

	resp := &regionalapiv1.ListVpcsResponse{}
	for _, approute := range approutes.Items {
		vpcName := approute.GetLabels()[v1alpha1.VPCNameKey]
		if vpcName == "" {
			continue
		}
		vpc := &v1alpha1.VPC{}
		if err := s.vpcrw.Get(ctx, client.ObjectKey{Namespace: approute.Namespace, Name: vpcName}, vpc); err != nil {
			log.Error("failed to get vpc", zap.Error(err))
			if apierrors.IsNotFound(err) {
				continue
			}
			return nil, bizerr.New(bizerr.K8SClientError, "failed to get vpc")
		}
		if len(req.Statuses) > 0 {
			if !meetVPCStatus(vpc.Status.Phase, req.Statuses) {
				log.Info("vpc status not meet", zap.String("vpc", vpc.Name), zap.String("status", string(vpc.Status.Phase)))
				continue
			}
		}
		internet, err := s.getInternetByVPC(ctx, vpc)
		if err != nil {
			return nil, bizerr.New(bizerr.K8SClientError, "failed to get internet of vpc")
		}

		info, err := getVPCInfo(vpc, internet, s.config.Provider)
		if err != nil {
			// This is internal error, should not return to user
			log.Error("failed to get vpc info", zap.Error(err))
			continue
		}
		resp.Vpcs = append(resp.Vpcs, info)
	}
	return resp, nil
}

func getVPCInfo(vpc *v1alpha1.VPC, internet *v1alpha1.Internet, provider string) (*regionalapiv1.Vpc, error) {
	var info *regionalapiv1.Vpc
	switch provider {
	case consts.ProviderTypeEKS:
		info = &regionalapiv1.Vpc{
			VpcId: vpc.GetLabels()[v1alpha1.VPCIDKey],
			Info: &regionalapiv1.Vpc_Aws{
				Aws: &regionalapiv1.Vpc_AwsVpcInfo{
					VpcId:     vpc.GetLabels()[v1alpha1.VPCIDKey],
					AccountId: vpc.GetLabels()[v1alpha1.AccountIDKey],
				},
			},
			CidrBlock: vpc.Spec.CIDR,
		}
	case consts.ProviderTypeGKE:
		info = &regionalapiv1.Vpc{
			VpcId: vpc.GetLabels()[v1alpha1.VPCIDKey],
			Info: &regionalapiv1.Vpc_Gcp{
				Gcp: &regionalapiv1.Vpc_GcpVpcInfo{
					ProjectId:   vpc.Spec.AccountRef.AccountID,
					NetworkName: vpc.GetLabels()[v1alpha1.VPCIDKey],
				},
			},
			CidrBlock: vpc.Spec.CIDR,
		}
	case consts.ProviderTypeAKS:
		info = &regionalapiv1.Vpc{
			VpcId: vpc.GetLabels()[v1alpha1.VPCIDKey],
			Info: &regionalapiv1.Vpc_Azure{
				Azure: &regionalapiv1.Vpc_AzureVpcInfo{
					VnetId: vpc.GetLabels()[v1alpha1.VPCIDKey],
				},
			},
			CidrBlock: vpc.Spec.CIDR,
		}
	default:
		return nil, fmt.Errorf("unsupported provider type %s", provider)
	}

	for key, value := range vpc.GetLabels() {
		if strings.HasPrefix(key, v1alpha1.TenantIDKeyPrefix) && value == "true" {
			info.TenantIds = append(info.TenantIds, strings.TrimPrefix(key, v1alpha1.TenantIDKeyPrefix))
		}
		if strings.HasPrefix(key, v1alpha1.ProjectIDKeyPrefix) && value == "true" {
			info.ProjectIds = append(info.ProjectIds, strings.TrimPrefix(key, v1alpha1.ProjectIDKeyPrefix))
		}
	}
	info.Status = getVPCStatus(vpc.Status.Phase)

	if internet != nil {
		info.ExternalIps = internet.Status.ExternalIPs
	}
	if len(vpc.Spec.SecondaryCidrs) > 0 {
		info.SecondaryCidrBlocks = vpc.Spec.SecondaryCidrs
	}
	if len(vpc.Spec.Zones) > 0 {
		info.Zones = vpc.Spec.Zones
	}
	return info, nil
}

func getVPCStatus(phase v1alpha1.VPCPhase) regionalapiv1.Vpc_Status {
	switch phase {
	case "", v1alpha1.VPCPhaseCreating, v1alpha1.VPCPhasePending, v1alpha1.VPCPhaseReadyForCreate:
		return regionalapiv1.Vpc_CREATING
	case v1alpha1.VPCPhaseActive:
		return regionalapiv1.Vpc_ACTIVE
	case v1alpha1.VPCPhaseFailed:
		return regionalapiv1.Vpc_FAILED
	case v1alpha1.VPCPhaseDeleting, v1alpha1.VPCPhaseDeleted, v1alpha1.VPCPhaseReadyForDelete:
		return regionalapiv1.Vpc_DELETING
	default:
		return regionalapiv1.Vpc_STATE_UNSPECIFIED
	}
}

func (s *VPCService) DeleteVPC(ctx context.Context, req *regionalapiv1.DeleteVpcRequest) error {
	vpcList := &v1alpha1.VPCList{}
	err := s.vpcrw.List(ctx, vpcList, client.MatchingLabels{
		v1alpha1.VPCIDKey: req.VpcId,
	})
	if client.IgnoreNotFound(err) != nil {
		log.Error("failed to list vpcs", zap.Error(err))
		return bizerr.New(bizerr.K8SClientError, "failed to list vpcs")
	}
	if apierrors.IsNotFound(err) || len(vpcList.Items) == 0 {
		log.Info("vpc not found", zap.String("vpcid", req.VpcId))
		return bizerr.New(bizerr.ResourceNotFound, "vpc not found")
	}
	for _, vpc := range vpcList.Items {
		approutes, err := s.filterAPPRoute(ctx, &vpc)
		if err != nil {
			log.Error("failed to filter approutes", zap.Error(err))
			return bizerr.New(bizerr.K8SClientError, "failed to list approutes")
		}
		if len(approutes) > 0 {
			log.Info("vpc is still used by approute, skip delete", zap.String("vpc", vpc.Name))
			return bizerr.New(bizerr.ResuouceIsUsed, "vpc is still used by approute")
		}

		vpcpeerings, err := s.filterVPCPeering(ctx, &vpc)
		if err != nil {
			log.Error("failed to filter vpcpeerings", zap.Error(err))
			return bizerr.New(bizerr.K8SClientError, "failed to list vpcpeerings")
		}
		if len(vpcpeerings) > 0 {
			log.Info("vpc is still used by vpcpeering, skip delete", zap.String("vpc", vpc.Name))
			return bizerr.New(bizerr.ResuouceIsUsed, "vpc is still used by vpcpeering")
		}
		// delete appbox first
		appboxes, err := s.filterAPPBox(ctx, &vpc)
		if err != nil {
			log.Error("failed to filter appboxes", zap.Error(err))
			return bizerr.New(bizerr.K8SClientError, "failed to list appboxes")
		}
		for _, appbox := range appboxes {
			if err := s.appboxrw.Delete(ctx, &appbox); err != nil {
				if apierrors.IsNotFound(err) {
					continue
				}
				log.Error("failed to delete appbox", zap.Error(err))
				return bizerr.New(bizerr.K8SClientError, "failed to delete appbox")
			}
		}
		// delete internet
		internets, err := s.filterInternet(ctx, &vpc)
		if err != nil {
			log.Error("failed to filter internets", zap.Error(err))
			return bizerr.New(bizerr.K8SClientError, "failed to list internets")
		}
		for _, internet := range internets {
			if err := s.internetrw.Delete(ctx, &internet); err != nil {
				if apierrors.IsNotFound(err) {
					continue
				}
				log.Error("failed to delete internet", zap.Error(err))
				return bizerr.New(bizerr.K8SClientError, "failed to delete internet")
			}
		}
		// delete vpc
		if err := s.vpcrw.Delete(ctx, &vpc); err != nil {
			if apierrors.IsNotFound(err) {
				continue
			}
			log.Error("failed to delete vpc", zap.Error(err))
			return bizerr.New(bizerr.K8SClientError, "failed to delete vpc")
		}
	}
	return nil
}

func (s *VPCService) filterInternet(ctx context.Context, vpc *v1alpha1.VPC) ([]v1alpha1.Internet, error) {
	opt := client.MatchingLabels{
		// use vpc name to filter internet, vpcname must be set when create internet, but vpcid won't be set if vpc is not ready.
		v1alpha1.VPCNameKey: vpc.Name,
	}
	internets := &v1alpha1.InternetList{}
	if err := s.internetrw.List(ctx, internets, opt); err != nil {
		log.Error("failed to list internet", zap.Error(err))
		return nil, bizerr.New(bizerr.K8SClientError, "failed to list internet")
	}
	return internets.Items, nil
}

func (s *VPCService) filterAPPRoute(ctx context.Context, vpc *v1alpha1.VPC) ([]v1alpha1.APPRoute, error) {
	opt := client.MatchingLabels{
		// use vpc name to filter approute, vpcname must be set when create approute, but vpcid won't be set if vpc is not ready.
		v1alpha1.VPCNameKey: vpc.Name,
	}
	approutes := &v1alpha1.APPRouteList{}
	if err := s.approuter.List(ctx, approutes, opt); err != nil {
		log.Error("failed to list approute", zap.Error(err))
		return nil, bizerr.New(bizerr.K8SClientError, "failed to list approute")
	}
	return approutes.Items, nil
}

func (s *VPCService) filterVPCPeering(ctx context.Context, vpc *v1alpha1.VPC) ([]v1alpha1.VPCPeering, error) {
	opt := client.MatchingLabels{
		// use vpc id to filter vpcpeering, vpcid must be set when create vpcpeering
		v1alpha1.VPCIDKey: vpc.GetLabels()[v1alpha1.VPCIDKey],
	}
	vpcpeerings := &v1alpha1.VPCPeeringList{}
	if err := s.vpcpeeringr.List(ctx, vpcpeerings, opt); err != nil {
		log.Error("failed to list vpcpeering", zap.Error(err))
		return nil, bizerr.New(bizerr.K8SClientError, "failed to list vpcpeering")
	}
	return vpcpeerings.Items, nil
}

func (s *VPCService) filterAPPBox(ctx context.Context, vpc *v1alpha1.VPC) ([]v1alpha1.APPBox, error) {
	opt := client.MatchingLabels{
		// use vpc name to filter appbox, vpcname must be set when create appbox, but vpcid won't be set if vpc is not ready.
		v1alpha1.VPCNameKey: vpc.Name,
	}
	appboxes := &v1alpha1.APPBoxList{}
	if err := s.appboxrw.List(ctx, appboxes, opt); err != nil {
		log.Error("failed to list appbox", zap.Error(err))
		return nil, bizerr.New(bizerr.K8SClientError, "failed to list appbox")
	}
	return appboxes.Items, nil
}

func (s *VPCService) GetVPC(ctx context.Context, req *regionalapiv1.GetVpcRequest) (*regionalapiv1.Vpc, error) {
	vpcList := &v1alpha1.VPCList{}
	err := s.vpcrw.List(ctx, vpcList, client.MatchingLabels{
		v1alpha1.VPCIDKey: req.VpcId,
	})
	if err != nil {
		log.Error("failed to list vpcs", zap.Error(err))
		return nil, bizerr.New(bizerr.K8SClientError, "failed to list vpcs")
	}
	if len(vpcList.Items) == 0 {
		return nil, bizerr.New(bizerr.ResourceNotFound, "vpc not found")
	}
	if len(vpcList.Items) != 1 {
		return nil, bizerr.New(bizerr.K8SClientError, "vpc record is not unique")
	}
	internet, err := s.getInternetByVPC(ctx, &vpcList.Items[0])
	if err != nil {
		return nil, bizerr.New(bizerr.K8SClientError, "cannot get internet of vpc")
	}
	info, err := getVPCInfo(&vpcList.Items[0], internet, s.config.Provider)
	if err != nil {
		// This is internal error, should not return to user
		log.Error("failed to get vpc info", zap.Error(err))
		return nil, bizerr.New(bizerr.K8SClientError, "vpc is not invalid")
	}

	return info, nil
}

// Return an internet associated with a vpc
// It may be nil because the internet may have not been created
func (s *VPCService) getInternetByVPC(ctx context.Context, vpc *v1alpha1.VPC) (*v1alpha1.Internet, error) {

	internets, err := s.filterInternet(ctx, vpc)
	if err != nil {
		log.Error("failed to filter internets", zap.Error(err))
		return nil, bizerr.New(bizerr.K8SClientError, "failed to list internets")
	}
	for i := range internets {
		internet := &internets[i]
		if internet.Status.Phase == v1alpha1.InternetPhaseActive {
			return internet, nil
		}
	}

	return nil, nil
}

func (s *VPCService) AddSecondaryCidr(ctx context.Context, req *regionalapiv1.AddSecondaryCidrRequest) (*regionalapiv1.Vpc, error) {
	vpcList := &v1alpha1.VPCList{}
	err := s.vpcrw.List(ctx, vpcList, client.MatchingLabels{
		v1alpha1.VPCIDKey: req.VpcId,
	})
	if err != nil {
		log.Error("failed to list vpcs", zap.Error(err))
		return nil, bizerr.New(bizerr.K8SClientError, "failed to list vpcs")
	}
	if len(vpcList.Items) == 0 {
		return nil, bizerr.New(bizerr.ResourceNotFound, "vpc not found")
	}
	if len(vpcList.Items) != 1 {
		return nil, bizerr.New(bizerr.K8SClientError, "vpc record is not unique")
	}
	vpc := &vpcList.Items[0]
	if vpc.Status.Phase != v1alpha1.VPCPhaseActive {
		return nil, bizerr.New(bizerr.InternalServerError, "vpc is not ready")
	}
	if vpc.Spec.SecondaryCidrs == nil {
		vpc.Spec.SecondaryCidrs = []string{}
	}
	if len(vpc.Spec.SecondaryCidrs) >= 4 {
		return nil, bizerr.New(bizerr.ParameterInvalid, "vpc already has 4 secondary CIDRs, cannot add more")
	}
	if !utils.IsValidCIDR(req.SecondaryCidr) {
		return nil, bizerr.New(bizerr.ParameterInvalid, "secondaryCidr is not a valid IPv4 CIDR")
	}
	if valid, err := utils.ValidSecondaryCidr(vpc.Spec.CIDR, vpc.Spec.SecondaryCidrs, req.SecondaryCidr); !valid {
		return nil, bizerr.New(bizerr.ParameterInvalid, err.Error())
	}
	vpc.Spec.SecondaryCidrs = append(vpc.Spec.SecondaryCidrs, req.SecondaryCidr)
	if req.ValidateOnly {
		return getVPCInfo(vpc, nil, s.config.Provider)
	}
	if vpc.Annotations == nil {
		vpc.Annotations = make(map[string]string)
	}
	secondaryCidrName := v1alpha1.SecondaryCidrNamePrefix + utils.SanitizeCidr(req.SecondaryCidr)
	vpc.Annotations[secondaryCidrName] = v1alpha1.SecondaryCidrStatusPending
	if err := s.vpcrw.Update(ctx, vpc); err != nil {
		log.Error("failed to update vpc", zap.Error(err))
		return nil, bizerr.New(bizerr.K8SClientError, "failed to update vpc")
	}
	return getVPCInfo(vpc, nil, s.config.Provider)
}
