package eks

import (
	"context"
	"fmt"
	"sort"
	"strings"
	"time"

	"github.com/aws/smithy-go/ptr"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/crossplane/crossplane-runtime/pkg/meta"
	"github.com/google/go-cmp/cmp"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/cloud/aws"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/consts"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/appbox/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/k8s"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
	ec2v1beta1 "github.com/upbound/provider-aws/apis/ec2/v1beta1"
	eksv1beta1 "github.com/upbound/provider-aws/apis/eks/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/pointer"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
	subnetTagRolePublicELB   = "kubernetes.io/role/elb"
	subnetTagRoleInternalELB = "kubernetes.io/role/internal-elb"
)

type EKS struct {
	*task.BaseTask
	awsCli aws.Client

	Manager *controllers.Manager
}

func NewEKS(ctx *task.Context, awsCli aws.Client, manager *controllers.Manager) *EKS {
	return &EKS{BaseTask: &task.BaseTask{Context: ctx}, awsCli: awsCli, Manager: manager}
}

func (t *EKS) Name() string {
	return "EKS"
}

func (t *EKS) buildEKS(ctx context.Context) (*eksv1beta1.Cluster, error) {
	eksName := t.K8sName()
	providerConfigName := t.ProviderConfigName()
	tags := controllers.ConvertToCloudTags(t.APPBox.Labels, cloudv1.ProviderTypeEKS)
	tags = util.BuildAWSTagWithName(tags, eksName)
	tags[consts.TagKeyCrossplaneVendor] = consts.TagValCrossplaneVendor
	tags = util.MergeMaps(tags, t.Config.AdditionalCloudTags)
	version := t.APPBox.Spec.KubernetesConfig.Version
	labels := util.MergeMaps(t.Namer.Labels(), t.APPBox.Labels)
	cluster := &eksv1beta1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:   eksName,
			Labels: labels,
		},
		Spec: eksv1beta1.ClusterSpec{
			InitProvider: eksv1beta1.ClusterInitParameters{
				BootstrapSelfManagedAddons: ptr.Bool(true),
			},
			ForProvider: eksv1beta1.ClusterParameters{
				AccessConfig: []eksv1beta1.AccessConfigParameters{
					{
						AuthenticationMode:                      ptr.String("API_AND_CONFIG_MAP"),
						BootstrapClusterCreatorAdminPermissions: ptr.Bool(true),
					},
				},
				Region:  pointer.String(t.APPBox.Spec.Region),
				Version: pointer.String(version), // this is the only filed can be update after creation.
				VPCConfig: []eksv1beta1.VPCConfigParameters{
					{
						EndpointPrivateAccess: pointer.Bool(true),
						EndpointPublicAccess:  pointer.Bool(t.Config.APPBoxConfig.EnableK8sPublicAccess),
					},
				},
				RoleArn: ptr.String(util.GetRoleARN(t.VPC.Spec.AccountRef.AccountID, t.Config.APPBoxConfig.ServiceRoleName)),
				Tags:    util.ToMapPointer(tags),
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: providerConfigName},
				ManagementPolicies:      controllers.GetResourceManagementPolicies(),
			},
		},
	}

	serviceCidr := t.APPBox.Spec.KubernetesConfig.ServiceCidr
	if serviceCidr == "" {
		serviceCidr = t.Config.APPBoxConfig.K8sServiceCidr
	}
	if serviceCidr == "" {
		t.Logger.Info("No service CIDR is configured, use EKS default value")
	} else {
		// Set service CIDR in initProvider since it's immutable
		cluster.Spec.InitProvider.KubernetesNetworkConfig = []eksv1beta1.KubernetesNetworkConfigInitParameters{
			{
				IPFamily:        pointer.String("ipv4"),
				ServiceIPv4Cidr: pointer.String(serviceCidr),
			},
		}
	}

	cluster.ObjectMeta.Labels = util.MergeMaps(cluster.ObjectMeta.Labels, k8s.SanitizeLabels(tags))

	if updated, err := t.disablePublicAccessForBYOC(ctx, cluster); err != nil {
		return nil, err
	} else if updated && t.Manager != nil {
		// if we disable public access for the EKS, we need to re-register the K8s manager to use the private endpoint
		// we deregister the old one here, and the controller will register the new one.
		t.Manager.Deregister(ctx, t.APPBox)
	}

	_, subnetRefs, err := t.controlPlaneSubnets()
	if err != nil {
		return nil, err
	}
	cluster.Spec.ForProvider.VPCConfig[0].SubnetIDRefs = subnetRefs

	if t.Config.APPBoxConfig.EnableK8sPublicAccess {
		cluster.Spec.ForProvider.VPCConfig[0].PublicAccessCidrs = util.ToSlicePointer(t.Config.APPBoxConfig.ApiserverPublicAccessCidrs)
	}
	if err := t.ignoreChanges(ctx, cluster); err != nil {
		return nil, err
	}
	return cluster, nil
}

func (t *EKS) ignoreChanges(ctx context.Context, newCluster *eksv1beta1.Cluster) error {
	key := types.NamespacedName{
		Name: newCluster.GetName(),
	}
	existing := &eksv1beta1.Cluster{}
	if err := t.Local.Get(ctx, key, existing); err != nil {
		return client.IgnoreNotFound(err)
	}
	// ignore the changes on roleArn, since it is immutable.
	newCluster.Spec.ForProvider.RoleArn = existing.Spec.ForProvider.RoleArn
	return nil
}

// IsSafeToApplyEKS check the generated eks cr has the same field as the existed eks cr,
// but only focuse on some fileds.
func (t *EKS) IsSafeToApplyEKS(ctx context.Context, existedCluster *eksv1beta1.Cluster) (bool, error) {
	cluster, err := t.buildEKS(ctx)
	if err != nil {
		return false, err
	}
	if cluster.Spec.ProviderConfigReference.Name != existedCluster.Spec.ProviderConfigReference.Name {
		t.Logger.Info("detect diff on Spec.ProviderConfigReference.Name",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", cluster.Spec.ProviderConfigReference.Name,
			"existed", existedCluster.Spec.ProviderConfigReference.Name,
		)
		return false, nil
	}
	generated := cluster.Spec.ForProvider      // the generated cr
	existed := existedCluster.Spec.ForProvider // the existed cr
	if *generated.Region != *existed.Region {
		t.Logger.Info("detect diff on Spec.ForProvider.Region",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", *generated.Region,
			"existed", *existed.Region,
		)
		return false, nil
	}
	if *generated.Version != *existed.Version {
		t.Logger.Info("detect diff on Spec.ForProvider.Version",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", *generated.Region,
			"existed", *existed.Region,
		)
		return false, nil
	}
	if *generated.VPCConfig[0].EndpointPublicAccess != *existed.VPCConfig[0].EndpointPublicAccess {
		t.Logger.Info("detect diff on Spec.ForProvider.ResourcesVpcConfig.EndpointPublicAccess",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", *generated.VPCConfig[0].EndpointPublicAccess,
			"existed", *existed.VPCConfig[0].EndpointPublicAccess,
		)
		return false, nil
	}
	if *generated.VPCConfig[0].EndpointPrivateAccess != *existed.VPCConfig[0].EndpointPrivateAccess {
		t.Logger.Info("detect diff on Spec.ForProvider.ResourcesVpcConfig.EndpointPrivateAccess",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", *generated.VPCConfig[0].EndpointPrivateAccess,
			"existed", *existed.VPCConfig[0].EndpointPrivateAccess,
		)
		return false, nil
	}
	if *generated.RoleArn != *existed.RoleArn {
		t.Logger.Info("detect diff on Spec.ForProvider.RoleArn",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", generated.RoleArn,
			"existed", existed.RoleArn,
		)
		return false, nil
	}
	// these three tags should not take into consideration when perform the equality checking.
	// ExternalResourceTagKeyKind     = "crossplane-kind"
	// ExternalResourceTagKeyName     = "crossplane-name"
	// ExternalResourceTagKeyProvider = "crossplane-providerconfig"
	existedTags := map[string]string{}
	for k, v := range existed.Tags {
		switch k {
		case "crossplane-kind", "crossplane-name", "crossplane-providerconfig":
			continue
		}
		existedTags[k] = *v
	}
	// if the generated tags contains all existed tags, we consider it safe to apply.
	// thus adding tag on existed eks is consider as a safe operation.
	for k, v := range existedTags {
		if *generated.Tags[k] != v {
			t.Logger.Info("detect diff on Spec.ForProvider.Tags",
				"key", client.ObjectKeyFromObject(existedCluster).String(),
				"generated", generated.Tags,
				"existed", existedTags,
			)
			return false, nil
		}
	}
	// if fmt.Sprint(generated.VPCConfig[0].SecurityGroupIds) != fmt.Sprint(existed.VPCConfig[0].SecurityGroupIds) {
	// 	t.Logger.Info("detect diff on Spec.ForProvider.ResourcesVpcConfig.SecurityGroupIDs",
	// 		"key", client.ObjectKeyFromObject(existedCluster).String(),
	// 		"generated", generated.VPCConfig[0].SecurityGroupIds,
	// 		"existed", existed.VPCConfig[0].SecurityGroupIds,
	// 	)
	// 	return false, nil
	// }
	generatedCidrsSorted := make([]string, 0, len(generated.VPCConfig[0].PublicAccessCidrs))
	for _, cidr := range generated.VPCConfig[0].PublicAccessCidrs {
		generatedCidrsSorted = append(generatedCidrsSorted, *cidr)
	}
	existedCidrsSorted := make([]string, 0, len(existed.VPCConfig[0].PublicAccessCidrs))
	for _, cidr := range existed.VPCConfig[0].PublicAccessCidrs {
		existedCidrsSorted = append(existedCidrsSorted, *cidr)
	}
	sort.Strings(generatedCidrsSorted)
	sort.Strings(existedCidrsSorted)

	if !cmp.Equal(generatedCidrsSorted, existedCidrsSorted) {
		t.Logger.Info("detect diff on Spec.ForProvider.ResourcesVpcConfig.PublicAccessCidrs",
			"key", client.ObjectKeyFromObject(existedCluster).String(),
			"generated", generated.VPCConfig[0].PublicAccessCidrs,
			"existed", existed.VPCConfig[0].PublicAccessCidrs,
		)
		return false, nil
	}
	// this is a workaround comparison.
	if len(existed.VPCConfig[0].SubnetIDRefs) == 0 {
		ids, refs, err := t.controlPlaneSubnets()
		if err != nil {
			return false, err
		}
		if fmt.Sprint(generated.VPCConfig[0].SubnetIDRefs) != fmt.Sprint(refs) {
			t.Logger.Info("detect diff on Spec.ForProvider.ResourcesVpcConfig.SubnetIDRefs",
				"key", client.ObjectKeyFromObject(existedCluster).String(),
				"generated", generated.VPCConfig[0].SubnetIDRefs,
				"existed", refs,
			)
			return false, nil
		}
		if fmt.Sprint(ids) != fmt.Sprint(existed.VPCConfig[0].SubnetIds) {
			t.Logger.Info("detect diff on Spec.ForProvider.ResourcesVpcConfig.SubnetID",
				"key", client.ObjectKeyFromObject(existedCluster).String(),
				"generated", ids,
				"existed", existed.VPCConfig[0].SubnetIds,
			)
			return false, nil
		}
	} else {
		if fmt.Sprint(generated.VPCConfig[0].SubnetIDRefs) != fmt.Sprint(existed.VPCConfig[0].SubnetIDRefs) {
			t.Logger.Info("detect diff on Spec.ForProvider.ResourcesVpcConfig.SubnetIDRefs",
				"key", client.ObjectKeyFromObject(existedCluster).String(),
				"generated", generated.VPCConfig[0].SubnetIDRefs,
				"existed", existed.VPCConfig[0].SubnetIDRefs,
			)
			return false, nil
		}
	}
	return true, nil
}

func (t *EKS) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())

	if t.APPBox.Annotations[v1alpha1.AnnotationKeyLegacyAPPBoxSkipEKSTask] == "true" {
		return deploy.Done()
	}
	cluster, err := t.buildEKS(ctx)
	if err != nil {
		return deploy.Panic(err)
	}
	return deploy.PanicOrDone(k8s.Apply(ctx, t.Local, cluster))
}

func (t *EKS) PostCreate(ctx context.Context) (res deploy.Result) {
	key := types.NamespacedName{
		Name: t.K8sName(),
	}
	var c eksv1beta1.Cluster
	err := t.Local.Get(ctx, key, &c)
	if err != nil {
		return deploy.Panic(err)
	}

	t.Cluster = task.NewEKSClusterIdentifier(&c)
	t.APPBox.Status.KubernetesClusterName = c.Name
	status := c.Status.AtProvider.Status
	if status == nil || strings.ToUpper(*status) != "ACTIVE" {
		t.APPBox.Status.Conditions.SetCondition(metav1.Condition{
			Type:               v1alpha1.APPBoxConditionK8SReady,
			Status:             metav1.ConditionFalse,
			Reason:             "Creating",
			Message:            fmt.Sprintf("EKS Cluster has the status of %v", status),
			LastTransitionTime: metav1.Now(),
		})
		return deploy.Error(fmt.Errorf("eks cluster status not ready: %v", status)).WithRequeuAfter(3 * time.Second)
	}

	if c.Spec.ForProvider.Version != nil && c.Status.AtProvider.Version != nil &&
		*c.Status.AtProvider.Version != *c.Spec.ForProvider.Version {
		t.APPBox.Status.Conditions.SetCondition(metav1.Condition{
			Type:               v1alpha1.APPBoxConditionK8SReady,
			Status:             metav1.ConditionFalse,
			Reason:             "Upgrading",
			Message:            fmt.Sprintf("EKS Cluster is upgrading to version %v", *c.Spec.ForProvider.Version),
			LastTransitionTime: metav1.Now(),
		})
		return deploy.Error(fmt.Errorf("eks cluster is upgrading to version %v", *c.Spec.ForProvider.Version)).WithRequeuAfter(3 * time.Second)
	}

	t.APPBox.Status.Conditions.SetCondition(metav1.Condition{
		Type:               v1alpha1.APPBoxConditionK8SReady,
		Status:             metav1.ConditionTrue,
		Reason:             "EKSReady",
		Message:            "EKS Creation/Upgrade succeeded",
		LastTransitionTime: metav1.Now(),
	})
	if err = t.setSubnetTags(ctx, c.Name); err != nil {
		return deploy.Error(fmt.Errorf("update subNet tags failed: %w", err))
	}
	// remove the skip annotation if it is safe to apply eks for legacy appbox.
	if t.APPBox.Annotations[v1alpha1.AnnotationKeyLegacyAPPBoxSkipEKSTask] == "true" {
		ok, err := t.IsSafeToApplyEKS(ctx, &c)
		if err != nil {
			return deploy.Error(fmt.Errorf("check safe to apply eks for legacy appbox failed: %w", err))
		}
		if ok {
			delete(t.APPBox.Annotations, v1alpha1.AnnotationKeyLegacyAPPBoxSkipEKSTask)
		}
	}

	return deploy.Done()
}

func (t *EKS) Delete(ctx context.Context) deploy.Result {
	cluster := &eksv1beta1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.K8sName(),
		},
	}
	err := t.Local.Get(ctx, client.ObjectKeyFromObject(cluster), cluster)
	if err != nil {
		if client.IgnoreNotFound(err) != nil {
			return deploy.Error(err)
		}
		// cluster is deleted
		return deploy.Done()
	}
	cluster.Spec.ResourceSpec.ManagementPolicies = xpv1.ManagementPolicies{xpv1.ManagementActionAll}
	if err := k8s.PatchObject(ctx, t.Local, cluster, controllers.ManagementPoliciesPatch); client.IgnoreNotFound(err) != nil {
		return deploy.Error(err)
	}
	if err := k8s.DeleteObject(ctx, t.Local, cluster); err != nil {
		return deploy.Error(err)
	}
	patches := []k8s.ObjectPatch{
		k8s.RemoveAnnotationPatch(meta.AnnotationKeyExternalCreatePending),
		k8s.SetAnnotationPatch(consts.AnnotationKeyDeletionTrigger, time.Now().Format(time.RFC3339)),
	}
	if err := k8s.PatchObject(ctx, t.Local, cluster, patches...); err != nil {
		return deploy.Error(err)
	}
	return deploy.Done()
}

func (t *EKS) PostDelete(ctx context.Context) deploy.Result {
	key := types.NamespacedName{
		Name: t.K8sName(),
	}
	var c eksv1beta1.Cluster
	return deploy.ErrorOrDone(k8s.GetDeletedObject(ctx, t.Local, key, &c))
}

func (t *EKS) controlPlaneSubnets() ([]string, []xpv1.Reference, error) {
	allSubnets := make([]v1alpha1.Subnet, 0, len(t.VPC.Status.NodeSubnets)+len(t.VPC.Status.ControlPlaneSubnets))
	allSubnets = append(allSubnets, t.VPC.Status.NodeSubnets...)
	allSubnets = append(allSubnets, t.VPC.Status.ControlPlaneSubnets...)

	sort.SliceStable(allSubnets, func(i, j int) bool {
		if allSubnets[i].AvailabilityZone == allSubnets[j].AvailabilityZone {
			return allSubnets[i].Name < allSubnets[j].Name
		}
		return allSubnets[i].AvailabilityZone < allSubnets[j].AvailabilityZone
	})

	seen := make(map[string]struct{}, len(allSubnets))
	subnetRefs := make([]xpv1.Reference, 0, len(allSubnets))
	subnetIDs := make([]string, 0, len(allSubnets))
	for _, subnet := range allSubnets {
		if subnet.Name == "" {
			continue
		}
		if _, ok := seen[subnet.Name]; ok {
			continue
		}
		seen[subnet.Name] = struct{}{}
		subnetRefs = append(subnetRefs, xpv1.Reference{Name: subnet.Name})
		subnetIDs = append(subnetIDs, subnet.SubnetID)
	}
	return subnetIDs, subnetRefs, nil
}

// it is safe to rerun this func again for migrated resource, since it hase compare and update logic.
func (t *EKS) setSubnetTags(ctx context.Context, clusterName string) error {
	setTagF := func(subnets []v1alpha1.Subnet, elbRoleTag string) error {
		clusterTag := eksClusterNameTagKey(clusterName)
		for _, subnet := range subnets {
			sn := &ec2v1beta1.Subnet{
				ObjectMeta: metav1.ObjectMeta{
					Name:      subnet.Name,
					Namespace: t.VPC.Namespace,
				},
			}
			err := t.Local.Get(ctx, client.ObjectKeyFromObject(sn), sn)
			if err != nil {
				return err
			}

			var clusterTagSet, elbRoleTagSet bool
			for tag, _ := range sn.Spec.ForProvider.Tags {
				// cluster tag already set
				if !clusterTagSet && tag == clusterTag {
					clusterTagSet = true
				}
				if !elbRoleTagSet && tag == elbRoleTag {
					elbRoleTagSet = true
				}
			}

			// https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.1/deploy/subnet_discovery/#public-subnets
			if !clusterTagSet {
				sn.Spec.ForProvider.Tags[clusterTag] = ptr.String("owned")
			}
			if !elbRoleTagSet {
				sn.Spec.ForProvider.Tags[elbRoleTag] = ptr.String("1")
			}
			if err := t.Local.Update(ctx, sn); err != nil {
				return err
			}
		}
		return nil
	}

	if err := setTagF(t.VPC.Status.PublicSubnets, subnetTagRolePublicELB); err != nil {
		return err
	}

	if err := setTagF(t.VPC.Status.PrivateSubnets, subnetTagRoleInternalELB); err != nil {
		return err
	}

	return nil
}

func (t *EKS) disablePublicAccessForBYOC(ctx context.Context, cluster *eksv1beta1.Cluster) (updated bool, err error) {
	if !t.Config.APPBoxConfig.EnableK8sPublicAccess || !t.IsBYOC() ||
		!t.APPBox.GetDeletionTimestamp().IsZero() {
		// NOTE: when deleting, we should reenable the public access
		// so that we can still delete resources in the cluster
		// even when the PrivateLink or the ingress is deleted.
		return false, nil
	}
	// in BYOC, if the PrivateLink and Ingress to the EKS API server is enabled,
	// we should set the EndpointPublicAccess to false.
	var existing eksv1beta1.Cluster
	if err := t.Local.Get(ctx, types.NamespacedName{Name: cluster.Name}, &existing); client.IgnoreNotFound(err) != nil {
		return false, fmt.Errorf("failed to get eks cluster %s: %w", cluster.Name, err)
	}
	eksEndpoint := util.GetAnnotation(&existing, consts.AnnotationKeyEKSEndpoint)
	if eksEndpoint != "" {
		if existing.Spec.ForProvider.VPCConfig[0].EndpointPublicAccess == nil ||
			*existing.Spec.ForProvider.VPCConfig[0].EndpointPublicAccess {
			updated = true
		}

		cluster.Spec.ForProvider.VPCConfig[0].EndpointPublicAccess = pointer.Bool(false)
		cluster.Annotations = map[string]string{
			consts.AnnotationKeyEKSEndpoint: eksEndpoint, // still keep it
		}
	}
	return updated, nil
}

func eksClusterNameTagKey(clusterName string) string {
	return fmt.Sprintf("kubernetes.io/cluster/%s", clusterName)
}

// CheckEKSVersionReady checks if the EKS cluster version is ready for the target version.
// It returns an error if:
// 1. The EKS cluster is currently upgrading (Spec.Version != Status.Version)
// 2. The EKS cluster's current version doesn't match the target version (not started upgrading yet)
// NOTE: if the EKS cluster is creating (without Status.Version), it is considered ready for the target version.
func CheckEKSVersionReady(ctx context.Context, cli client.Client, clusterName string, targetVersion string) error {
	var eksCluster eksv1beta1.Cluster
	err := cli.Get(ctx, types.NamespacedName{Name: clusterName}, &eksCluster)
	if err != nil {
		return fmt.Errorf("failed to get EKS cluster: %w", err)
	}

	if eksCluster.Spec.ForProvider.Version == nil || eksCluster.Status.AtProvider.Version == nil ||
		eksCluster.Status.AtProvider.Status == nil || *eksCluster.Status.AtProvider.Status == "CREATING" {
		// the EKS cluster is creating, it is considered ready for the target version.
		// so that we can continue to create the other resources.
		return nil
	}

	// eksCluster.Status.AtProvider.Status: One of CREATING, ACTIVE, DELETING, FAILED
	// NO UPDATING status in the EKS cluster status, so we can only check the version.

	if *eksCluster.Status.AtProvider.Version != *eksCluster.Spec.ForProvider.Version {
		return fmt.Errorf("EKS cluster is upgrading from version %s to %s, waiting for upgrade to complete",
			*eksCluster.Status.AtProvider.Version, *eksCluster.Spec.ForProvider.Version)
	}

	if *eksCluster.Status.AtProvider.Version != targetVersion {
		return fmt.Errorf("EKS cluster version %s is not ready for target version %s, waiting for EKS upgrade",
			*eksCluster.Status.AtProvider.Version, targetVersion)
	}

	return nil
}
