package eks

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go-v2/service/ec2"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	ec2v1beta1 "github.com/upbound/provider-aws/apis/ec2/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"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/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/privatelinkendpoint/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"
	"github.com/tidbcloud/observability/agent-control-server/pkg/consts"
)

const (
	securityGroupRuleTypeIngress = "ingress"
	securityGroupRuleTypeEgress  = "egress"

	pleMiniDNSEntriesNum         = 2
	zonalDNSLevelNum             = 6
	partsOfSixLevelOfZonalDNS    = 6
	partsOfSixLevelOfRegionalDNS = 3
)

type endpoint struct {
	*task.BaseTask
	awsCli aws.Client
}

func NewEndpoint(ctx *task.Context, awsCli aws.Client) deploy.Task {
	return &endpoint{
		BaseTask: &task.BaseTask{Context: ctx},
		awsCli:   awsCli,
	}
}

func (t *endpoint) Name() string {
	return "Endpoint"
}

func (t *endpoint) Create(ctx context.Context) deploy.Result {
	tags := t.EffectiveCloudTags(consts.ProviderTypeEKS)

	sg, err := t.createSecurityGroupAndRules(ctx, tags)
	if err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}
	ep, err := t.createVPCEndpoint(ctx, tags)
	if err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}

	labels := util.MergeMaps(t.VPC.Labels, map[string]string{v1alpha1.PrivateLinkEndpointNameKey: t.PrivateLinkEndpoint.Name})
	if err := t.createVPCEndpointAssociations(ctx, ep, sg, labels); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}

	return deploy.Done()
}

func (t *endpoint) createVPCEndpoint(ctx context.Context, tags map[string]string) (*ec2v1beta1.VPCEndpoint, error) {
	name := t.EndpointName()
	tags = util.BuildAWSTagWithName(tags, name)
	labels := util.MergeMaps(t.VPC.Labels, tags, map[string]string{v1alpha1.PrivateLinkEndpointNameKey: t.PrivateLinkEndpoint.Name})
	ep := &ec2v1beta1.VPCEndpoint{
		ObjectMeta: metav1.ObjectMeta{
			Name:   name,
			Labels: labels,
		},
		Spec: ec2v1beta1.VPCEndpointSpec{
			ForProvider: ec2v1beta1.VPCEndpointParameters_2{
				Region:          ptr.To(t.PrivateLinkEndpoint.Spec.Region),
				ServiceRegion:   ptr.To(t.PrivateLinkEndpoint.Spec.ServiceRegion),
				ServiceName:     ptr.To(t.PrivateLinkEndpoint.Spec.ServiceName),
				VPCEndpointType: ptr.To("Interface"),
				VPCIDRef: &xpv1.Reference{
					Name: t.VPC.Status.VPC.Name,
				},
				Tags: util.ToMapPointer(tags),
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.ProviderConfigName(),
				},
				ManagementPolicies: controllers.GetResourceManagementPolicies(),
			},
		},
	}
	if err := k8s.Apply(ctx, t.Local, ep); err != nil {
		return nil, err
	}
	return ep, nil
}

func (t *endpoint) createSecurityGroupAndRules(ctx context.Context, tags map[string]string) (*ec2v1beta1.SecurityGroup, error) {
	sg, err := t.createSecurityGroup(ctx, util.DeepCopyMap(tags))
	if err != nil {
		return nil, err
	}

	labels := util.MergeMaps(t.VPC.Labels, map[string]string{v1alpha1.PrivateLinkEndpointNameKey: t.PrivateLinkEndpoint.Name})
	if err := t.createSecurityGroupRule(ctx, securityGroupRuleTypeIngress, sg, labels); err != nil {
		return nil, fmt.Errorf("create ingress security group rule error: %w", err)
	}
	if err := t.createSecurityGroupRule(ctx, securityGroupRuleTypeEgress, sg, labels); err != nil {
		return nil, fmt.Errorf("create egress security group rule error: %w", err)
	}
	return sg, nil
}

func (t *endpoint) createSecurityGroup(ctx context.Context, tags map[string]string) (*ec2v1beta1.SecurityGroup, error) {
	name := t.EndpointName()
	tags = util.BuildAWSTagWithName(tags, name)
	labels := util.MergeMaps(t.VPC.Labels, tags, map[string]string{v1alpha1.PrivateLinkEndpointNameKey: t.PrivateLinkEndpoint.Name})
	sg := &ec2v1beta1.SecurityGroup{
		ObjectMeta: metav1.ObjectMeta{
			Name:   name,
			Labels: labels,
		},
		Spec: ec2v1beta1.SecurityGroupSpec{
			ForProvider: ec2v1beta1.SecurityGroupParameters_2{
				Region:      ptr.To(t.PrivateLinkEndpoint.Spec.Region),
				Name:        ptr.To(name),
				Description: ptr.To("allow all security group"),
				VPCIDRef: &xpv1.Reference{
					Name: t.VPC.Status.VPC.Name,
				},
				Tags: util.ToMapPointer(tags),
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.ProviderConfigName(),
				},
				ManagementPolicies: controllers.GetResourceManagementPolicies(),
			},
		},
	}
	if err := k8s.Apply(ctx, t.Local, sg); err != nil {
		return nil, err
	}
	return sg, nil
}

func (t *endpoint) createSecurityGroupRule(ctx context.Context, ruleType string, sg *ec2v1beta1.SecurityGroup, labels map[string]string) error {
	name := t.securityGroupRuleName(ruleType, sg.Name)
	sgRule := &ec2v1beta1.SecurityGroupRule{
		ObjectMeta: metav1.ObjectMeta{
			Name:   name,
			Labels: labels,
		},
		Spec: ec2v1beta1.SecurityGroupRuleSpec{
			ForProvider: ec2v1beta1.SecurityGroupRuleParameters_2{
				Region: ptr.To(t.PrivateLinkEndpoint.Spec.Region),
				SecurityGroupIDRef: &xpv1.Reference{
					Name: sg.Name,
				},
				Protocol:    ptr.To("all"),
				Description: ptr.To("allow all"),
				FromPort:    ptr.To(float64(1)),
				ToPort:      ptr.To(float64(65535)),
				Type:        ptr.To(ruleType),
				CidrBlocks: []*string{
					ptr.To("0.0.0.0/0"),
				},
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.ProviderConfigName(),
				},
				ManagementPolicies: controllers.GetResourceManagementPolicies(),
			},
		},
	}
	return k8s.Apply(ctx, t.Local, sgRule)
}

func (t *endpoint) createVPCEndpointAssociations(ctx context.Context, ep *ec2v1beta1.VPCEndpoint, sg *ec2v1beta1.SecurityGroup, labels map[string]string) error {
	if err := t.createEndpointSgAssociation(ctx, ep, sg, labels); err != nil {
		return err
	}

	subnets, err := t.getMatchedSubnetsByEndpointZones()
	if err != nil {
		return fmt.Errorf("get endpoint subnets error: %w", err)
	}
	for _, subnet := range subnets {
		if err := t.createEndpointSubnetAssociation(ctx, ep, subnet, labels); err != nil {
			return fmt.Errorf("associate endpoint with subnet %s error: %w", subnet, err)
		}
	}
	return nil
}

func (t *endpoint) createEndpointSgAssociation(ctx context.Context, ep *ec2v1beta1.VPCEndpoint, sg *ec2v1beta1.SecurityGroup, labels map[string]string) error {
	association := &ec2v1beta1.VPCEndpointSecurityGroupAssociation{
		ObjectMeta: metav1.ObjectMeta{
			Name:   t.EndpointName(),
			Labels: labels,
		},
		Spec: ec2v1beta1.VPCEndpointSecurityGroupAssociationSpec{
			ForProvider: ec2v1beta1.VPCEndpointSecurityGroupAssociationParameters{
				Region: ptr.To(t.PrivateLinkEndpoint.Spec.Region),
				VPCEndpointIDRef: &xpv1.Reference{
					Name: ep.Name,
				},
				SecurityGroupIDRef: &xpv1.Reference{
					Name: sg.Name,
				},
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.ProviderConfigName(),
				},
				ManagementPolicies: controllers.GetResourceManagementPolicies(),
			},
		},
	}
	return k8s.Apply(ctx, t.Local, association)
}

func (t *endpoint) createEndpointSubnetAssociation(ctx context.Context, ep *ec2v1beta1.VPCEndpoint, subnetID string, labels map[string]string) error {
	association := &ec2v1beta1.VPCEndpointSubnetAssociation{
		ObjectMeta: metav1.ObjectMeta{
			Name:   t.endpointSubnetAssociationName(ep.Name, subnetID),
			Labels: labels,
		},
		Spec: ec2v1beta1.VPCEndpointSubnetAssociationSpec{
			ForProvider: ec2v1beta1.VPCEndpointSubnetAssociationParameters{
				Region:   ptr.To(t.PrivateLinkEndpoint.Spec.Region),
				SubnetID: ptr.To(subnetID),
				VPCEndpointIDRef: &xpv1.Reference{
					Name: ep.Name,
				},
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.ProviderConfigName(),
				},
				ManagementPolicies: controllers.GetResourceManagementPolicies(),
			},
		},
	}
	return k8s.Apply(ctx, t.Local, association)
}

func (t *endpoint) getMatchedSubnetsByEndpointZones() ([]string, error) {
	subnets, err := t.endpointSubnets()
	if err != nil {
		return nil, err
	}
	az2subnets := make(map[string]string)
	for _, subnet := range subnets {
		az2subnets[subnet.AvailabilityZoneID] = subnet.SubnetID
	}

	// Spec.Zones is the user-requested endpoint AZ ID list. Endpoint-only mode switches
	// the subnet source, but still creates one association for each requested AZ. We do
	// not validate whether the customer endpoint service supports these AZs here; AWS
	// returns the authoritative error when the VPCEndpoint association is reconciled.
	endpointSubnets := make([]string, 0, len(t.PrivateLinkEndpoint.Spec.Zones))
	for _, azID := range t.PrivateLinkEndpoint.Spec.Zones {
		subnetId, ok := az2subnets[azID]
		if !ok {
			if t.usePrivateLinkEndpointSubnets() {
				return nil, fmt.Errorf("az %s is not found in private link endpoint subnets", azID)
			}
			return nil, fmt.Errorf("az %s is not found in subnets", azID)
		}
		endpointSubnets = append(endpointSubnets, subnetId)
	}
	return endpointSubnets, nil
}

func (t *endpoint) PostCreate(ctx context.Context) deploy.Result {
	t.PrivateLinkEndpoint.Status.Phase = v1alpha1.PrivateLinkEndpointPhaseCreating

	sg, sgRules, err := t.listSecurityGroupCRs(ctx)
	if err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}
	if !controllers.IsResourceReady(sg.Status.ResourceStatus) {
		return deploy.Error(fmt.Errorf("security group is not ready"))
	}
	for _, sgRule := range sgRules {
		if !controllers.IsResourceReady(sgRule.Status.ResourceStatus) {
			return deploy.Error(fmt.Errorf("security group rule %s is not ready", sgRule.Name))
		}
	}

	ep, epSgAssociation, epSubnetAssociations, err := t.listVPCEndpointCRs(ctx)
	if err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}
	if !controllers.IsResourceReady(ep.Status.ResourceStatus) {
		return deploy.Error(fmt.Errorf("vpc endpoint is not ready"))
	}
	if !controllers.IsResourceReady(epSgAssociation.Status.ResourceStatus) {
		return deploy.Error(fmt.Errorf("vpc endpoint security group association is not ready"))
	}
	for _, subnetAssociation := range epSubnetAssociations {
		if !controllers.IsResourceReady(subnetAssociation.Status.ResourceStatus) {
			return deploy.Error(fmt.Errorf("vpc endpoint subnet association %s is not ready", subnetAssociation.Name))
		}
	}

	if err := t.syncEndpointInfo(ctx, ep); err != nil {
		return deploy.Error(err)
	}
	if t.PrivateLinkEndpoint.Status.Phase != v1alpha1.PrivateLinkEndpointPhaseEndpointAvailable {
		return deploy.Error(fmt.Errorf("vpc endpoint is not available"))
	}
	return deploy.Done()
}

func (t *endpoint) syncEndpointInfo(ctx context.Context, ep *ec2v1beta1.VPCEndpoint) error {
	t.PrivateLinkEndpoint.Status.ID = *ep.Status.AtProvider.ID
	t.syncEndpointState(*ep.Status.AtProvider.State)

	subnets, err := t.endpointSubnets()
	if err != nil {
		return err
	}
	azIDMap := make(map[string]string)
	for _, subnet := range subnets {
		azIDMap[subnet.AvailabilityZone] = subnet.AvailabilityZoneID
	}
	if err := t.syncEndpointDNS(ep.Status.AtProvider.DNSEntry, azIDMap); err != nil {
		return err
	}

	// crossplane take a long time to refresh the status, try to use aws SDK to get the detail ASAP
	if t.shouldRefreshEndpointDNSFromAWS() {
		cli := t.awsCli.EC2(&aws.AssumedRole{RoleARN: t.RoleARN()})
		result, err := cli.DescribeVpcEndpoints(ctx, &ec2.DescribeVpcEndpointsInput{
			VpcEndpointIds: []string{t.PrivateLinkEndpoint.Status.ID},
		})
		if err != nil {
			return fmt.Errorf("describe vpc endpoint %s error: %w", t.PrivateLinkEndpoint.Status.ID, err)
		}
		if len(result.VpcEndpoints) == 0 {
			return fmt.Errorf("VPC endpoint %s not found", t.PrivateLinkEndpoint.Status.ID)
		}

		ve := result.VpcEndpoints[0]
		t.syncEndpointState(string(ve.State))
		dnsEntries := make([]ec2v1beta1.DNSEntryObservation, 0, len(ve.DnsEntries))
		for _, dnsEntry := range ve.DnsEntries {
			dnsEntries = append(dnsEntries, ec2v1beta1.DNSEntryObservation{
				DNSName:      dnsEntry.DnsName,
				HostedZoneID: dnsEntry.HostedZoneId,
			})
		}
		if err := t.syncEndpointDNS(dnsEntries, azIDMap); err != nil {
			return err
		}
	}
	return nil
}

func (t *endpoint) shouldRefreshEndpointDNSFromAWS() bool {
	return len(t.PrivateLinkEndpoint.Status.ZonesAddresses) != len(t.PrivateLinkEndpoint.Spec.Zones)
}

func (t *endpoint) endpointSubnets() ([]v1alpha1.Subnet, error) {
	if t.usePrivateLinkEndpointSubnets() {
		if len(t.VPC.Status.PrivateLinkEndpointSubnets) == 0 {
			return nil, fmt.Errorf("private link endpoint subnets are not ready")
		}
		return t.VPC.Status.PrivateLinkEndpointSubnets, nil
	}
	return t.VPC.Status.NodeSubnets, nil
}

func (t *endpoint) usePrivateLinkEndpointSubnets() bool {
	return util.ShouldUsePrivateLinkEndpointSubnets(t.VPC.Spec.PrivateLinkEndpointZones, t.VPC.Spec.NodeZones)
}

func (t *endpoint) syncEndpointDNS(dnsEntries []ec2v1beta1.DNSEntryObservation, azIDMap map[string]string) error {
	if len(dnsEntries) > 0 {
		addr, zonesAddr, err := parsePLEDNSEntries(dnsEntries, azIDMap)
		if err != nil {
			return err
		}
		t.PrivateLinkEndpoint.Status.EndpointAddress = addr
		t.PrivateLinkEndpoint.Status.ZonesAddresses = zonesAddr
	}
	return nil
}

func (t *endpoint) syncEndpointState(state string) {
	switch strings.ToLower(state) {
	case "available":
		t.PrivateLinkEndpoint.Status.Phase = v1alpha1.PrivateLinkEndpointPhaseEndpointAvailable
	case "pendingacceptance":
		t.PrivateLinkEndpoint.Status.Phase = v1alpha1.PrivateLinkEndpointPhasePendingAcceptance
	case "deleting":
		t.PrivateLinkEndpoint.Status.Phase = v1alpha1.PrivateLinkEndpointPhaseDeleting
	case "rejected":
		t.PrivateLinkEndpoint.Status.Phase = v1alpha1.PrivateLinkEndpointPhaseRejected
	case "failed":
		t.PrivateLinkEndpoint.Status.Phase = v1alpha1.PrivateLinkEndpointPhaseFailed
	}
}

func (t *endpoint) listSecurityGroupCRs(ctx context.Context) (*ec2v1beta1.SecurityGroup, []ec2v1beta1.SecurityGroupRule, error) {
	sg := &ec2v1beta1.SecurityGroup{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.EndpointName(),
		},
	}
	if err := t.Local.Get(ctx, client.ObjectKeyFromObject(sg), sg); err != nil {
		return nil, nil, err
	}

	labelOpt := client.MatchingLabels{
		v1alpha1.PrivateLinkEndpointNameKey: t.PrivateLinkEndpoint.Name,
	}
	sgRules := &ec2v1beta1.SecurityGroupRuleList{}
	if err := t.Local.List(ctx, sgRules, labelOpt); err != nil {
		return nil, nil, err
	}
	return sg, sgRules.Items, nil
}

func (t *endpoint) listVPCEndpointCRs(ctx context.Context) (*ec2v1beta1.VPCEndpoint, *ec2v1beta1.VPCEndpointSecurityGroupAssociation, []ec2v1beta1.VPCEndpointSubnetAssociation, error) {
	ep := &ec2v1beta1.VPCEndpoint{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.EndpointName(),
		},
	}
	if err := t.Local.Get(ctx, client.ObjectKeyFromObject(ep), ep); err != nil {
		return nil, nil, nil, err
	}

	sgAssociation := &ec2v1beta1.VPCEndpointSecurityGroupAssociation{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.EndpointName(),
		},
	}
	if err := t.Local.Get(ctx, client.ObjectKeyFromObject(sgAssociation), sgAssociation); err != nil {
		return nil, nil, nil, err
	}

	labelOpt := client.MatchingLabels{
		v1alpha1.PrivateLinkEndpointNameKey: t.PrivateLinkEndpoint.Name,
	}
	subnetAssociations := &ec2v1beta1.VPCEndpointSubnetAssociationList{}
	if err := t.Local.List(ctx, subnetAssociations, labelOpt); err != nil {
		return nil, nil, nil, err
	}
	return ep, sgAssociation, subnetAssociations.Items, nil
}

func (t *endpoint) Delete(ctx context.Context) deploy.Result {
	sgAssociation := &ec2v1beta1.VPCEndpointSecurityGroupAssociation{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.EndpointName(),
		},
	}
	if err := controllers.DeleteUpboundResource(ctx, t.Local, sgAssociation); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}

	labelOpt := client.MatchingLabels{
		v1alpha1.PrivateLinkEndpointNameKey: t.PrivateLinkEndpoint.Name,
	}
	subnetAssociations := &ec2v1beta1.VPCEndpointSubnetAssociationList{}
	if err := t.Local.List(ctx, subnetAssociations, labelOpt); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}
	for i := range subnetAssociations.Items {
		if err := controllers.DeleteUpboundResource(ctx, t.Local, &subnetAssociations.Items[i]); err != nil {
			return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
		}
	}

	sgRules := &ec2v1beta1.SecurityGroupRuleList{}
	if err := t.Local.List(ctx, sgRules, labelOpt); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}
	for i := range sgRules.Items {
		if err := controllers.DeleteUpboundResource(ctx, t.Local, &sgRules.Items[i]); err != nil {
			return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
		}
	}

	ep := &ec2v1beta1.VPCEndpoint{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.EndpointName(),
		},
	}
	if err := controllers.DeleteUpboundResource(ctx, t.Local, ep); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}

	sg := &ec2v1beta1.SecurityGroup{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.EndpointName(),
		},
	}
	if err := controllers.DeleteUpboundResource(ctx, t.Local, sg); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}
	return deploy.Done()
}

func (t *endpoint) PostDelete(ctx context.Context) deploy.Result {
	sgAssociation := &ec2v1beta1.VPCEndpointSecurityGroupAssociation{}
	if err := k8s.GetDeletedObject(ctx, t.Local, types.NamespacedName{Name: t.EndpointName()}, sgAssociation); err != nil {
		return deploy.Error(err).WithRequeuAfter(time.Second * 30)
	}

	labelOpt := client.MatchingLabels{
		v1alpha1.PrivateLinkEndpointNameKey: t.PrivateLinkEndpoint.Name,
	}
	subnetAssociations := &ec2v1beta1.VPCEndpointSubnetAssociationList{}
	if err := t.Local.List(ctx, subnetAssociations, labelOpt); err != nil {
		return deploy.Error(err).WithRequeuAfter(time.Second * 30)
	}
	if len(subnetAssociations.Items) > 0 {
		return deploy.Error(errors.New("vpc endpoint subnet associations still exist")).WithRequeuAfter(time.Second * 30)
	}

	sgRules := &ec2v1beta1.SecurityGroupRuleList{}
	if err := t.Local.List(ctx, sgRules, labelOpt); err != nil {
		return deploy.Error(err).WithRequeuAfter(time.Second * 30)
	}
	if len(sgRules.Items) > 0 {
		return deploy.Error(errors.New("security group rules still exist")).WithRequeuAfter(time.Second * 30)
	}

	ep := &ec2v1beta1.VPCEndpoint{}
	if err := k8s.GetDeletedObject(ctx, t.Local, types.NamespacedName{Name: t.EndpointName()}, ep); err != nil {
		return deploy.Error(err).WithRequeuAfter(time.Second * 30)
	}

	sg := &ec2v1beta1.SecurityGroup{}
	if err := k8s.GetDeletedObject(ctx, t.Local, types.NamespacedName{Name: t.EndpointName()}, sg); err != nil {
		return deploy.Error(err).WithRequeuAfter(time.Second * 30)
	}
	return deploy.Done()
}

func (t *endpoint) securityGroupRuleName(ruleType, sgName string) string {
	return fmt.Sprintf("%s-%s", ruleType, sgName)
}

func (t *endpoint) endpointSubnetAssociationName(epName, subnetID string) string {
	return fmt.Sprintf("%s-%s", epName, subnetID)
}

// Private Link Endpoint DNS entry example:
//
//	 [{
//		DnsName: "vpce-023af6250979c0365-j9m3ti32.vpce-svc-0e427c5d93acce18f.us-west-2.vpce.amazonaws.com",
//		HostedZoneId: "Z1YSA3EXCYUU9Z"
//		},{
//		DnsName: "vpce-023af6250979c0365-j9m3ti32-us-west-2a.vpce-svc-0e427c5d93acce18f.us-west-2.vpce.amazonaws.com",
//		HostedZoneId: "Z1YSA3EXCYUU9Z"
//		},
//		},{
//		DnsName: "privatelink-19964668.vwiyyv21q7co.clusters.tidb-cloud.com",
//	    HostedZoneId: Z09814683NRQ8JG4GH6UE
//		}
//	 ...
//
// ]
func parsePLEDNSEntries(entries []ec2v1beta1.DNSEntryObservation,
	logicalToPhysicalZoneMap map[string]string,
) (string, map[string]string, error) {
	if len(entries) < pleMiniDNSEntriesNum {
		return "", nil, fmt.Errorf("dns entries of private link should have at least 2, but got %d",
			len(entries))
	}

	var (
		regionalAddress string
		zonalAddresses  = make(map[string]string)
	)
	for i := 0; i < len(entries); i++ {
		dnsName := *entries[i].DNSName
		if !strings.HasSuffix(dnsName, ".vpce.amazonaws.com") {
			// it's the private dns name, skip
			continue
		}
		levels := strings.Split(dnsName, ".")
		if len(levels) != zonalDNSLevelNum {
			return "", nil, fmt.Errorf(
				"zonal dns name should have 6 level, but %d, the dns name: %s ",
				len(levels), dnsName)
		}
		smallParts := strings.Split(levels[0], "-")
		if len(smallParts) == partsOfSixLevelOfRegionalDNS {
			// it's regional address
			regionalAddress = dnsName
			continue
		}
		if len(smallParts) != partsOfSixLevelOfZonalDNS {
			return "", nil, fmt.Errorf(
				"the sixth level of zonal dns name should have 6 parts, but %d, the sixth level: %s ",
				len(smallParts), levels[0])
		}
		logicalAZ := strings.Join([]string{smallParts[3], smallParts[4], smallParts[5]}, "-")
		zonalAddresses[logicalToPhysicalZoneMap[logicalAZ]] = dnsName
	}
	return regionalAddress, zonalAddresses, nil
}
