package aws

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	ec2v1alpha1 "github.com/crossplane-contrib/provider-aws/apis/ec2/v1alpha1"
	ec2v1beta1 "github.com/crossplane-contrib/provider-aws/apis/ec2/v1beta1"
	"github.com/crossplane-contrib/provider-aws/apis/eks/manualv1alpha1"
	eksv1beta1 "github.com/crossplane-contrib/provider-aws/apis/eks/v1beta1"
	iamv1alpha1 "github.com/crossplane-contrib/provider-aws/apis/iam/v1alpha1"
	iamv1beta1 "github.com/crossplane-contrib/provider-aws/apis/iam/v1beta1"
	route53v1alpha1 "github.com/crossplane-contrib/provider-aws/apis/route53/v1alpha1"
	xpv1beta1 "github.com/crossplane-contrib/provider-aws/apis/v1beta1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	xpmeta "github.com/crossplane/crossplane-runtime/pkg/meta"

	"github.com/pingcap/log"
	"go.uber.org/zap"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/util/retry"
	"k8s.io/utils/pointer"
	"sigs.k8s.io/controller-runtime/pkg/client"

	bizerr "github.com/tidbcloud/infra-provider/regional-server/pkg/common/errors"
)

var patchAnnotationTPL = `{ "metadata": {"annotations": {"%s": "%s"}} }`

type Option func(*Service) error

type Service struct {
	// we have some special case when creating VPCPeering CR, need to read kube-apiserver directly.
	kubeClient    client.Client // access kube-apiserver with mgr cache.
	kubeAPIReader client.Reader // access kube-apiserver directly.

	// provider string // value can be `eks` or `gke`
	// region   string // phyical region, value can be `us-west-2`, `us-east-2`, `us-west1`
}

func NewService(kubeClient client.Client, kubeAPIReader client.Reader, opts ...Option) (*Service, error) {
	svc := &Service{
		kubeClient:    kubeClient,
		kubeAPIReader: kubeAPIReader,
	}
	for _, opt := range opts {
		if err := opt(svc); err != nil {
			return nil, err
		}
	}
	return svc, nil
}

func (svc *Service) checkProviderConfigExisted(ctx context.Context, name string) error {
	providerConfig := new(xpv1beta1.ProviderConfig)
	providerConfig.Name = name
	if err := svc.kubeAPIReader.Get(ctx, client.ObjectKeyFromObject(providerConfig), providerConfig); err != nil {
		log.Error("get providerconfig failed", zap.Any("key", client.ObjectKeyFromObject(providerConfig)), zap.Error(err))
		if apierrors.IsNotFound(err) {
			return bizerr.New(bizerr.ResourceNotFound, err.Error())
		}
		return bizerr.New(bizerr.K8SClientError, err.Error())
	}
	return nil
}

type PObject[T any] interface {
	client.Object
	*T
}

type DeepCopyable[PT PObject[T], T any] interface {
	DeepCopy() PT
}

// we need a type which:
// 1. implement client.Object interface.
// 2. has a DeepCopy() method which return a copy of itself.
type DeepCopyableObject[PT PObject[T], T any] interface {
	DeepCopyable[PT, T]
	PObject[T]
}

func upsertObject[DT DeepCopyableObject[PT, T], PT PObject[T], T any](ctx context.Context, kubeClient client.Client, kubeAPIReader client.Reader, cr DT) error {
	key := client.ObjectKeyFromObject(cr).String()
	cp := cr.DeepCopy()
	if err := kubeClient.Create(ctx, cr); err != nil {
		if !apierrors.IsAlreadyExists(err) {
			log.Error("create object failed", zap.String("key", key), zap.Error(err))
			return bizerr.New(bizerr.K8SClientError, err.Error())
		}
		// overwrite the object if existed
		var existed PT = new(T)
		if err := kubeAPIReader.Get(ctx, client.ObjectKeyFromObject(cr), existed); err != nil {
			log.Error("get object failed", zap.Error(err), zap.String("key", key))
			return bizerr.New(bizerr.K8SClientError, err.Error())
		}
		if _, ok := any(existed).(*xpv1beta1.ProviderConfig); !ok {
			if !xpmeta.IsPaused(existed) {
				return bizerr.New(bizerr.ResourceConflict, "resource is not paused")
			}
		}
		// commonly, we may implement the update like following:
		// existed.Annotations =  cr.Annotaions
		// existed.Labels = cr.Labels
		// existed.Spec = cr.Spec
		// existed.Status = cr.Status
		// _ := kubeClient.Updated(ctx, cr)
		// however, we cannot access .Spec and .Status field directly since we use generics here
		// so we set resourceversion and uid on cr and update cr as a workaround
		// set these fields to solve precondition error
		cr.SetResourceVersion(existed.GetResourceVersion())
		cr.SetUID(existed.GetUID())
		if err := kubeClient.Update(ctx, cr); err != nil {
			log.Error("update object failed",
				zap.String("key", key),
				zap.String("resourceversion", cp.GetResourceVersion()),
				zap.Int64("generation", cp.GetGeneration()),
				zap.Error(err))
			return bizerr.New(bizerr.K8SClientError, err.Error())
		}
		err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
			if err := kubeAPIReader.Get(ctx, client.ObjectKeyFromObject(existed), existed); err != nil {
				log.Error("get object failed", zap.Error(err), zap.String("key", key))
				return err
			}
			// set these fields to solve conflict error
			cp.SetResourceVersion(existed.GetResourceVersion())
			cp.SetUID(existed.GetUID())
			if err := kubeClient.Status().Update(ctx, cp); err != nil {
				log.Error("update status failed",
					zap.String("key", key),
					zap.String("resourceversion", cp.GetResourceVersion()),
					zap.Int64("generation", cp.GetGeneration()),
					zap.Error(err))
				return err
			}
			return nil
		})
		if err != nil {
			return bizerr.New(bizerr.K8SClientError, err.Error())
		}
	} else {
		err := retry.OnError(retry.DefaultRetry,
			func(err error) bool {
				return apierrors.IsConflict(err) || apierrors.IsNotFound(err)
			},
			func() error {
				var existed PT = new(T)
				if err := kubeAPIReader.Get(ctx, client.ObjectKeyFromObject(cr), existed); err != nil {
					log.Error("get object failed", zap.String("key", key), zap.Error(err))
					return err
				}
				// still need to update status with the given cr for the first creation
				cp.SetResourceVersion(existed.GetResourceVersion())
				cp.SetUID(existed.GetUID())
				if err := kubeClient.Status().Update(ctx, cp); err != nil {
					log.Error("update status failed",
						zap.String("key", key),
						zap.String("resourceversion", cp.GetResourceVersion()),
						zap.Int64("generation", cp.GetGeneration()),
						zap.Error(err))
					return err
				}
				return nil
			})
		if err != nil {
			return bizerr.New(bizerr.K8SClientError, err.Error())
		}
	}
	return nil
}

func (svc *Service) ImportCluster(ctx context.Context, cr *eksv1beta1.Cluster) error {
	if cr.Annotations == nil {
		cr.Annotations = map[string]string{}
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if !cr.DeletionTimestamp.IsZero() {
		return bizerr.New(bizerr.ParameterInvalid, "deletiontimestamp has value")
	}
	cr.Annotations[xpmeta.AnnotationKeyReconciliationPaused] = "true"
	// the OwnerReferences must be nil, otherwise the resource will be delete automatically
	// since uid in the OwnerReferences cannot match to real resource.
	cr.OwnerReferences = nil
	// TODO: other filed need to change
	// existed cr may used different providerconfig, check the referenced providerconfig is existed
	if cr.Spec.ProviderConfigReference == nil {
		return bizerr.New(bizerr.ParameterInvalid, "providerconfigreference is nil")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}
	if err := upsertObject[*eksv1beta1.Cluster](ctx, svc.kubeClient, svc.kubeAPIReader, cr); err != nil {
		log.Error("upsert cluster failed", zap.String("key", client.ObjectKeyFromObject(cr).String()), zap.Error(err))
		return err
	}
	return nil
}

func (svc *Service) CheckCluster(ctx context.Context, key client.ObjectKey) error {
	cr := new(eksv1beta1.Cluster)
	if err := svc.kubeAPIReader.Get(ctx, key, cr); err != nil {
		if apierrors.IsNotFound(err) {
			return bizerr.New(bizerr.ResourceNotFound, err.Error())
		}
		return bizerr.New(bizerr.K8SClientError, err.Error())
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}
	// check vpc cr is existed
	// just use name convention
	vpc := new(ec2v1beta1.VPC)
	vpc.Name = cr.Name
	if err := svc.kubeAPIReader.Get(ctx, client.ObjectKeyFromObject(vpc), vpc); err != nil {
		if apierrors.IsNotFound(err) {
			return bizerr.New(bizerr.ResourceNotFound, err.Error())
		}
		return bizerr.New(bizerr.K8SClientError, err.Error())
	}
	// check securitygroup cr is existed
	// just use name convention
	if len(cr.Spec.ForProvider.ResourcesVpcConfig.SecurityGroupIDs) > 0 {
		sg := new(ec2v1beta1.SecurityGroup)
		sg.Name = cr.Name
		if err := svc.kubeAPIReader.Get(ctx, client.ObjectKeyFromObject(sg), sg); err != nil {
			if apierrors.IsNotFound(err) {
				return bizerr.New(bizerr.ResourceNotFound, err.Error())
			}
			return bizerr.New(bizerr.K8SClientError, err.Error())
		}
		if xpmeta.GetExternalName(sg) != cr.Spec.ForProvider.ResourcesVpcConfig.SecurityGroupIDs[0] {
			return bizerr.New(bizerr.InternalServerError, fmt.Sprintf("sg %v is invalide", xpmeta.GetExternalName(sg)))
		}
	}

	// TODO: check securitygroup cr is existed
	// just use name convention
	if len(cr.Spec.ForProvider.ResourcesVpcConfig.SubnetIDs) > 0 {
		// do we need check subnet cr?
	}

	for _, c := range cr.Status.Conditions {
		if c.Type == xpv1.TypeSynced && c.Reason == xpv1.ReasonReconcileError {
			return bizerr.New(bizerr.InternalServerError, fmt.Sprintf("xp resource %s sync failed", cr.Name))
		}
	}
	return nil
}

func (svc *Service) ImportVPC(ctx context.Context, cr *ec2v1beta1.VPC) error {
	if cr.Annotations == nil {
		cr.Annotations = map[string]string{}
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if !cr.DeletionTimestamp.IsZero() {
		return bizerr.New(bizerr.ParameterInvalid, "deletiontimestamp has value")
	}
	cr.Annotations[xpmeta.AnnotationKeyReconciliationPaused] = "true"
	// the OwnerReferences must be nil, otherwise the resource will be delete automatically
	// since uid in the OwnerReferences cannot match to real resource.
	cr.OwnerReferences = nil
	// TODO: other filed need to change

	// existed cr may used different providerconfig, check the referenced providerconfig is existed
	if cr.Spec.ProviderConfigReference == nil {
		return bizerr.New(bizerr.ParameterInvalid, "providerconfigreference is nil")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}

	if err := upsertObject[*ec2v1beta1.VPC](ctx, svc.kubeClient, svc.kubeAPIReader, cr); err != nil {
		log.Error("upsert vpc failed", zap.String("key", client.ObjectKeyFromObject(cr).String()), zap.Error(err))
		return err
	}
	return nil
}

func (svc *Service) CheckVPC(ctx context.Context, key client.ObjectKey) error {
	cr := new(ec2v1beta1.VPC)
	if err := svc.kubeAPIReader.Get(ctx, key, cr); err != nil {
		if apierrors.IsNotFound(err) {
			return bizerr.New(bizerr.ResourceNotFound, err.Error())
		}
		return bizerr.New(bizerr.K8SClientError, err.Error())
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}
	for _, c := range cr.Status.Conditions {
		if c.Type == xpv1.TypeSynced && c.Reason == xpv1.ReasonReconcileError {
			return bizerr.New(bizerr.InternalServerError, fmt.Sprintf("xp resource %s sync failed", cr.Name))
		}
	}
	return nil
}

func (svc *Service) ImportProviderConfig(ctx context.Context, cr *xpv1beta1.ProviderConfig) error {
	if !cr.DeletionTimestamp.IsZero() {
		return bizerr.New(bizerr.ParameterInvalid, "deletiontimestamp has value")
	}
	// TODO: other filed need to change
	if err := upsertObject[*xpv1beta1.ProviderConfig](ctx, svc.kubeClient, svc.kubeAPIReader, cr); err != nil {
		log.Error("upsert providerconfig failed", zap.String("key", client.ObjectKeyFromObject(cr).String()), zap.Error(err))
		return err
	}
	return nil
}

func (svc *Service) CheckProviderConfig(ctx context.Context, key client.ObjectKey) error {
	return nil
}

func (svc *Service) ImportAddress(ctx context.Context, cr *ec2v1beta1.Address) error {
	if cr.Annotations == nil {
		cr.Annotations = map[string]string{}
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if !cr.DeletionTimestamp.IsZero() {
		return bizerr.New(bizerr.ParameterInvalid, "deletiontimestamp has value")
	}
	cr.Annotations[xpmeta.AnnotationKeyReconciliationPaused] = "true"
	// the OwnerReferences must be nil, otherwise the resource will be delete automatically
	// since uid in the OwnerReferences cannot match to real resource.
	cr.OwnerReferences = nil
	// TODO: other filed need to change

	// existed cr may used different providerconfig, check the referenced providerconfig is existed
	if cr.Spec.ProviderConfigReference == nil {
		return bizerr.New(bizerr.ParameterInvalid, "providerconfigreference is nil")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}

	key := client.ObjectKeyFromObject(cr)
	if err := upsertObject[*ec2v1beta1.Address](ctx, svc.kubeClient, svc.kubeAPIReader, cr); err != nil {
		log.Error("upsert address failed", zap.String("key", key.String()), zap.Error(err))
		return err
	}
	return nil
}

func (svc *Service) CheckAddress(ctx context.Context, key client.ObjectKey) error {
	cr := new(ec2v1beta1.Address)
	if err := svc.kubeAPIReader.Get(ctx, key, cr); err != nil {
		if apierrors.IsNotFound(err) {
			return bizerr.New(bizerr.ResourceNotFound, err.Error())
		}
		return bizerr.New(bizerr.K8SClientError, err.Error())
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}

	for _, c := range cr.Status.Conditions {
		if c.Type == xpv1.TypeSynced && c.Reason == xpv1.ReasonReconcileError {
			return bizerr.New(bizerr.InternalServerError, fmt.Sprintf("xp resource %s sync failed", cr.Name))
		}
	}
	return nil
}

func (svc *Service) ImportInternetGateway(ctx context.Context, cr *ec2v1beta1.InternetGateway) error {
	if cr.Annotations == nil {
		cr.Annotations = map[string]string{}
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if !cr.DeletionTimestamp.IsZero() {
		return bizerr.New(bizerr.ParameterInvalid, "deletiontimestamp has value")
	}
	cr.Annotations[xpmeta.AnnotationKeyReconciliationPaused] = "true"
	// the OwnerReferences must be nil, otherwise the resource will be delete automatically
	// since uid in the OwnerReferences cannot match to real resource.
	cr.OwnerReferences = nil
	// TODO: other filed need to change

	// existed cr may used different providerconfig, check the referenced providerconfig is existed
	if cr.Spec.ProviderConfigReference == nil {
		return bizerr.New(bizerr.ParameterInvalid, "providerconfigreference is nil")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}

	key := client.ObjectKeyFromObject(cr)
	if err := upsertObject[*ec2v1beta1.InternetGateway](ctx, svc.kubeClient, svc.kubeAPIReader, cr); err != nil {
		log.Error("upsert intergetgateway failed", zap.String("key", key.String()), zap.Error(err))
		return err
	}
	return nil
}

func (svc *Service) CheckInternetGateway(ctx context.Context, key client.ObjectKey) error {
	cr := new(ec2v1beta1.InternetGateway)
	if err := svc.kubeAPIReader.Get(ctx, key, cr); err != nil {
		if apierrors.IsNotFound(err) {
			return bizerr.New(bizerr.ResourceNotFound, err.Error())
		}
		return bizerr.New(bizerr.K8SClientError, err.Error())
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}
	if ref := cr.Spec.ForProvider.VPCIDRef; ref != nil && ref.Name != "" {
		vpc := new(ec2v1beta1.VPC)
		vpc.Name = ref.Name
		if err := svc.kubeAPIReader.Get(ctx, client.ObjectKeyFromObject(vpc), vpc); err != nil {
			if apierrors.IsNotFound(err) {
				return bizerr.New(bizerr.ResourceNotFound, err.Error())
			}
			return bizerr.New(bizerr.K8SClientError, err.Error())
		}
		if vpcID := pointer.StringDeref(cr.Spec.ForProvider.VPCID, ""); vpcID != "" {
			if xpmeta.GetExternalName(vpc) != *cr.Spec.ForProvider.VPCID {
				return bizerr.New(bizerr.InternalServerError, "vpcid is mismatched")
			}
		}
	}
	for _, c := range cr.Status.Conditions {
		if c.Type == xpv1.TypeSynced && c.Reason == xpv1.ReasonReconcileError {
			return bizerr.New(bizerr.InternalServerError, fmt.Sprintf("xp resource %s sync failed", cr.Name))
		}
	}
	return nil
}

func (svc *Service) ImportLaunchTemplate(ctx context.Context, cr *ec2v1alpha1.LaunchTemplate) error {
	if cr.Annotations == nil {
		cr.Annotations = map[string]string{}
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if !cr.DeletionTimestamp.IsZero() {
		return bizerr.New(bizerr.ParameterInvalid, "deletiontimestamp has value")
	}
	cr.Annotations[xpmeta.AnnotationKeyReconciliationPaused] = "true"
	// the OwnerReferences must be nil, otherwise the resource will be delete automatically
	// since uid in the OwnerReferences cannot match to real resource.
	cr.OwnerReferences = nil
	// TODO: other filed need to change

	// existed cr may used different providerconfig, check the referenced providerconfig is existed
	if cr.Spec.ProviderConfigReference == nil {
		return bizerr.New(bizerr.ParameterInvalid, "providerconfigreference is nil")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}

	key := client.ObjectKeyFromObject(cr)
	if err := upsertObject[*ec2v1alpha1.LaunchTemplate](ctx, svc.kubeClient, svc.kubeAPIReader, cr); err != nil {
		log.Error("upsert launchtemplate failed", zap.String("key", key.String()), zap.Error(err))
		return err
	}
	return nil
}

func (svc *Service) CheckLaunchTemplate(ctx context.Context, key client.ObjectKey) error {
	cr := new(ec2v1alpha1.LaunchTemplate)
	if err := svc.kubeAPIReader.Get(ctx, key, cr); err != nil {
		if apierrors.IsNotFound(err) {
			return bizerr.New(bizerr.ResourceNotFound, err.Error())
		}
		return bizerr.New(bizerr.K8SClientError, err.Error())
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}
	for _, c := range cr.Status.Conditions {
		if c.Type == xpv1.TypeSynced && c.Reason == xpv1.ReasonReconcileError {
			return bizerr.New(bizerr.InternalServerError, fmt.Sprintf("xp resource %s sync failed", cr.Name))
		}
	}
	return nil
}

func (svc *Service) ImportNATGateway(ctx context.Context, cr *ec2v1beta1.NATGateway) error {
	if cr.Annotations == nil {
		cr.Annotations = map[string]string{}
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if !cr.DeletionTimestamp.IsZero() {
		return bizerr.New(bizerr.ParameterInvalid, "deletiontimestamp has value")
	}
	cr.Annotations[xpmeta.AnnotationKeyReconciliationPaused] = "true"
	// the OwnerReferences must be nil, otherwise the resource will be delete automatically
	// since uid in the OwnerReferences cannot match to real resource.
	cr.OwnerReferences = nil
	// TODO: other filed need to change

	// existed cr may used different providerconfig, check the referenced providerconfig is existed
	if cr.Spec.ProviderConfigReference == nil {
		return bizerr.New(bizerr.ParameterInvalid, "providerconfigreference is nil")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}

	key := client.ObjectKeyFromObject(cr)
	if err := upsertObject[*ec2v1beta1.NATGateway](ctx, svc.kubeClient, svc.kubeAPIReader, cr); err != nil {
		log.Error("upsert natgateway failed", zap.String("key", key.String()), zap.Error(err))
		return err
	}
	return nil
}

func (svc *Service) CheckNATGateway(ctx context.Context, key client.ObjectKey) error {
	cr := new(ec2v1beta1.NATGateway)
	if err := svc.kubeAPIReader.Get(ctx, key, cr); err != nil {
		if apierrors.IsNotFound(err) {
			return bizerr.New(bizerr.ResourceNotFound, err.Error())
		}
		return bizerr.New(bizerr.K8SClientError, err.Error())
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}
	// TODO: check subnet with aws client?
	for _, c := range cr.Status.Conditions {
		if c.Type == xpv1.TypeSynced && c.Reason == xpv1.ReasonReconcileError {
			return bizerr.New(bizerr.InternalServerError, fmt.Sprintf("xp resource %s sync failed", cr.Name))
		}
	}
	return nil
}

func (svc *Service) ImportRoute(ctx context.Context, cr *ec2v1alpha1.Route) error {
	if cr.Annotations == nil {
		cr.Annotations = map[string]string{}
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if !cr.DeletionTimestamp.IsZero() {
		return bizerr.New(bizerr.ParameterInvalid, "deletiontimestamp has value")
	}
	cr.Annotations[xpmeta.AnnotationKeyReconciliationPaused] = "true"
	// the OwnerReferences must be nil, otherwise the resource will be delete automatically
	// since uid in the OwnerReferences cannot match to real resource.
	cr.OwnerReferences = nil
	// TODO: other filed need to change

	// existed cr may used different providerconfig, check the referenced providerconfig is existed
	if cr.Spec.ProviderConfigReference == nil {
		return bizerr.New(bizerr.ParameterInvalid, "providerconfigreference is nil")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}

	key := client.ObjectKeyFromObject(cr)
	if err := upsertObject[*ec2v1alpha1.Route](ctx, svc.kubeClient, svc.kubeAPIReader, cr); err != nil {
		log.Error("upsert route failed", zap.String("key", key.String()), zap.Error(err))
		return err
	}
	return nil
}

func (svc *Service) CheckRoute(ctx context.Context, key client.ObjectKey) error {
	cr := new(ec2v1alpha1.Route)
	if err := svc.kubeAPIReader.Get(ctx, key, cr); err != nil {
		if apierrors.IsNotFound(err) {
			return bizerr.New(bizerr.ResourceNotFound, err.Error())
		}
		return bizerr.New(bizerr.K8SClientError, err.Error())
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}
	if rtRef := cr.Spec.ForProvider.RouteTableIDRef; rtRef != nil && rtRef.Name != "" {
		rt := new(ec2v1beta1.RouteTable)
		rt.Name = rtRef.Name
		if err := svc.kubeAPIReader.Get(ctx, client.ObjectKeyFromObject(rt), rt); err != nil {
			if apierrors.IsNotFound(err) {
				return bizerr.New(bizerr.ResourceNotFound, err.Error())
			}
			return bizerr.New(bizerr.K8SClientError, err.Error())
		}
		if pointer.StringDeref(cr.Spec.ForProvider.RouteTableID, "") != xpmeta.GetExternalName(rt) {
			return bizerr.New(bizerr.InternalServerError, "rotuetableid is mismatched")
		}
	}
	if natgwRef := cr.Spec.ForProvider.NATGatewayIDRef; natgwRef != nil && natgwRef.Name != "" {
		natgw := new(ec2v1beta1.NATGateway)
		natgw.Name = natgwRef.Name
		if err := svc.kubeAPIReader.Get(ctx, client.ObjectKeyFromObject(natgw), natgw); err != nil {
			if apierrors.IsNotFound(err) {
				return bizerr.New(bizerr.ResourceNotFound, err.Error())
			}
			return bizerr.New(bizerr.K8SClientError, err.Error())
		}
		if pointer.StringDeref(cr.Spec.ForProvider.NATGatewayID, "") != xpmeta.GetExternalName(natgw) {
			return bizerr.New(bizerr.InternalServerError, "natgatewayid is mismatched")
		}
	}

	if igwRef := cr.Spec.ForProvider.GatewayIDRef; igwRef != nil && igwRef.Name != "" {
		igw := new(ec2v1beta1.InternetGateway)
		igw.Name = igwRef.Name
		if err := svc.kubeAPIReader.Get(ctx, client.ObjectKeyFromObject(igw), igw); err != nil {
			if apierrors.IsNotFound(err) {
				return bizerr.New(bizerr.ResourceNotFound, err.Error())
			}
			return bizerr.New(bizerr.K8SClientError, err.Error())
		}
		if pointer.StringDeref(cr.Spec.ForProvider.GatewayID, "") != xpmeta.GetExternalName(igw) {
			return bizerr.New(bizerr.InternalServerError, "gatewayid is mismatched")
		}
	}
	for _, c := range cr.Status.Conditions {
		if c.Type == xpv1.TypeSynced && c.Reason == xpv1.ReasonReconcileError {
			return bizerr.New(bizerr.InternalServerError, fmt.Sprintf("xp resource %s sync failed", cr.Name))
		}
	}
	return nil
}

func (svc *Service) ImportRouteTable(ctx context.Context, cr *ec2v1beta1.RouteTable) error {
	if cr.Annotations == nil {
		cr.Annotations = map[string]string{}
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if !cr.DeletionTimestamp.IsZero() {
		return bizerr.New(bizerr.ParameterInvalid, "deletiontimestamp has value")
	}
	cr.Annotations[xpmeta.AnnotationKeyReconciliationPaused] = "true"
	// the OwnerReferences must be nil, otherwise the resource will be delete automatically
	// since uid in the OwnerReferences cannot match to real resource.
	cr.OwnerReferences = nil
	// TODO: other filed need to change

	// existed cr may used different providerconfig, check the referenced providerconfig is existed
	if cr.Spec.ProviderConfigReference == nil {
		return bizerr.New(bizerr.ParameterInvalid, "providerconfigreference is nil")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}

	key := client.ObjectKeyFromObject(cr)
	if err := upsertObject[*ec2v1beta1.RouteTable](ctx, svc.kubeClient, svc.kubeAPIReader, cr); err != nil {
		log.Error("upsert routetable failed", zap.String("key", key.String()), zap.Error(err))
		return err
	}
	return nil
}

func (svc *Service) CheckRouteTable(ctx context.Context, key client.ObjectKey) error {
	cr := new(ec2v1beta1.RouteTable)
	if err := svc.kubeAPIReader.Get(ctx, key, cr); err != nil {
		if apierrors.IsNotFound(err) {
			return bizerr.New(bizerr.ResourceNotFound, err.Error())
		}
		return bizerr.New(bizerr.K8SClientError, err.Error())
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}

	if ref := cr.Spec.ForProvider.VPCIDRef; ref != nil && ref.Name != "" {
		vpc := new(ec2v1beta1.VPC)
		vpc.Name = ref.Name
		if err := svc.kubeAPIReader.Get(ctx, client.ObjectKeyFromObject(vpc), vpc); err != nil {
			if apierrors.IsNotFound(err) {
				return bizerr.New(bizerr.ResourceNotFound, err.Error())
			}
			return bizerr.New(bizerr.K8SClientError, err.Error())
		}
		if pointer.StringDeref(cr.Spec.ForProvider.VPCID, "") != xpmeta.GetExternalName(vpc) {
			return bizerr.New(bizerr.InternalServerError, "vpcid is mismatched")
		}
	}
	for _, association := range cr.Spec.ForProvider.Associations {
		if ref := association.SubnetIDRef; ref != nil && ref.Name != "" {
			subnet := new(ec2v1beta1.Subnet)
			subnet.Name = ref.Name
			if err := svc.kubeAPIReader.Get(ctx, client.ObjectKeyFromObject(subnet), subnet); err != nil {
				if apierrors.IsNotFound(err) {
					return bizerr.New(bizerr.ResourceNotFound, err.Error())
				}
				return bizerr.New(bizerr.K8SClientError, err.Error())
			}
			if pointer.StringDeref(association.SubnetID, "") != xpmeta.GetExternalName(subnet) {
				return bizerr.New(bizerr.InternalServerError, "subnetid is mismatched")
			}
		}
	}
	for _, c := range cr.Status.Conditions {
		if c.Type == xpv1.TypeSynced && c.Reason == xpv1.ReasonReconcileError {
			return bizerr.New(bizerr.InternalServerError, fmt.Sprintf("xp resource %s sync failed", cr.Name))
		}
	}
	hasRouteAssociated := false
	for _, route := range cr.Status.AtProvider.Routes {
		if strings.Contains(cr.Name, "public") && strings.HasPrefix(route.GatewayID, "igw") {
			hasRouteAssociated = true
			break
		}
		if strings.Contains(cr.Name, "private") && route.NatGatewayID != "" {
			hasRouteAssociated = true
			break
		}
	}
	if hasRouteAssociated {
		// route and routetable share same naming format
		// make sure the related route cr exists
		route := new(ec2v1alpha1.Route)
		route.Name = cr.Name
		if err := svc.kubeAPIReader.Get(ctx, client.ObjectKeyFromObject(route), route); err != nil {
			if apierrors.IsNotFound(err) {
				return bizerr.New(bizerr.ResourceNotFound, err.Error())
			}
			return bizerr.New(bizerr.K8SClientError, err.Error())
		}
	}
	return nil
}

func (svc *Service) ImportSecurityGroup(ctx context.Context, cr *ec2v1beta1.SecurityGroup) error {
	if cr.Annotations == nil {
		cr.Annotations = map[string]string{}
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if !cr.DeletionTimestamp.IsZero() {
		return bizerr.New(bizerr.ParameterInvalid, "deletiontimestamp has value")
	}
	cr.Annotations[xpmeta.AnnotationKeyReconciliationPaused] = "true"
	// the OwnerReferences must be nil, otherwise the resource will be delete automatically
	// since uid in the OwnerReferences cannot match to real resource.
	cr.OwnerReferences = nil
	// TODO: other filed need to change

	// existed cr may used different providerconfig, check the referenced providerconfig is existed
	if cr.Spec.ProviderConfigReference == nil {
		return bizerr.New(bizerr.ParameterInvalid, "providerconfigreference is nil")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}

	key := client.ObjectKeyFromObject(cr)
	if err := upsertObject[*ec2v1beta1.SecurityGroup](ctx, svc.kubeClient, svc.kubeAPIReader, cr); err != nil {
		log.Error("upsert securitygroup failed", zap.String("key", key.String()), zap.Error(err))
		return err
	}
	return nil
}

func (svc *Service) CheckSecurityGroup(ctx context.Context, key client.ObjectKey) error {
	cr := new(ec2v1beta1.SecurityGroup)
	if err := svc.kubeAPIReader.Get(ctx, key, cr); err != nil {
		if apierrors.IsNotFound(err) {
			return bizerr.New(bizerr.ResourceNotFound, err.Error())
		}
		return bizerr.New(bizerr.K8SClientError, err.Error())
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}

	if ref := cr.Spec.ForProvider.VPCIDRef; ref != nil && ref.Name != "" {
		vpc := new(ec2v1beta1.VPC)
		vpc.Name = ref.Name
		if err := svc.kubeAPIReader.Get(ctx, client.ObjectKeyFromObject(vpc), vpc); err != nil {
			if apierrors.IsNotFound(err) {
				return bizerr.New(bizerr.ResourceNotFound, err.Error())
			}
			return bizerr.New(bizerr.K8SClientError, err.Error())
		}
		if pointer.StringDeref(cr.Spec.ForProvider.VPCID, "") != xpmeta.GetExternalName(vpc) {
			return bizerr.New(bizerr.InternalServerError, "vpcid is mismatched")
		}
	}
	for _, c := range cr.Status.Conditions {
		if c.Type == xpv1.TypeSynced && c.Reason == xpv1.ReasonReconcileError {
			return bizerr.New(bizerr.InternalServerError, fmt.Sprintf("xp resource %s sync failed", cr.Name))
		}
	}
	return nil
}

func (svc *Service) ImportSubnet(ctx context.Context, cr *ec2v1beta1.Subnet) error {
	if cr.Annotations == nil {
		cr.Annotations = map[string]string{}
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if !cr.DeletionTimestamp.IsZero() {
		return bizerr.New(bizerr.ParameterInvalid, "deletiontimestamp has value")
	}
	cr.Annotations[xpmeta.AnnotationKeyReconciliationPaused] = "true"
	// the OwnerReferences must be nil, otherwise the resource will be delete automatically
	// since uid in the OwnerReferences cannot match to real resource.
	cr.OwnerReferences = nil
	// TODO: other filed need to change

	// existed cr may used different providerconfig, check the referenced providerconfig is existed
	if cr.Spec.ProviderConfigReference == nil {
		return bizerr.New(bizerr.ParameterInvalid, "providerconfigreference is nil")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}

	key := client.ObjectKeyFromObject(cr)
	if err := upsertObject[*ec2v1beta1.Subnet](ctx, svc.kubeClient, svc.kubeAPIReader, cr); err != nil {
		log.Error("upsert subnet failed", zap.String("key", key.String()), zap.Error(err))
		return err
	}
	return nil
}

func (svc *Service) CheckSubnet(ctx context.Context, key client.ObjectKey) error {
	cr := new(ec2v1beta1.Subnet)
	if err := svc.kubeAPIReader.Get(ctx, key, cr); err != nil {
		if apierrors.IsNotFound(err) {
			return bizerr.New(bizerr.ResourceNotFound, err.Error())
		}
		return bizerr.New(bizerr.K8SClientError, err.Error())
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}
	if ref := cr.Spec.ForProvider.VPCIDRef; ref != nil && ref.Name != "" {
		vpc := new(ec2v1beta1.VPC)
		vpc.Name = ref.Name
		if err := svc.kubeAPIReader.Get(ctx, client.ObjectKeyFromObject(vpc), vpc); err != nil {
			if apierrors.IsNotFound(err) {
				return bizerr.New(bizerr.ResourceNotFound, err.Error())
			}
			return bizerr.New(bizerr.K8SClientError, err.Error())
		}
		if pointer.StringDeref(cr.Spec.ForProvider.VPCID, "") != xpmeta.GetExternalName(vpc) {
			return bizerr.New(bizerr.InternalServerError, "vpcid is mismatched")
		}
	}
	for _, c := range cr.Status.Conditions {
		if c.Type == xpv1.TypeSynced && c.Reason == xpv1.ReasonReconcileError {
			return bizerr.New(bizerr.InternalServerError, fmt.Sprintf("xp resource %s sync failed", cr.Name))
		}
	}
	return nil
}

func (svc *Service) ImportVPCEndpoint(ctx context.Context, cr *ec2v1alpha1.VPCEndpoint) error {
	if cr.Annotations == nil {
		cr.Annotations = map[string]string{}
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if !cr.DeletionTimestamp.IsZero() {
		return bizerr.New(bizerr.ParameterInvalid, "deletiontimestamp has value")
	}
	cr.Annotations[xpmeta.AnnotationKeyReconciliationPaused] = "true"
	// the OwnerReferences must be nil, otherwise the resource will be delete automatically
	// since uid in the OwnerReferences cannot match to real resource.
	cr.OwnerReferences = nil
	// TODO: other filed need to change

	// existed cr may used different providerconfig, check the referenced providerconfig is existed
	if cr.Spec.ProviderConfigReference == nil {
		return bizerr.New(bizerr.ParameterInvalid, "providerconfigreference is nil")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}

	key := client.ObjectKeyFromObject(cr)
	if err := upsertObject[*ec2v1alpha1.VPCEndpoint](ctx, svc.kubeClient, svc.kubeAPIReader, cr); err != nil {
		log.Error("upsert vpcendpoint failed", zap.String("key", key.String()), zap.Error(err))
		return err
	}
	return nil
}

func (svc *Service) CheckVPCEndpoint(ctx context.Context, key client.ObjectKey) error {
	cr := new(ec2v1alpha1.VPCEndpoint)
	if err := svc.kubeAPIReader.Get(ctx, key, cr); err != nil {
		if apierrors.IsNotFound(err) {
			return bizerr.New(bizerr.ResourceNotFound, err.Error())
		}
		return bizerr.New(bizerr.K8SClientError, err.Error())
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}
	if ref := cr.Spec.ForProvider.VPCIDRef; ref != nil && ref.Name != "" {
		vpc := new(ec2v1beta1.VPC)
		vpc.Name = ref.Name
		if err := svc.kubeAPIReader.Get(ctx, client.ObjectKeyFromObject(vpc), vpc); err != nil {
			if apierrors.IsNotFound(err) {
				return bizerr.New(bizerr.ResourceNotFound, err.Error())
			}
			return bizerr.New(bizerr.K8SClientError, err.Error())
		}
		if pointer.StringDeref(cr.Spec.ForProvider.VPCID, "") != xpmeta.GetExternalName(vpc) {
			return bizerr.New(bizerr.InternalServerError, "vpcid is mismatched")
		}
	}
	for idx := range cr.Spec.ForProvider.RouteTableIDRefs {
		ref := cr.Spec.ForProvider.RouteTableIDRefs[idx]
		id := cr.Spec.ForProvider.RouteTableIDs[idx]
		if ref.Name != "" {
			rt := new(ec2v1beta1.RouteTable)
			rt.Name = ref.Name
			if err := svc.kubeAPIReader.Get(ctx, client.ObjectKeyFromObject(rt), rt); err != nil {
				if apierrors.IsNotFound(err) {
					return bizerr.New(bizerr.ResourceNotFound, err.Error())
				}
				return bizerr.New(bizerr.K8SClientError, err.Error())
			}
			if pointer.StringDeref(id, "") != xpmeta.GetExternalName(rt) {
				return bizerr.New(bizerr.InternalServerError, "routetableid is mismatched")
			}
		}
	}
	for _, c := range cr.Status.Conditions {
		if c.Type == xpv1.TypeSynced && c.Reason == xpv1.ReasonReconcileError {
			return bizerr.New(bizerr.InternalServerError, fmt.Sprintf("xp resource %s sync failed", cr.Name))
		}
	}

	return nil
}

func (svc *Service) ImportVPCEndpointServiceConfiguration(ctx context.Context, cr *ec2v1alpha1.VPCEndpointServiceConfiguration) error {
	if cr.Annotations == nil {
		cr.Annotations = map[string]string{}
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if !cr.DeletionTimestamp.IsZero() {
		return bizerr.New(bizerr.ParameterInvalid, "deletiontimestamp has value")
	}
	cr.Annotations[xpmeta.AnnotationKeyReconciliationPaused] = "true"
	// the OwnerReferences must be nil, otherwise the resource will be delete automatically
	// since uid in the OwnerReferences cannot match to real resource.
	cr.OwnerReferences = nil
	// TODO: other filed need to change

	// existed cr may used different providerconfig, check the referenced providerconfig is existed
	if cr.Spec.ProviderConfigReference == nil {
		return bizerr.New(bizerr.ParameterInvalid, "providerconfigreference is nil")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}

	key := client.ObjectKeyFromObject(cr)
	if err := upsertObject[*ec2v1alpha1.VPCEndpointServiceConfiguration](ctx, svc.kubeClient, svc.kubeAPIReader, cr); err != nil {
		log.Error("upsert vpcendpointserviceconfiguration failed", zap.String("key", key.String()), zap.Error(err))
		return err
	}
	return nil
}

func (svc *Service) CheckVPCEndpointServiceConfiguration(ctx context.Context, key client.ObjectKey) error {
	cr := new(ec2v1alpha1.VPCEndpointServiceConfiguration)
	if err := svc.kubeAPIReader.Get(ctx, key, cr); err != nil {
		if apierrors.IsNotFound(err) {
			return bizerr.New(bizerr.ResourceNotFound, err.Error())
		}
		return bizerr.New(bizerr.K8SClientError, err.Error())
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}
	for _, c := range cr.Status.Conditions {
		if c.Type == xpv1.TypeSynced && c.Reason == xpv1.ReasonReconcileError {
			return bizerr.New(bizerr.InternalServerError, fmt.Sprintf("xp resource %s sync failed", cr.Name))
		}
	}
	return nil
}

func (svc *Service) ImportNodeGroup(ctx context.Context, cr *manualv1alpha1.NodeGroup) error {
	if cr.Annotations == nil {
		cr.Annotations = map[string]string{}
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if !cr.DeletionTimestamp.IsZero() {
		return bizerr.New(bizerr.ParameterInvalid, "deletiontimestamp has value")
	}
	cr.Annotations[xpmeta.AnnotationKeyReconciliationPaused] = "true"
	// the OwnerReferences must be nil, otherwise the resource will be delete automatically
	// since uid in the OwnerReferences cannot match to real resource.
	cr.OwnerReferences = nil
	// TODO: other filed need to change

	// existed cr may used different providerconfig, check the referenced providerconfig is existed
	if cr.Spec.ProviderConfigReference == nil {
		return bizerr.New(bizerr.ParameterInvalid, "providerconfigreference is nil")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}

	key := client.ObjectKeyFromObject(cr)
	if err := upsertObject[*manualv1alpha1.NodeGroup](ctx, svc.kubeClient, svc.kubeAPIReader, cr); err != nil {
		log.Error("upsert nodegroup failed", zap.String("key", key.String()), zap.Error(err))
		return err
	}
	return nil
}

func (svc *Service) CheckNodeGroup(ctx context.Context, key client.ObjectKey) error {
	cr := new(manualv1alpha1.NodeGroup)
	if err := svc.kubeAPIReader.Get(ctx, key, cr); err != nil {
		if apierrors.IsNotFound(err) {
			return bizerr.New(bizerr.ResourceNotFound, err.Error())
		}
		return bizerr.New(bizerr.K8SClientError, err.Error())
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}
	if tpl := cr.Spec.ForProvider.LaunchTemplate; tpl != nil {
		if ref := tpl.NameRef; ref != nil && ref.Name != "" {
			ltpl := new(ec2v1alpha1.LaunchTemplate)
			ltpl.Name = ref.Name
			if err := svc.kubeAPIReader.Get(ctx, client.ObjectKeyFromObject(ltpl), ltpl); err != nil {
				if apierrors.IsNotFound(err) {
					return bizerr.New(bizerr.ResourceNotFound, err.Error())
				}
				return bizerr.New(bizerr.K8SClientError, err.Error())
			}
			if pointer.StringDeref(tpl.Name, "") != xpmeta.GetExternalName(ltpl) {
				return bizerr.New(bizerr.InternalServerError, "launchtemplatename is mismatched")
			}
		}
	}
	for _, c := range cr.Status.Conditions {
		if c.Type == xpv1.TypeSynced && c.Reason == xpv1.ReasonReconcileError {
			return bizerr.New(bizerr.InternalServerError, fmt.Sprintf("xp resource %s sync failed", cr.Name))
		}
	}
	return nil
}

func (svc *Service) ImportInstanceProfile(ctx context.Context, cr *iamv1alpha1.InstanceProfile) error {
	if cr.Annotations == nil {
		cr.Annotations = map[string]string{}
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if !cr.DeletionTimestamp.IsZero() {
		return bizerr.New(bizerr.ParameterInvalid, "deletiontimestamp has value")
	}
	cr.Annotations[xpmeta.AnnotationKeyReconciliationPaused] = "true"
	// the OwnerReferences must be nil, otherwise the resource will be delete automatically
	// since uid in the OwnerReferences cannot match to real resource.
	cr.OwnerReferences = nil
	// TODO: other filed need to change

	// existed cr may used different providerconfig, check the referenced providerconfig is existed
	if cr.Spec.ProviderConfigReference == nil {
		return bizerr.New(bizerr.ParameterInvalid, "providerconfigreference is nil")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}

	key := client.ObjectKeyFromObject(cr)
	if err := upsertObject[*iamv1alpha1.InstanceProfile](ctx, svc.kubeClient, svc.kubeAPIReader, cr); err != nil {
		log.Error("upsert instanceprofile failed", zap.String("key", key.String()), zap.Error(err))
		return err
	}
	return nil
}

func (svc *Service) CheckInstanceProfile(ctx context.Context, key client.ObjectKey) error {
	cr := new(iamv1alpha1.InstanceProfile)
	if err := svc.kubeAPIReader.Get(ctx, key, cr); err != nil {
		if apierrors.IsNotFound(err) {
			return bizerr.New(bizerr.ResourceNotFound, err.Error())
		}
		return bizerr.New(bizerr.K8SClientError, err.Error())
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}
	if ref := cr.Spec.ForProvider.RoleRef; ref != nil && ref.Name != "" {
		role := new(iamv1beta1.Role)
		role.Name = ref.Name
		if err := svc.kubeAPIReader.Get(ctx, client.ObjectKeyFromObject(role), role); err != nil {
			if apierrors.IsNotFound(err) {
				return bizerr.New(bizerr.ResourceNotFound, err.Error())
			}
			return bizerr.New(bizerr.K8SClientError, err.Error())
		}
		if pointer.StringDeref(cr.Spec.ForProvider.Role, "") != xpmeta.GetExternalName(role) {
			return bizerr.New(bizerr.InternalServerError, "rolename is mismatched")
		}
	}
	for _, c := range cr.Status.Conditions {
		if c.Type == xpv1.TypeSynced && c.Reason == xpv1.ReasonReconcileError {
			return bizerr.New(bizerr.InternalServerError, fmt.Sprintf("xp resource %s sync failed", cr.Name))
		}
	}
	return nil
}

func (svc *Service) ImportPolicy(ctx context.Context, cr *iamv1beta1.Policy) error {
	if cr.Annotations == nil {
		cr.Annotations = map[string]string{}
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if !cr.DeletionTimestamp.IsZero() {
		return bizerr.New(bizerr.ParameterInvalid, "deletiontimestamp has value")
	}
	cr.Annotations[xpmeta.AnnotationKeyReconciliationPaused] = "true"
	// the OwnerReferences must be nil, otherwise the resource will be delete automatically
	// since uid in the OwnerReferences cannot match to real resource.
	cr.OwnerReferences = nil
	// TODO: other filed need to change

	// existed cr may used different providerconfig, check the referenced providerconfig is existed
	if cr.Spec.ProviderConfigReference == nil {
		return bizerr.New(bizerr.ParameterInvalid, "providerconfigreference is nil")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}

	key := client.ObjectKeyFromObject(cr)
	if err := upsertObject[*iamv1beta1.Policy](ctx, svc.kubeClient, svc.kubeAPIReader, cr); err != nil {
		log.Error("upsert policy failed", zap.String("key", key.String()), zap.Error(err))
		return err
	}
	return nil
}

func (svc *Service) CheckPolicy(ctx context.Context, key client.ObjectKey) error {
	cr := new(iamv1beta1.Policy)
	if err := svc.kubeAPIReader.Get(ctx, key, cr); err != nil {
		if apierrors.IsNotFound(err) {
			return bizerr.New(bizerr.ResourceNotFound, err.Error())
		}
		return bizerr.New(bizerr.K8SClientError, err.Error())
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}
	for _, c := range cr.Status.Conditions {
		if c.Type == xpv1.TypeSynced && c.Reason == xpv1.ReasonReconcileError {
			return bizerr.New(bizerr.InternalServerError, fmt.Sprintf("xp resource %s sync failed", cr.Name))
		}
	}
	return nil
}

func (svc *Service) ImportRolePolicyAttachment(ctx context.Context, cr *iamv1beta1.RolePolicyAttachment) error {
	if cr.Annotations == nil {
		cr.Annotations = map[string]string{}
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if !cr.DeletionTimestamp.IsZero() {
		return bizerr.New(bizerr.ParameterInvalid, "deletiontimestamp has value")
	}
	cr.Annotations[xpmeta.AnnotationKeyReconciliationPaused] = "true"
	// the OwnerReferences must be nil, otherwise the resource will be delete automatically
	// since uid in the OwnerReferences cannot match to real resource.
	cr.OwnerReferences = nil
	// TODO: other filed need to change

	// existed cr may used different providerconfig, check the referenced providerconfig is existed
	if cr.Spec.ProviderConfigReference == nil {
		return bizerr.New(bizerr.ParameterInvalid, "providerconfigreference is nil")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}

	key := client.ObjectKeyFromObject(cr)
	if err := upsertObject[*iamv1beta1.RolePolicyAttachment](ctx, svc.kubeClient, svc.kubeAPIReader, cr); err != nil {
		log.Error("upsert rolepolicyattachment failed", zap.String("key", key.String()), zap.Error(err))
		return err
	}
	return nil
}

func (svc *Service) CheckRolePolicyAttachment(ctx context.Context, key client.ObjectKey) error {
	cr := new(iamv1beta1.RolePolicyAttachment)
	if err := svc.kubeAPIReader.Get(ctx, key, cr); err != nil {
		if apierrors.IsNotFound(err) {
			return bizerr.New(bizerr.ResourceNotFound, err.Error())
		}
		return bizerr.New(bizerr.K8SClientError, err.Error())
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}
	if ref := cr.Spec.ForProvider.RoleNameRef; ref != nil && ref.Name != "" {
		role := new(iamv1beta1.Role)
		role.Name = ref.Name
		if err := svc.kubeAPIReader.Get(ctx, client.ObjectKeyFromObject(role), role); err != nil {
			if apierrors.IsNotFound(err) {
				return bizerr.New(bizerr.ResourceNotFound, err.Error())
			}
			return bizerr.New(bizerr.K8SClientError, err.Error())
		}
		if pointer.StringDeref(&cr.Spec.ForProvider.RoleName, "") != xpmeta.GetExternalName(role) {
			return bizerr.New(bizerr.InternalServerError, "rolename is mismatched")
		}
	}
	for _, c := range cr.Status.Conditions {
		if c.Type == xpv1.TypeSynced && c.Reason == xpv1.ReasonReconcileError {
			return bizerr.New(bizerr.InternalServerError, fmt.Sprintf("xp resource %s sync failed", cr.Name))
		}
	}
	return nil
}

func (svc *Service) ImportRole(ctx context.Context, cr *iamv1beta1.Role) error {
	if cr.Annotations == nil {
		cr.Annotations = map[string]string{}
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if !cr.DeletionTimestamp.IsZero() {
		return bizerr.New(bizerr.ParameterInvalid, "deletiontimestamp has value")
	}
	cr.Annotations[xpmeta.AnnotationKeyReconciliationPaused] = "true"
	// the OwnerReferences must be nil, otherwise the resource will be delete automatically
	// since uid in the OwnerReferences cannot match to real resource.
	cr.OwnerReferences = nil
	// TODO: other filed need to change

	// existed cr may used different providerconfig, check the referenced providerconfig is existed
	if cr.Spec.ProviderConfigReference == nil {
		return bizerr.New(bizerr.ParameterInvalid, "providerconfigreference is nil")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}

	key := client.ObjectKeyFromObject(cr)
	if err := upsertObject[*iamv1beta1.Role](ctx, svc.kubeClient, svc.kubeAPIReader, cr); err != nil {
		log.Error("upsert role failed", zap.String("key", key.String()), zap.Error(err))
		return err
	}
	return nil
}

func (svc *Service) CheckRole(ctx context.Context, key client.ObjectKey) error {
	cr := new(iamv1beta1.Role)
	if err := svc.kubeAPIReader.Get(ctx, key, cr); err != nil {
		if apierrors.IsNotFound(err) {
			return bizerr.New(bizerr.ResourceNotFound, err.Error())
		}
		return bizerr.New(bizerr.K8SClientError, err.Error())
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}
	for _, c := range cr.Status.Conditions {
		if c.Type == xpv1.TypeSynced && c.Reason == xpv1.ReasonReconcileError {
			return bizerr.New(bizerr.InternalServerError, fmt.Sprintf("xp resource %s sync failed", cr.Name))
		}
	}
	return nil
}

func (svc *Service) ImportResourceRecordSet(ctx context.Context, cr *route53v1alpha1.ResourceRecordSet) error {
	if cr.Annotations == nil {
		cr.Annotations = map[string]string{}
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if !cr.DeletionTimestamp.IsZero() {
		return bizerr.New(bizerr.ParameterInvalid, "deletiontimestamp has value")
	}
	cr.Annotations[xpmeta.AnnotationKeyReconciliationPaused] = "true"
	// the OwnerReferences must be nil, otherwise the resource will be delete automatically
	// since uid in the OwnerReferences cannot match to real resource.
	cr.OwnerReferences = nil
	// TODO: other filed need to change

	// existed cr may used different providerconfig, check the referenced providerconfig is existed
	if cr.Spec.ProviderConfigReference == nil {
		return bizerr.New(bizerr.ParameterInvalid, "providerconfigreference is nil")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}

	key := client.ObjectKeyFromObject(cr)
	if err := upsertObject[*route53v1alpha1.ResourceRecordSet](ctx, svc.kubeClient, svc.kubeAPIReader, cr); err != nil {
		log.Error("upsert resourcerecordset failed", zap.String("key", key.String()), zap.Error(err))
		return err
	}
	return nil
}

func (svc *Service) CheckResourceRecordSet(ctx context.Context, key client.ObjectKey) error {
	cr := new(route53v1alpha1.ResourceRecordSet)
	if err := svc.kubeAPIReader.Get(ctx, key, cr); err != nil {
		if apierrors.IsNotFound(err) {
			return bizerr.New(bizerr.ResourceNotFound, err.Error())
		}
		return bizerr.New(bizerr.K8SClientError, err.Error())
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}
	for _, c := range cr.Status.Conditions {
		if c.Type == xpv1.TypeSynced && c.Reason == xpv1.ReasonReconcileError {
			return bizerr.New(bizerr.InternalServerError, fmt.Sprintf("xp resource %s sync failed", cr.Name))
		}
	}
	return nil
}

func (svc *Service) ImportVPCPeeringConnection(ctx context.Context, cr *ec2v1alpha1.VPCPeeringConnection) error {
	if cr.Annotations == nil {
		cr.Annotations = map[string]string{}
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if !cr.DeletionTimestamp.IsZero() {
		return bizerr.New(bizerr.ParameterInvalid, "deletiontimestamp has value")
	}
	cr.Annotations[xpmeta.AnnotationKeyReconciliationPaused] = "true"
	// TODO: other filed need to change
	// the OwnerReferences must be nil, otherwise the resource will be delete automatically
	// since uid in the OwnerReferences cannot match to real resource.
	cr.OwnerReferences = nil

	// existed cr may used different providerconfig, check the referenced providerconfig is existed
	if cr.Spec.ProviderConfigReference == nil {
		return bizerr.New(bizerr.ParameterInvalid, "providerconfigreference is nil")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}

	key := client.ObjectKeyFromObject(cr)
	if err := upsertObject[*ec2v1alpha1.VPCPeeringConnection](ctx, svc.kubeClient, svc.kubeAPIReader, cr); err != nil {
		log.Error("upsert vpcpeeringconnection failed", zap.String("key", key.String()), zap.Error(err))
		return err
	}
	return nil
}

func (svc *Service) CheckVPCPeeringConnection(ctx context.Context, key client.ObjectKey) error {
	cr := new(ec2v1alpha1.VPCPeeringConnection)
	if err := svc.kubeAPIReader.Get(ctx, key, cr); err != nil {
		if apierrors.IsNotFound(err) {
			return bizerr.New(bizerr.ResourceNotFound, err.Error())
		}
		return bizerr.New(bizerr.K8SClientError, err.Error())
	}
	if xpmeta.GetExternalName(cr) == "" {
		return bizerr.New(bizerr.ParameterInvalid, "external name is empty")
	}
	if err := svc.checkProviderConfigExisted(ctx, cr.Spec.ProviderConfigReference.Name); err != nil {
		return err
	}
	if ref := cr.Spec.ForProvider.VPCIDRef; ref != nil && ref.Name != "" {
		vpc := new(ec2v1beta1.VPC)
		vpc.Name = ref.Name
		if err := svc.kubeAPIReader.Get(ctx, client.ObjectKeyFromObject(vpc), vpc); err != nil {
			if apierrors.IsNotFound(err) {
				return bizerr.New(bizerr.ResourceNotFound, err.Error())
			}
			return bizerr.New(bizerr.K8SClientError, err.Error())
		}
		if pointer.StringDeref(cr.Spec.ForProvider.VPCID, "") != xpmeta.GetExternalName(vpc) {
			return bizerr.New(bizerr.InternalServerError, "vpcid is mismatched")
		}
	}
	for _, c := range cr.Status.Conditions {
		if c.Type == xpv1.TypeSynced && c.Reason == xpv1.ReasonReconcileError {
			return bizerr.New(bizerr.InternalServerError, fmt.Sprintf("xp resource %s sync failed", cr.Name))
		}
	}
	return nil
}

func (svc *Service) PauseReconcile(ctx context.Context, cr client.Object, pause bool) error {
	patch := []byte(fmt.Sprintf(patchAnnotationTPL,
		xpmeta.AnnotationKeyReconciliationPaused,
		strconv.FormatBool(pause)))
	if err := svc.kubeClient.Patch(context.Background(), cr, client.RawPatch(types.MergePatchType, patch)); err != nil {
		log.Error("patch cr pause annotation failed", zap.Error(err), zap.Any("key", client.ObjectKeyFromObject(cr)))
		return err
	}
	return nil
}
