package selfmanaged

import (
	"context"
	"fmt"

	"github.com/crossplane-contrib/provider-aws/apis/autoscaling/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"sigs.k8s.io/controller-runtime/pkg/client"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/namer"
	"github.com/tidbcloud/infra-provider/pkg/nodegroup"
	"github.com/tidbcloud/infra-provider/pkg/util"
)

// TiDBEKSSelfManagedNGBrief describes a TiDB-related self-managed ASG Crossplane CR during an EKS Kubernetes version upgrade.
type TiDBEKSSelfManagedNGBrief struct {
	Name            string
	K8sVersion      string
	DesiredCapacity *int32
	ExtraGroupName  string
	Component       string
}

// TiDBEKSSelfManagedNGCreatePlan is the ASG (and matching Launch Template) name pair that createNewSelfManagedNodeGroups
// would apply for one old self-managed group, matching upgradeoperation/eks.fromSelfManagedToSelfManaged.
type TiDBEKSSelfManagedNGCreatePlan struct {
	OldName        string
	NewName        string
	ExtraGroupName string
}

// TiDBEKSK8sUpgradeSelfManagedNGPlan is the resolved view of old / new-at-target / to-create self-managed node groups
// for TiDB on EKS, aligned with cluster-controller/controllers/upgradeoperation/eks/upgrader.go.
type TiDBEKSK8sUpgradeSelfManagedNGPlan struct {
	OldSelfManaged                   []TiDBEKSSelfManagedNGBrief
	NewSelfManagedAtTargetK8sVersion []TiDBEKSSelfManagedNGBrief
	NewSelfManagedToCreate           []TiDBEKSSelfManagedNGCreatePlan
}

// ClassifyTiDBSelfManagedNGsForK8sVersionUpgrade splits TiDB self-managed ASGs into "old" and "new" (already at desired K8s)
// the same way as getTiDBClusterSelfManagedNodeGroups in upgradeoperation/eks.
func ClassifyTiDBSelfManagedNGsForK8sVersionUpgrade(ctx context.Context, infraCli client.Client, cluster *cloudv1.Cluster, desiredK8sVersion string) (
	oldNGs []*v1beta1.AutoScalingGroup, newNGs []*v1beta1.AutoScalingGroup, asgExtraGroupMap map[string]string, err error,
) {
	asgExtraGroupMap = make(map[string]string)

	allASGs, err := GetTiDBClusterSelfManagedNodeGroups(ctx, infraCli, client.MatchingLabelsSelector{
		Selector: labels.Set{namer.ClusterSelectorKey: cluster.Name}.AsSelector(),
	}, cluster)
	if err != nil {
		return nil, nil, nil, fmt.Errorf("failed to get all self-managed nodegroups: %w", err)
	}

	for _, extraGroup := range cluster.GetExtraGroups() {
		extraGroupNamer := namer.NewNamer().
			ForTiDBGroup(cluster, extraGroup.Spec, "").
			WithComponent(string(cloudv1.ComponentTiDB))

		extraGroupASGs, err := GetTiDBClusterSelfManagedNodeGroups(ctx, infraCli, client.MatchingLabelsSelector{
			Selector: labels.Set{namer.ClusterSelectorKey: extraGroupNamer.GetClusterName()}.AsSelector(),
		}, cluster)
		if err != nil {
			return nil, nil, nil, fmt.Errorf("failed to get self-managed nodegroups for extra group %s: %w", extraGroup.Name, err)
		}

		for _, asg := range extraGroupASGs {
			asgExtraGroupMap[asg.Name] = extraGroup.Name
		}
		allASGs = append(allASGs, extraGroupASGs...)
	}

	for _, asg := range allASGs {
		if util.GetAnnotation(asg, K8sVersionAnnotationKey) == desiredK8sVersion {
			newNGs = append(newNGs, asg)
		} else if cluster.Status.Phase == cloudv1.ClusterPhaseSuspend || (asg.Spec.ForProvider.DesiredCapacity != nil && *asg.Spec.ForProvider.DesiredCapacity > 0) {
			oldNGs = append(oldNGs, asg)
		}
	}

	return oldNGs, newNGs, asgExtraGroupMap, nil
}

// LegacyNetworkNewTiDBSelfManagedASGName returns the Crossplane ASG name for a new-version self-managed group on legacy (2-layer) networks.
// It matches getNewNodegroupCRNameForLegacy in upgradeoperation/eks/utils.go.
func LegacyNetworkNewTiDBSelfManagedASGName(cluster *cloudv1.Cluster, oldNGLabels map[string]string, instanceType, region, version, extraGroupName string) string {
	ownerName := cluster.Name
	if extraGroupName != "" {
		ownerName = fmt.Sprintf("%s-%s", cluster.Name, extraGroupName)
	}

	component := oldNGLabels[namer.ComponentSelectorKey]
	zone := oldNGLabels[consts.NodeGroupZonesLabelKey]
	namespace := oldNGLabels[namer.LegacyNetworkNamespaceSelectorKey]

	ngName := util.FnvHash(namer.NodeGroupName(ownerName, component, zone))
	instanceTypeHash := util.FnvHash(instanceType)
	ngName = "tidbcloud-" + ngName + "-" + instanceTypeHash

	identifier := namer.WithPrefix(ngName + nodegroup.NormalizeVersion(version))
	objMeta := metav1.ObjectMeta{
		Name:      "eks-" + region,
		Namespace: namespace,
	}

	return namer.UniqueIdentifier(objMeta, identifier)
}

// NewVersionTiDBSelfManagedASGName returns the Crossplane ASG name that fromSelfManagedToSelfManaged would use for new LT/ASG CRs.
func NewVersionTiDBSelfManagedASGName(cluster *cloudv1.Cluster, oldNG *v1beta1.AutoScalingGroup, instanceType, desiredK8sVersion, region, extraGroupName string) string {
	if cluster.Spec.NetworkName != "" {
		return LegacyNetworkNewTiDBSelfManagedASGName(cluster, oldNG.GetLabels(), instanceType, region, desiredK8sVersion, extraGroupName)
	}
	component := oldNG.Labels[namer.ComponentSelectorKey]
	zone := oldNG.Labels[consts.NodeGroupZonesLabelKey]
	return nodegroup.GetTiDBClusterNodeGroupNameIn3Layer(cluster, instanceType, component, desiredK8sVersion, []string{zone}, extraGroupName)
}

func asgBrief(asg *v1beta1.AutoScalingGroup, extraGroup string) TiDBEKSSelfManagedNGBrief {
	var dc *int32
	if asg.Spec.ForProvider.DesiredCapacity != nil {
		v := int32(*asg.Spec.ForProvider.DesiredCapacity)
		dc = &v
	}
	return TiDBEKSSelfManagedNGBrief{
		Name:            asg.Name,
		K8sVersion:      util.GetAnnotation(asg, K8sVersionAnnotationKey),
		DesiredCapacity: dc,
		ExtraGroupName:  extraGroup,
		Component:       asg.Labels[namer.ComponentSelectorKey],
	}
}

// ComputeTiDBEKSK8sUpgradeSelfManagedNGPlan lists old self-managed TiDB ASGs, those already at the target K8s version,
// and the new ASG names that createNewSelfManagedNodeGroups would create (DesiredCapacity > 0 only), using Launch Template
// instance types like fromSelfManagedToSelfManaged.
func ComputeTiDBEKSK8sUpgradeSelfManagedNGPlan(ctx context.Context, infraCli client.Client, cluster *cloudv1.Cluster, desiredK8sVersion string) (*TiDBEKSK8sUpgradeSelfManagedNGPlan, error) {
	region := cluster.Labels[consts.RegionLabel]
	if region == "" {
		return nil, fmt.Errorf("cluster %s has empty region label %q", cluster.Name, consts.RegionLabel)
	}

	oldNGs, newNGs, asgExtraGroupMap, err := ClassifyTiDBSelfManagedNGsForK8sVersionUpgrade(ctx, infraCli, cluster, desiredK8sVersion)
	if err != nil {
		return nil, err
	}

	plan := &TiDBEKSK8sUpgradeSelfManagedNGPlan{}
	for _, asg := range oldNGs {
		plan.OldSelfManaged = append(plan.OldSelfManaged, asgBrief(asg, asgExtraGroupMap[asg.Name]))
	}
	for _, asg := range newNGs {
		plan.NewSelfManagedAtTargetK8sVersion = append(plan.NewSelfManagedAtTargetK8sVersion, asgBrief(asg, asgExtraGroupMap[asg.Name]))
	}

	for _, ng := range oldNGs {
		if ng.Spec.ForProvider.DesiredCapacity == nil || *ng.Spec.ForProvider.DesiredCapacity <= 0 {
			continue
		}
		instanceType, err := GetInstanceTypeFromSelfManagedNodeGroup(ng)
		if err != nil {
			return nil, fmt.Errorf("failed to get instance type for self-managed node group %s: %w", ng.Name, err)
		} else if instanceType == "" {
			return nil, fmt.Errorf("instance type not found for self-managed node group %s", ng.Name)
		}
		extra := asgExtraGroupMap[ng.Name]
		newName := NewVersionTiDBSelfManagedASGName(cluster, ng, instanceType, desiredK8sVersion, region, extra)
		plan.NewSelfManagedToCreate = append(plan.NewSelfManagedToCreate, TiDBEKSSelfManagedNGCreatePlan{
			OldName:        ng.Name,
			NewName:        newName,
			ExtraGroupName: extra,
		})
	}

	return plan, nil
}

func GetInstanceTypeFromSelfManagedNodeGroup(asg *v1beta1.AutoScalingGroup) (string, error) {
	if asg == nil {
		return "", fmt.Errorf("the self-managed node group is nil")
	}
	if len(asg.Status.AtProvider.Instances) == 0 {
		return "", fmt.Errorf("the self-managed node group %s has no instances", asg.Name)
	} else if asg.Status.AtProvider.Instances[0].InstanceType == nil {
		return "", fmt.Errorf("the instance type of the first instance in self-managed node group %s is nil", asg.Name)
	}

	return *asg.Status.AtProvider.Instances[0].InstanceType, nil
}
