package selfmanaged

import (
	"github.com/crossplane-contrib/provider-aws/apis/autoscaling/v1beta1"
	ec2v1alpha1 "github.com/crossplane-contrib/provider-aws/apis/ec2/v1alpha1"
	"github.com/crossplane-contrib/provider-aws/apis/eks/manualv1alpha1"
	eksv1beta1 "github.com/crossplane-contrib/provider-aws/apis/eks/v1beta1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/pointer"

	"github.com/tidbcloud/infra-provider/pkg/nodegroup"
	"github.com/tidbcloud/infra-provider/pkg/util"
)

// crossplaneTaintsToCoreTaints converts a slice of crossplane's taint objects to a slice of corev1.Taint objects.
func crossplaneTaintsToCoreTaints(taints []manualv1alpha1.Taint) []corev1.Taint {
	var coreTaints []corev1.Taint

	for _, taint := range taints {
		var effect corev1.TaintEffect
		switch taint.Effect {
		case "NO_SCHEDULE":
			effect = corev1.TaintEffectNoSchedule
		case "NO_EXECUTE":
			effect = corev1.TaintEffectNoExecute
		case "PREFER_NO_SCHEDULE":
			effect = corev1.TaintEffectPreferNoSchedule
		}

		if taint.Key != nil && *taint.Key != "" {
			coreTaints = append(coreTaints, corev1.Taint{
				Key:    *taint.Key,
				Value:  *taint.Value,
				Effect: effect,
			})
		}
	}
	return coreTaints
}

// FromManagedToSelfManaged returns a launch template CR and an autoscaling group CR
// based on the given managed nodegroup CR and its launch template.
//
// Args:
// oldLT: The managed nodegroup's launch template CR.
// oldNG: The managed nodegroup CR.
// eks: The eksv1beta1.Cluster object associated with the nodegroup.
// asgName: The name of the AutoScalingGroup to be created.
// amiID: The ID of the Amazon Machine Image (AMI) to be used.
func FromManagedToSelfManaged(
	oldLT *ec2v1alpha1.LaunchTemplate,
	oldNG *manualv1alpha1.NodeGroup,
	eks *eksv1beta1.Cluster,
	asgName, amiID, desiredVersion, systemConfiguration string,
) (lt *ec2v1alpha1.LaunchTemplate, asg *v1beta1.AutoScalingGroup) {
	crLabels := util.AddResourceName(util.DeepCopyMap(oldNG.Labels), asgName)
	region := oldLT.Spec.ForProvider.Region
	providerConfigName := oldLT.Spec.ProviderConfigReference.Name
	instanceType := oldNG.Spec.ForProvider.InstanceTypes[0]
	eksName := oldNG.Spec.ForProvider.ClusterName
	endpoint := eks.Status.AtProvider.Endpoint
	ca := eks.Status.AtProvider.CertificateAuthorityData
	userData := GetBottlerocketUserData(getEKSNameFromARN(eks.Status.AtProvider.Arn), endpoint, ca, crLabels, crossplaneTaintsToCoreTaints(oldNG.Spec.ForProvider.Taints), systemConfiguration)

	var sgIDMap = make(map[string]struct{})
	// Add tidb cluster dedicated security group, if any
	for _, sg := range oldLT.Spec.ForProvider.LaunchTemplateData.SecurityGroupIDs {
		if sg != nil {
			sgIDMap[*sg] = struct{}{}
		}
	}
	// Add eks cluster security group
	if id := eks.Status.AtProvider.ResourcesVpcConfig.ClusterSecurityGroupID; id != "" {
		sgIDMap[id] = struct{}{}
	}
	var sgIDs []*string
	for id := range sgIDMap {
		sgIDs = append(sgIDs, pointer.String(id))
	}

	lt = nodegroup.GetBaseLaunchTemplate(asgName, region, providerConfigName, crLabels, crLabels, oldLT.Spec.ForProvider.LaunchTemplateData.BlockDeviceMappings)
	MutateLaunchTemplate(lt, eksName, amiID, instanceType, userData, sgIDs)

	scalingCfg := oldNG.Spec.ForProvider.ScalingConfig
	asg = GetAutoScalingGroup(asgName, region, providerConfigName, desiredVersion, crLabels, getASGTags(eksName, asgName, false), oldNG.Spec.ForProvider.Subnets, *scalingCfg.MinSize, *scalingCfg.MaxSize)

	return lt, asg
}

// FromSelfManagedToSelfManaged returns a new launch template CR and a new autoscaling group CR based on the given old launch template CR and autoscaling group CR.
func FromSelfManagedToSelfManaged(
	oldLT *ec2v1alpha1.LaunchTemplate,
	oldNG *v1beta1.AutoScalingGroup,
	eksName, asgName, amiID, desiredVersion string,
) (lt *ec2v1alpha1.LaunchTemplate, asg *v1beta1.AutoScalingGroup) {
	crLabels := util.AddResourceName(util.DeepCopyMap(oldNG.Labels), asgName)

	objMeta := metav1.ObjectMeta{
		Name:   asgName,
		Labels: crLabels,
	}

	lt = &ec2v1alpha1.LaunchTemplate{
		ObjectMeta: objMeta,
		Spec:       oldLT.DeepCopy().Spec,
	}
	// For the new launch template, we only need to update the image ID, user data, and tag specifications.
	lt.Spec.ForProvider.LaunchTemplateName = pointer.String(asgName)
	lt.Spec.ForProvider.LaunchTemplateData.ImageID = pointer.String(amiID)
	lt.Spec.ForProvider.LaunchTemplateData.TagSpecifications = nodegroup.GetLaunchTemplateTagSpecifications(crLabels)
	oldUserdata := lt.Spec.ForProvider.LaunchTemplateData.UserData
	if oldUserdata != nil {
		lt.Spec.ForProvider.LaunchTemplateData.UserData = OverwriteNodeLabels(*oldUserdata, crLabels)
	}

	// For the new asg, we only need to update the launch template name, tags and the annotation.
	asg = &v1beta1.AutoScalingGroup{
		ObjectMeta: objMeta,
		Spec:       oldNG.DeepCopy().Spec,
	}
	asg.Spec.ForProvider.LaunchTemplate = &v1beta1.LaunchTemplateSpecification{
		LaunchTemplateName: pointer.String(asgName),
	}
	asg.Spec.ForProvider.Tags = getASGTags(eksName, asgName, false)
	util.SetAnnotation(asg, K8sVersionAnnotationKey, desiredVersion)

	return lt, asg
}
