/*
Copyright 2024.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package providers

import (
	"context"
	"errors"
	"fmt"
	"maps"
	"strings"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/ec2"
	"github.com/aws/aws-sdk-go-v2/service/eks"
	ekstypes "github.com/aws/aws-sdk-go-v2/service/eks/types"

	o11yv1 "github.com/tidbcloud/observability/api/regionalserver/resourceworker/v1"
)

// EKSCloudProvider implements CloudProviderInterface for AWS EKS
type EKSCloudProvider struct {
	eksClient *eks.Client
	ec2Client *ec2.Client
	region    string
}

// NewEKSCloudProvider creates a new EKS cloud provider instance
func NewEKSCloudProvider(region string) (*EKSCloudProvider, error) {
	cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(region))
	if err != nil {
		return nil, fmt.Errorf("failed to load AWS config: %w", err)
	}

	return &EKSCloudProvider{
		eksClient: eks.NewFromConfig(cfg),
		ec2Client: ec2.NewFromConfig(cfg),
		region:    region,
	}, nil
}

func (e *EKSCloudProvider) DescribeCluster(ctx context.Context, clusterName string) (any, error) {
	clusterOutput, err := e.eksClient.DescribeCluster(ctx, &eks.DescribeClusterInput{
		Name: aws.String(clusterName),
	})
	if err != nil {
		return nil, fmt.Errorf("failed to describe cluster: %w", err)
	}
	return clusterOutput.Cluster, nil
}

// UpgradeControlPlane upgrades the EKS control plane to the target version
func (e *EKSCloudProvider) UpgradeControlPlane(ctx context.Context, clusterName, targetVersion string) error {
	_, err := e.eksClient.UpdateClusterVersion(ctx, &eks.UpdateClusterVersionInput{
		Name:    aws.String(clusterName),
		Version: aws.String(targetVersion),
	})
	if err != nil {
		return fmt.Errorf("failed to upgrade EKS control plane: %w", err)
	}

	return nil
}

// UpgradeClusterAddons triggers EKS addon updates (UpdateAddon is async; controller polls via GetClusterAddonsStatus + RequeueAfter).
func (e *EKSCloudProvider) UpgradeClusterAddons(ctx context.Context, clusterName string, addons []o11yv1.AddonUpgrade) error {
	for _, addon := range addons {
		input := &eks.UpdateAddonInput{
			ClusterName:  aws.String(clusterName),
			AddonName:    aws.String(addon.Name),
			AddonVersion: aws.String(addon.Version),
		}

		// Set ConfigurationValues if provided
		if addon.ConfigurationValues != "" {
			input.ConfigurationValues = aws.String(addon.ConfigurationValues)
		}

		// Set ResolveConflicts if provided
		if addon.ResolveConflicts != "" {
			resolveConflicts, err := normalizeEKSResolveConflicts(addon.ResolveConflicts)
			if err != nil {
				return err
			}
			input.ResolveConflicts = resolveConflicts
		}

		// Set ServiceAccountRoleArn if provided
		if addon.ServiceAccountRoleArn != "" {
			input.ServiceAccountRoleArn = aws.String(addon.ServiceAccountRoleArn)
		}

		_, err := e.eksClient.UpdateAddon(ctx, input)
		if err != nil {
			return fmt.Errorf("failed to upgrade addon %s: %w", addon.Name, err)
		}
	}
	return nil
}

func normalizeEKSResolveConflicts(value string) (ekstypes.ResolveConflicts, error) {
	switch value {
	case string(ekstypes.ResolveConflictsNone):
		return ekstypes.ResolveConflictsNone, nil
	case string(ekstypes.ResolveConflictsOverwrite):
		return ekstypes.ResolveConflictsOverwrite, nil
	case string(ekstypes.ResolveConflictsPreserve):
		return ekstypes.ResolveConflictsPreserve, nil
	default:
		return "", fmt.Errorf("invalid EKS resolveConflicts %q", value)
	}
}

// ListClusterAddons returns all installed addons in the EKS cluster.
func (e *EKSCloudProvider) ListClusterAddons(ctx context.Context, clusterName string) ([]AddonStatusInfo, error) {
	paginator := eks.NewListAddonsPaginator(e.eksClient, &eks.ListAddonsInput{
		ClusterName: aws.String(clusterName),
	})
	var addonNames []string
	for paginator.HasMorePages() {
		out, err := paginator.NextPage(ctx)
		if err != nil {
			return nil, fmt.Errorf("list EKS addons: %w", err)
		}
		if out.Addons != nil {
			addonNames = append(addonNames, out.Addons...)
		}
	}
	if len(addonNames) == 0 {
		return nil, nil
	}
	result := make([]AddonStatusInfo, 0, len(addonNames))
	for _, name := range addonNames {
		desc, err := e.eksClient.DescribeAddon(ctx, &eks.DescribeAddonInput{
			ClusterName: aws.String(clusterName),
			AddonName:   aws.String(name),
		})
		if err != nil {
			return nil, fmt.Errorf("describe addon %s: %w", name, err)
		}
		version := ""
		status := ""
		if desc.Addon != nil {
			if desc.Addon.AddonVersion != nil {
				version = *desc.Addon.AddonVersion
			}
			status = string(desc.Addon.Status)
		}
		result = append(result, AddonStatusInfo{Name: name, Version: version, Status: status})
	}
	return result, nil
}

// GetClusterAddonsStatus returns current status and version for each addon (DescribeAddon).
func (e *EKSCloudProvider) GetClusterAddonsStatus(ctx context.Context, clusterName string, addons []o11yv1.AddonUpgrade) ([]AddonStatusInfo, error) {
	result := make([]AddonStatusInfo, 0, len(addons))
	for _, addon := range addons {
		out, err := e.eksClient.DescribeAddon(ctx, &eks.DescribeAddonInput{
			ClusterName: aws.String(clusterName),
			AddonName:   aws.String(addon.Name),
		})
		if err != nil {
			return nil, fmt.Errorf("describe addon %s: %w", addon.Name, err)
		}
		if out.Addon == nil {
			result = append(result, AddonStatusInfo{Name: addon.Name, Version: "", Status: ""})
			continue
		}
		version := ""
		if out.Addon.AddonVersion != nil {
			version = *out.Addon.AddonVersion
		}
		result = append(result, AddonStatusInfo{
			Name:    addon.Name,
			Version: version,
			Status:  string(out.Addon.Status),
		})
	}
	return result, nil
}

func (e *EKSCloudProvider) GetNodeGroupByName(ctx context.Context, clusterName string, nodeGroupName string) (*NodeGroup, error) {
	ngOutput, err := e.eksClient.DescribeNodegroup(ctx, &eks.DescribeNodegroupInput{
		ClusterName:   aws.String(clusterName),
		NodegroupName: aws.String(nodeGroupName),
	})
	if err != nil {
		var notFound *ekstypes.ResourceNotFoundException
		if errors.As(err, &notFound) {
			return nil, ErrNodeGroupNotFound
		}
		return nil, fmt.Errorf("failed to describe nodegroup %s: %w", nodeGroupName, err)
	}
	nodeGroup := e.convertEKSNodeGroupToNodeGroup(ngOutput.Nodegroup)
	return &nodeGroup, nil
}

// GetNodeGroupsByLabels retrieves EKS nodegroups matching the given label selector
func (e *EKSCloudProvider) GetNodeGroupsByLabels(ctx context.Context, clusterName string, labelSelector map[string]string) ([]NodeGroup, error) {
	// List all nodegroups for the cluster
	paginator := eks.NewListNodegroupsPaginator(e.eksClient, &eks.ListNodegroupsInput{
		ClusterName: aws.String(clusterName),
	})

	var allNodeGroupNames []string
	for paginator.HasMorePages() {
		output, err := paginator.NextPage(ctx)
		if err != nil {
			return nil, fmt.Errorf("failed to list nodegroups: %w", err)
		}
		allNodeGroupNames = append(allNodeGroupNames, output.Nodegroups...)
	}

	// Filter nodegroups by labels/tags
	var matchingNodeGroups []NodeGroup
	for _, ngName := range allNodeGroupNames {
		// Describe the nodegroup to get its details
		ngOutput, err := e.eksClient.DescribeNodegroup(ctx, &eks.DescribeNodegroupInput{
			ClusterName:   aws.String(clusterName),
			NodegroupName: aws.String(ngName),
		})
		if err != nil {
			var notFound *ekstypes.ResourceNotFoundException
			if errors.As(err, &notFound) {
				continue
			}
			return nil, fmt.Errorf("failed to describe nodegroup %s: %w", ngName, err)
		}

		ng := ngOutput.Nodegroup
		// Check if nodegroup matches label selector
		// In EKS, labels are stored in Tags
		matches := true
		for key, value := range labelSelector {
			found := false
			for tagKey, tagValue := range ng.Tags {
				if tagKey == key && tagValue == value {
					found = true
					break
				}
			}
			if !found {
				matches = false
				break
			}
		}

		if matches {
			nodeGroup := e.convertEKSNodeGroupToNodeGroup(ng)
			matchingNodeGroups = append(matchingNodeGroups, nodeGroup)
		}
	}

	return matchingNodeGroups, nil
}

// AWS reserves the "aws:" prefix (case insensitive) and rejects it in
// CreateNodegroup with BadRequestException: Invalid tag key. Old NodeGroup
// tags are copied verbatim and include CloudFormation-injected aws:cloudformation:*
// entries, which must be dropped here.
func (e *EKSCloudProvider) copyTags(oriTags map[string]string) map[string]string {
	tags := make(map[string]string)
	for k, v := range oriTags {
		if strings.HasPrefix(strings.ToLower(k), "aws:") {
			continue
		}
		tags[k] = v
	}
	return tags
}

// CreateNodeGroup creates a new EKS nodegroup, copying subnets, taints, and other config from spec.
func (e *EKSCloudProvider) CreateNodeGroup(ctx context.Context, clusterName string, spec NodeGroup) (string, error) {
	clusterOutput, err := e.eksClient.DescribeCluster(ctx, &eks.DescribeClusterInput{
		Name: aws.String(clusterName),
	})
	if err != nil {
		return "", fmt.Errorf("failed to describe cluster: %w", err)
	}

	cluster := clusterOutput.Cluster
	subnets := spec.Subnets
	if len(subnets) == 0 {
		if len(cluster.ResourcesVpcConfig.SubnetIds) == 0 {
			return "", fmt.Errorf("cluster has no subnet IDs and spec has no subnets")
		}
		subnets = cluster.ResourcesVpcConfig.SubnetIds
	}

	labels := make(map[string]string)
	maps.Copy(labels, spec.Labels)

	tags := e.copyTags(spec.Tags)

	scalingConfig := &ekstypes.NodegroupScalingConfig{}
	if spec.MinSize >= 0 {
		scalingConfig.MinSize = aws.Int32(int32(spec.MinSize))
	}
	if spec.MaxSize >= 0 {
		scalingConfig.MaxSize = aws.Int32(int32(spec.MaxSize))
	}
	if spec.DesiredSize >= 0 {
		scalingConfig.DesiredSize = aws.Int32(int32(spec.DesiredSize))
	}

	createInput := &eks.CreateNodegroupInput{
		ClusterName:   aws.String(clusterName),
		NodegroupName: aws.String(spec.Name),
		NodeRole:      aws.String(spec.NodeRole),
		Subnets:       subnets,
		Tags:          tags,
		Labels:        labels,
		ScalingConfig: scalingConfig,
	}

	hasLaunchTemplate := spec.LaunchTemplateId != "" || spec.LaunchTemplateName != ""
	if hasLaunchTemplate {
		createInput.LaunchTemplate = &ekstypes.LaunchTemplateSpecification{}
		if spec.LaunchTemplateId != "" {
			createInput.LaunchTemplate.Id = aws.String(spec.LaunchTemplateId)
		} else {
			createInput.LaunchTemplate.Name = aws.String(spec.LaunchTemplateName)
		}
		if spec.LaunchTemplateVersion != "" {
			createInput.LaunchTemplate.Version = aws.String(spec.LaunchTemplateVersion)
		}
		usesCustomAMI, err := e.launchTemplateUsesCustomAMI(ctx, createInput.LaunchTemplate)
		if err != nil {
			return "", err
		}
		if usesCustomAMI {
			return "", fmt.Errorf("%w: %s", ErrNodeGroupLaunchTemplateCustomAMI, spec.Name)
		}
	}

	if len(spec.InstanceType) != 0 {
		createInput.InstanceTypes = spec.InstanceType
	}

	if spec.Version != "" {
		createInput.Version = aws.String(spec.Version)
	}

	if len(spec.Taints) > 0 {
		createInput.Taints = make([]ekstypes.Taint, 0, len(spec.Taints))
		for _, t := range spec.Taints {
			createInput.Taints = append(createInput.Taints, ekstypes.Taint{
				Key:    aws.String(t.Key),
				Value:  aws.String(t.Value),
				Effect: ekstypes.TaintEffect(t.Effect),
			})
		}
	}

	if spec.AmiType != "" {
		createInput.AmiType = ekstypes.AMITypes(spec.AmiType)
	} else if !hasLaunchTemplate {
		createInput.AmiType = ekstypes.AMITypesBottlerocketX8664
	}

	if spec.CapacityType != "" {
		createInput.CapacityType = ekstypes.CapacityTypes(spec.CapacityType)
	}

	if spec.DiskSize > 0 {
		createInput.DiskSize = aws.Int32(int32(spec.DiskSize))
	}

	output, err := e.eksClient.CreateNodegroup(ctx, createInput)
	if err != nil {
		return "", fmt.Errorf("failed to create nodegroup: %w, %v", err, createInput)
	}

	if output.Nodegroup == nil || output.Nodegroup.NodegroupName == nil {
		return "", fmt.Errorf("nodegroup created but name is nil")
	}

	return *output.Nodegroup.NodegroupName, nil
}

func (e *EKSCloudProvider) UpdateNodeGroupMinSize(ctx context.Context, clusterName, nodeGroupID string, minSize int) error {
	_, err := e.eksClient.UpdateNodegroupConfig(ctx, &eks.UpdateNodegroupConfigInput{
		ClusterName:   aws.String(clusterName),
		NodegroupName: aws.String(nodeGroupID),
		ScalingConfig: &ekstypes.NodegroupScalingConfig{
			MinSize: aws.Int32(int32(minSize)),
		},
	})
	if err != nil {
		return fmt.Errorf("failed to update nodegroup min size: %w", err)
	}
	return nil
}

// UpgradeNodeGroup upgrades an existing EKS nodegroup.
// When the node group's launch template specifies a custom AMI, pass a new LaunchTemplate version instead of Version.
func (e *EKSCloudProvider) UpgradeNodeGroup(ctx context.Context, clusterName, nodeGroupID, targetVersion string, inPlaceOpts *o11yv1.NodeGroupUpgradeOptions) (string, error) {
	describeOut, err := e.eksClient.DescribeNodegroup(ctx, &eks.DescribeNodegroupInput{
		ClusterName:   aws.String(clusterName),
		NodegroupName: aws.String(nodeGroupID),
	})
	if err != nil {
		return "", fmt.Errorf("describe nodegroup before upgrade: %w", err)
	}
	ng := describeOut.Nodegroup

	input := &eks.UpdateNodegroupVersionInput{
		ClusterName:   aws.String(clusterName),
		NodegroupName: aws.String(nodeGroupID),
	}

	if inPlaceOpts != nil && inPlaceOpts.LaunchTemplateVersion != "" {
		lt := &ekstypes.LaunchTemplateSpecification{
			Version: aws.String(inPlaceOpts.LaunchTemplateVersion),
		}
		if inPlaceOpts.LaunchTemplateId != "" {
			lt.Id = aws.String(inPlaceOpts.LaunchTemplateId)
		} else if inPlaceOpts.LaunchTemplateName != "" {
			lt.Name = aws.String(inPlaceOpts.LaunchTemplateName)
		} else if ng.LaunchTemplate != nil && ng.LaunchTemplate.Id != nil {
			lt.Id = ng.LaunchTemplate.Id
		} else if ng.LaunchTemplate != nil && ng.LaunchTemplate.Name != nil {
			lt.Name = ng.LaunchTemplate.Name
		}
		if lt.Id == nil && lt.Name == nil {
			return "", fmt.Errorf("node group launch template has no id/name; set launchTemplateId or launchTemplateName in step options")
		}
		input.LaunchTemplate = lt
	} else {
		if ng.LaunchTemplate != nil {
			hasCustomAMI, err := e.launchTemplateUsesCustomAMI(ctx, ng.LaunchTemplate)
			if err != nil {
				return "", err
			}
			if hasCustomAMI {
				return "", fmt.Errorf("node group launch template specifies a custom AMI; set launchTemplateVersion in step options for in-place upgrade")
			}
		}
		input.Version = aws.String(targetVersion)
	}

	_, err = e.eksClient.UpdateNodegroupVersion(ctx, input)
	if err != nil {
		var inUse *ekstypes.ResourceInUseException
		if errors.As(err, &inUse) {
			return "", nil
		}
		return "", fmt.Errorf("failed to upgrade nodegroup: %w", err)
	}
	return "", nil
}

func (e *EKSCloudProvider) launchTemplateUsesCustomAMI(ctx context.Context, lt *ekstypes.LaunchTemplateSpecification) (bool, error) {
	if lt == nil {
		return false, nil
	}
	input := &ec2.DescribeLaunchTemplateVersionsInput{}
	if lt.Id != nil && *lt.Id != "" {
		input.LaunchTemplateId = lt.Id
	} else if lt.Name != nil && *lt.Name != "" {
		input.LaunchTemplateName = lt.Name
	} else {
		return false, fmt.Errorf("node group launch template has no id/name")
	}
	if lt.Version != nil && *lt.Version != "" {
		input.Versions = []string{*lt.Version}
	} else {
		input.Versions = []string{"$Default"}
	}

	out, err := e.ec2Client.DescribeLaunchTemplateVersions(ctx, input)
	if err != nil {
		return false, fmt.Errorf("failed to describe launch template version: %w", err)
	}
	if out == nil || len(out.LaunchTemplateVersions) == 0 {
		return false, fmt.Errorf("launch template version not found")
	}
	data := out.LaunchTemplateVersions[0].LaunchTemplateData
	return data.ImageId != nil && *data.ImageId != "", nil
}

func (_ *EKSCloudProvider) GetNodeGroupUpgradeOperationStatus(_ context.Context, _, _, _ string) (bool, error) {
	return true, nil
}

// DeleteNodeGroup deletes an EKS nodegroup
func (e *EKSCloudProvider) DeleteNodeGroup(ctx context.Context, clusterName, nodeGroupID string) error {
	_, err := e.eksClient.DeleteNodegroup(ctx, &eks.DeleteNodegroupInput{
		ClusterName:   aws.String(clusterName),
		NodegroupName: aws.String(nodeGroupID),
	})
	if err != nil {
		return fmt.Errorf("failed to delete nodegroup: %w", err)
	}

	return nil
}

// GetNodeGroupStatus retrieves the status of an EKS nodegroup
func (e *EKSCloudProvider) GetNodeGroupStatus(ctx context.Context, clusterName, nodeGroupID string) (NodeGroupStatus, error) {
	output, err := e.eksClient.DescribeNodegroup(ctx, &eks.DescribeNodegroupInput{
		ClusterName:   aws.String(clusterName),
		NodegroupName: aws.String(nodeGroupID),
	})
	if err != nil {
		var notFound *ekstypes.ResourceNotFoundException
		if errors.As(err, &notFound) {
			return "", ErrNodeGroupNotFound
		}
		return "", fmt.Errorf("failed to describe nodegroup: %w", err)
	}

	ng := output.Nodegroup
	status := e.convertEKSStatusToNodeGroupStatus(ng.Status)
	return status, nil
}

// GetNodeGroupLabelSelector returns the label selector for nodes belonging to an EKS nodegroup
func (e *EKSCloudProvider) GetNodeGroupLabelSelector(ctx context.Context, clusterName, nodeGroupID string) (map[string]string, error) {
	// EKS nodes are labeled with eks.amazonaws.com/nodegroup
	return map[string]string{
		"eks.amazonaws.com/nodegroup": nodeGroupID,
	}, nil
}

func (e *EKSCloudProvider) GetInstanceTypeByTemplateVersion(ctx context.Context, clusterName, launchTemplateId, launchTemplateVersion string) (string, error) {
	if launchTemplateId == "" || launchTemplateVersion == "" {
		return "", fmt.Errorf("launchTemplateId and launchTemplateVersion are required")
	}
	out, err := e.ec2Client.DescribeLaunchTemplateVersions(ctx, &ec2.DescribeLaunchTemplateVersionsInput{
		LaunchTemplateId: aws.String(launchTemplateId),
		Versions:         []string{launchTemplateVersion},
	})
	if err != nil {
		return "", fmt.Errorf("failed to describe launch template version %s@%s: %w", launchTemplateId, launchTemplateVersion, err)
	}
	if out == nil || len(out.LaunchTemplateVersions) == 0 || out.LaunchTemplateVersions[0].LaunchTemplateData == nil {
		return "", fmt.Errorf("launch template version not found: %s@%s", launchTemplateId, launchTemplateVersion)
	}
	return string(out.LaunchTemplateVersions[0].LaunchTemplateData.InstanceType), nil
}

// GetControlPlaneVersion returns the current K8s version of the EKS control plane
func (e *EKSCloudProvider) GetControlPlaneVersion(ctx context.Context, clusterName string) (string, error) {
	output, err := e.eksClient.DescribeCluster(ctx, &eks.DescribeClusterInput{
		Name: aws.String(clusterName),
	})
	if err != nil {
		return "", fmt.Errorf("failed to describe cluster: %w", err)
	}

	if output.Cluster.Version == nil {
		return "", fmt.Errorf("cluster version is nil")
	}

	return *output.Cluster.Version, nil
}

// GetControlPlaneStatus returns the EKS cluster control plane status (ACTIVE or UPDATING).
func (e *EKSCloudProvider) GetControlPlaneStatus(ctx context.Context, clusterName string) (ControlPlaneStatus, error) {
	output, err := e.eksClient.DescribeCluster(ctx, &eks.DescribeClusterInput{
		Name: aws.String(clusterName),
	})
	if err != nil {
		return "", fmt.Errorf("failed to describe cluster: %w", err)
	}
	switch output.Cluster.Status {
	case ekstypes.ClusterStatusUpdating:
		return ControlPlaneStatusUpdating, nil
	case ekstypes.ClusterStatusActive:
		return ControlPlaneStatusActive, nil
	default:
		return ControlPlaneStatusOther, nil
	}
}

// Helper functions

func (e *EKSCloudProvider) convertEKSNodeGroupToNodeGroup(ng *ekstypes.Nodegroup) NodeGroup {
	nodeGroup := NodeGroup{
		ID:      "",
		Name:    "",
		Labels:  make(map[string]string),
		Tags:    make(map[string]string),
		Version: "",
		Status:  "",
	}

	if ng.NodeRole != nil {
		nodeGroup.NodeRole = *ng.NodeRole
	}

	if ng.NodegroupName != nil {
		nodeGroup.ID = *ng.NodegroupName
		nodeGroup.Name = *ng.NodegroupName
	}

	if ng.Version != nil {
		nodeGroup.Version = *ng.Version
	}

	nodeGroup.Status = NodeGroupStatus(ng.Status)

	if ng.Tags != nil {
		maps.Copy(nodeGroup.Tags, ng.Tags)
	}

	if ng.Labels != nil {
		maps.Copy(nodeGroup.Labels, ng.Labels)
	}

	if len(ng.InstanceTypes) > 0 {
		nodeGroup.InstanceType = append([]string(nil), ng.InstanceTypes...)
	}

	if ng.Subnets != nil {
		nodeGroup.Subnets = append([]string(nil), ng.Subnets...)
	}

	if len(ng.Taints) > 0 {
		nodeGroup.Taints = make([]NodeGroupTaint, 0, len(ng.Taints))
		for _, t := range ng.Taints {
			nt := NodeGroupTaint{}
			if t.Key != nil {
				nt.Key = *t.Key
			}
			if t.Value != nil {
				nt.Value = *t.Value
			}
			nt.Effect = string(t.Effect)
			nodeGroup.Taints = append(nodeGroup.Taints, nt)
		}
	}

	nodeGroup.AmiType = string(ng.AmiType)
	nodeGroup.CapacityType = string(ng.CapacityType)
	if ng.DiskSize != nil {
		nodeGroup.DiskSize = int(*ng.DiskSize)
	}

	if ng.ScalingConfig != nil {
		if ng.ScalingConfig.MinSize != nil {
			nodeGroup.MinSize = int(*ng.ScalingConfig.MinSize)
		}
		if ng.ScalingConfig.MaxSize != nil {
			nodeGroup.MaxSize = int(*ng.ScalingConfig.MaxSize)
		}
		if ng.ScalingConfig.DesiredSize != nil {
			nodeGroup.DesiredSize = int(*ng.ScalingConfig.DesiredSize)
		}
	}

	if ng.Resources != nil {
		nodeGroup.NodeCount = nodeGroup.DesiredSize
	}

	if ng.LaunchTemplate != nil {
		if ng.LaunchTemplate.Version != nil {
			nodeGroup.LaunchTemplateVersion = *ng.LaunchTemplate.Version
		}
		if ng.LaunchTemplate.Id != nil {
			nodeGroup.LaunchTemplateId = *ng.LaunchTemplate.Id
		}
		if ng.LaunchTemplate.Name != nil {
			nodeGroup.LaunchTemplateName = *ng.LaunchTemplate.Name
		}
	}

	return nodeGroup
}

func (e *EKSCloudProvider) convertEKSStatusToNodeGroupStatus(status ekstypes.NodegroupStatus) NodeGroupStatus {
	switch status {
	case ekstypes.NodegroupStatusCreating:
		return NodeGroupStatusCreating
	case ekstypes.NodegroupStatusActive:
		return NodeGroupStatusActive
	case ekstypes.NodegroupStatusUpdating:
		return NodeGroupStatusUpdating
	case ekstypes.NodegroupStatusDeleting:
		return NodeGroupStatusDeleting
	case ekstypes.NodegroupStatusCreateFailed:
		return NodeGroupStatusCreateFailed
	case ekstypes.NodegroupStatusDeleteFailed:
		return NodeGroupStatusDeleteFailed
	default:
		// Be defensive against new/unknown statuses to avoid panics in controllers.
		return NodeGroupStatus(status)
	}
}
