package aks

import (
	"context"
	"fmt"
	"net/http"
	"sort"
	"testing"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6"
	"github.com/crossplane/crossplane-runtime/pkg/meta"
	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/test/mockazure"
	"github.com/upbound/provider-azure/apis/network/v1beta1"
	"go.uber.org/mock/gomock"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

func fakePrivateLinkEndpoint(t *testing.T) *PrivateLinkEndpoint {
	return NewPrivateLinkEndpoint(fakeTaskContext(t))
}

func TestPrivateLinkEndpoint_Create(t *testing.T) {
	t.Run("list conns failed", func(t *testing.T) {
		task := fakePrivateLinkEndpoint(t)
		ctl := gomock.NewController(t)
		plsCli := mockazure.NewMockPrivateLinkServiceClient(ctl)
		task.PLSCli = plsCli
		resourceGroupName := controllers.GetResourceGroupName(task.VPC)
		serviceName := task.PrivateLinkService.Status.AzurePrivateLinkService.Name
		plsCli.EXPECT().ListPrivateLinkEndpointConnections(gomock.Any(), resourceGroupName, serviceName).Return(nil, assert.AnError)
		result := task.Create(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.ErrorContains(t, retriableErr, "assert.AnError")
		assert.Equal(t, defaultRequeuInterval, res.RequeueAfter)
	})
	t.Run("1 unaccepted conns and 1 unrejected cons", func(t *testing.T) {
		task := fakePrivateLinkEndpoint(t)
		ctl := gomock.NewController(t)
		plsCli := mockazure.NewMockPrivateLinkServiceClient(ctl)
		task.PLSCli = plsCli
		resourceGroupName := controllers.GetResourceGroupName(task.VPC)
		serviceName := task.PrivateLinkService.Status.AzurePrivateLinkService.Name
		task.PrivateLinkService.Spec.Connections = []v1alpha1.Connection{
			{Endpoint: "/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2", IPs: []string{"10.2.3.4"}},
		}
		plsCli.EXPECT().ListPrivateLinkEndpointConnections(gomock.Any(), resourceGroupName, serviceName).Return([]*armnetwork.PrivateEndpointConnection{
			{
				ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1.x1"),
				Name: ptr.To("conn1.x1"),
				Properties: &armnetwork.PrivateEndpointConnectionProperties{
					PrivateEndpoint: &armnetwork.PrivateEndpoint{
						ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"),
					},
					PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
						Status: ptr.To("Approved"),
					},
				},
			},
			{
				ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2.x2"),
				Name: ptr.To("conn2.x2"),
				Properties: &armnetwork.PrivateEndpointConnectionProperties{
					PrivateEndpoint: &armnetwork.PrivateEndpoint{
						ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2"),
					},
					PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
						Status: ptr.To("Pending"),
					},
				},
			},
		}, nil)
		plsCli.EXPECT().AcceptPrivateLinkEndpointConnection(gomock.Any(), resourceGroupName, serviceName, &armnetwork.PrivateEndpointConnection{
			ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2.x2"),
			Name: ptr.To("conn2.x2"),
			Properties: &armnetwork.PrivateEndpointConnectionProperties{
				PrivateEndpoint: &armnetwork.PrivateEndpoint{
					ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2"),
				},
				PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
					Status: ptr.To("Pending"),
				},
			},
		}).Return(nil)
		plsCli.EXPECT().RejectPrivateLinkEndpointConnection(gomock.Any(), resourceGroupName, serviceName, &armnetwork.PrivateEndpointConnection{
			ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1.x1"),
			Name: ptr.To("conn1.x1"),
			Properties: &armnetwork.PrivateEndpointConnectionProperties{
				PrivateEndpoint: &armnetwork.PrivateEndpoint{
					ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"),
				},
				PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
					Status: ptr.To("Approved"),
				},
			},
		}).Return(nil)
		result := task.Create(context.Background())
		assert.Equal(t, deploy.Done(), result)
	})
}

func TestPrivateLink_calculateConnsStatus(t *testing.T) {
	cases := []struct {
		name             string
		allowed          []v1alpha1.Connection
		connsMap         map[string]*armnetwork.PrivateEndpointConnection
		outputConnStatus []v1alpha1.ConnectionStatus
		unrejected       []*armnetwork.PrivateEndpointConnection
	}{
		{
			name: "all accepted",
			allowed: []v1alpha1.Connection{
				{Endpoint: "/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"},
				{Endpoint: "/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2"},
			},
			connsMap: map[string]*armnetwork.PrivateEndpointConnection{
				"/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1": {
					ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1.x1"),
					Name: ptr.To("conn1.x1"),
					Properties: &armnetwork.PrivateEndpointConnectionProperties{
						PrivateEndpoint: &armnetwork.PrivateEndpoint{
							ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"),
						},
						PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
							Status: ptr.To("Approved"),
						},
					},
				},
				"/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2": {
					ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2.x2"),
					Name: ptr.To("conn2.x2"),
					Properties: &armnetwork.PrivateEndpointConnectionProperties{
						PrivateEndpoint: &armnetwork.PrivateEndpoint{
							ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2"),
						},
						PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
							Status: ptr.To("Approved"),
						},
					},
				},
			},
			outputConnStatus: []v1alpha1.ConnectionStatus{
				{
					Endpoint: "/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1",
					Phase:    v1alpha1.ConnectionPhaseActive,
				},
				{
					Endpoint: "/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2",
					Phase:    v1alpha1.ConnectionPhaseActive,
				},
			},
			unrejected: []*armnetwork.PrivateEndpointConnection{},
		},
		{
			name:    "all unrejected",
			allowed: []v1alpha1.Connection{},
			connsMap: map[string]*armnetwork.PrivateEndpointConnection{
				"/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1": {
					ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1.x1"),
					Name: ptr.To("conn1.x1"),
					Properties: &armnetwork.PrivateEndpointConnectionProperties{
						PrivateEndpoint: &armnetwork.PrivateEndpoint{
							ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"),
						},
						PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
							Status: ptr.To("Approved"),
						},
					},
				},
				"/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2": {
					ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2.x2"),
					Name: ptr.To("conn2.x2"),
					Properties: &armnetwork.PrivateEndpointConnectionProperties{
						PrivateEndpoint: &armnetwork.PrivateEndpoint{
							ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2"),
						},
						PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
							Status: ptr.To("Approved"),
						},
					},
				},
			},
			outputConnStatus: []v1alpha1.ConnectionStatus{},
			unrejected: []*armnetwork.PrivateEndpointConnection{
				{
					ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1.x1"),
					Name: ptr.To("conn1.x1"),
					Properties: &armnetwork.PrivateEndpointConnectionProperties{
						PrivateEndpoint: &armnetwork.PrivateEndpoint{
							ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"),
						},
						PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
							Status: ptr.To("Approved"),
						},
					},
				},
				{
					ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2.x2"),
					Name: ptr.To("conn2.x2"),
					Properties: &armnetwork.PrivateEndpointConnectionProperties{
						PrivateEndpoint: &armnetwork.PrivateEndpoint{
							ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2"),
						},
						PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
							Status: ptr.To("Approved"),
						},
					},
				},
			},
		},
		{
			name: "1 pending, 1 accepted, 1 rejected",
			allowed: []v1alpha1.Connection{
				{Endpoint: "/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"},
				{Endpoint: "/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2"},
			},
			connsMap: map[string]*armnetwork.PrivateEndpointConnection{
				"/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1": {
					ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1.x1"),
					Name: ptr.To("conn1.x1"),
					Properties: &armnetwork.PrivateEndpointConnectionProperties{
						PrivateEndpoint: &armnetwork.PrivateEndpoint{
							ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"),
						},
						PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
							Status: ptr.To("Approved"),
						},
					},
				},
				"/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2": {
					ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2.x2"),
					Name: ptr.To("conn2.x2"),
					Properties: &armnetwork.PrivateEndpointConnectionProperties{
						PrivateEndpoint: &armnetwork.PrivateEndpoint{
							ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2"),
						},
						PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
							Status: ptr.To("Pending"),
						},
					},
				},
				"/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn3": {
					ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn3.x3"),
					Name: ptr.To("conn3.x3"),
					Properties: &armnetwork.PrivateEndpointConnectionProperties{
						PrivateEndpoint: &armnetwork.PrivateEndpoint{
							ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn3"),
						},
						PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
							Status: ptr.To("Approved"),
						},
					},
				},
				"/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn4": {
					ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn3.x3"),
					Name: ptr.To("conn4.x4"),
					Properties: &armnetwork.PrivateEndpointConnectionProperties{
						PrivateEndpoint: &armnetwork.PrivateEndpoint{
							ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn4"),
						},
						PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
							Status: ptr.To("Rejected"),
						},
					},
				},
			},
			outputConnStatus: []v1alpha1.ConnectionStatus{
				{
					Endpoint: "/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1",
					Phase:    v1alpha1.ConnectionPhaseActive,
				},
				{
					Endpoint: "/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2",
					Phase:    v1alpha1.ConnectionPhasePending,
				},
			},
			unrejected: []*armnetwork.PrivateEndpointConnection{
				{
					ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn3.x3"),
					Name: ptr.To("conn3.x3"),
					Properties: &armnetwork.PrivateEndpointConnectionProperties{
						PrivateEndpoint: &armnetwork.PrivateEndpoint{
							ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn3"),
						},
						PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
							Status: ptr.To("Approved"),
						},
					},
				},
			},
		},
	}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			task := fakePrivateLinkEndpoint(t)
			connStatus, unrejected := task.calculateConnsStatus(c.allowed, c.connsMap)
			assert.Equal(t, c.outputConnStatus, connStatus)
			sort.Slice(c.unrejected, func(i, j int) bool {
				return *c.unrejected[i].Name < *c.unrejected[j].Name
			})
			sort.Slice(unrejected, func(i, j int) bool {
				return *unrejected[i].Name < *unrejected[j].Name
			})
			assert.Equal(t, c.unrejected, unrejected)
		})
	}
}

func TestPrivateLInkENdpoint_PostCreate(t *testing.T) {
	t.Run("no unaccepted and unrejected", func(t *testing.T) {
		task := fakePrivateLinkEndpoint(t)
		ctl := gomock.NewController(t)
		plsCli := mockazure.NewMockPrivateLinkServiceClient(ctl)
		task.PLSCli = plsCli
		resourceGroupName := controllers.GetResourceGroupName(task.VPC)
		serviceName := task.PrivateLinkService.Status.AzurePrivateLinkService.Name
		plsCli.EXPECT().ListPrivateLinkEndpointConnections(gomock.Any(), resourceGroupName, serviceName).Return([]*armnetwork.PrivateEndpointConnection{
			{
				ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1.x1"),
				Name: ptr.To("conn1.x1"),
				Properties: &armnetwork.PrivateEndpointConnectionProperties{
					PrivateEndpoint: &armnetwork.PrivateEndpoint{
						ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"),
					},
					PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
						Status: ptr.To("Approved"),
					},
				},
			},
		}, nil)
		task.PrivateLinkService.Spec.Connections = []v1alpha1.Connection{
			{Endpoint: "/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"},
		}
		result := task.PostCreate(context.Background())
		assert.Equal(t, deploy.Done(), result)
		pls := &v1alpha1.PrivateLinkService{}
		assert.Nil(t, task.Local.Get(context.Background(), client.ObjectKeyFromObject(task.PrivateLinkService), pls))
		assert.Equal(t, []v1alpha1.ConnectionStatus{
			{
				Endpoint: "/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1",
				Phase:    v1alpha1.ConnectionPhaseActive,
			},
		}, pls.Status.Connections)
	})
	t.Run("1 unaccepted", func(t *testing.T) {
		task := fakePrivateLinkEndpoint(t)
		ctl := gomock.NewController(t)
		plsCli := mockazure.NewMockPrivateLinkServiceClient(ctl)
		task.PLSCli = plsCli
		resourceGroupName := controllers.GetResourceGroupName(task.VPC)
		serviceName := task.PrivateLinkService.Status.AzurePrivateLinkService.Name
		plsCli.EXPECT().ListPrivateLinkEndpointConnections(gomock.Any(), resourceGroupName, serviceName).Return([]*armnetwork.PrivateEndpointConnection{
			{
				ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1.x1"),
				Name: ptr.To("conn1.x1"),
				Properties: &armnetwork.PrivateEndpointConnectionProperties{
					PrivateEndpoint: &armnetwork.PrivateEndpoint{
						ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"),
					},
					PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
						Status: ptr.To("Pending"),
					},
				},
			},
		}, nil)
		task.PrivateLinkService.Spec.Connections = []v1alpha1.Connection{
			{Endpoint: "/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"},
		}
		result := task.PostCreate(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.ErrorContains(t, retriableErr, "some endpoints are not accepted or rejected")
		assert.Equal(t, defaultRequeuInterval, res.RequeueAfter)
	})
	t.Run("1 unrejected", func(t *testing.T) {
		task := fakePrivateLinkEndpoint(t)
		ctl := gomock.NewController(t)
		plsCli := mockazure.NewMockPrivateLinkServiceClient(ctl)
		task.PLSCli = plsCli
		resourceGroupName := controllers.GetResourceGroupName(task.VPC)
		serviceName := task.PrivateLinkService.Status.AzurePrivateLinkService.Name
		plsCli.EXPECT().ListPrivateLinkEndpointConnections(gomock.Any(), resourceGroupName, serviceName).Return([]*armnetwork.PrivateEndpointConnection{
			{
				ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1.x1"),
				Name: ptr.To("conn1.x1"),
				Properties: &armnetwork.PrivateEndpointConnectionProperties{
					PrivateEndpoint: &armnetwork.PrivateEndpoint{
						ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"),
					},
					PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
						Status: ptr.To("Approved"),
					},
				},
			},
		}, nil)
		result := task.PostCreate(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.ErrorContains(t, retriableErr, "some endpoints are not accepted or rejected")
		assert.Equal(t, defaultRequeuInterval, res.RequeueAfter)
	})
	t.Run("1 pending and not in allowed", func(t *testing.T) {
		task := fakePrivateLinkEndpoint(t)
		ctl := gomock.NewController(t)
		plsCli := mockazure.NewMockPrivateLinkServiceClient(ctl)
		task.PLSCli = plsCli
		resourceGroupName := controllers.GetResourceGroupName(task.VPC)
		serviceName := task.PrivateLinkService.Status.AzurePrivateLinkService.Name
		plsCli.EXPECT().ListPrivateLinkEndpointConnections(gomock.Any(), resourceGroupName, serviceName).Return([]*armnetwork.PrivateEndpointConnection{
			{
				ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1.x1"),
				Name: ptr.To("conn1.x1"),
				Properties: &armnetwork.PrivateEndpointConnectionProperties{
					PrivateEndpoint: &armnetwork.PrivateEndpoint{
						ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"),
					},
					PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
						Status: ptr.To("Approved"),
					},
				},
			},
			{
				ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2.x2"),
				Name: ptr.To("conn2.x2"),
				Properties: &armnetwork.PrivateEndpointConnectionProperties{
					PrivateEndpoint: &armnetwork.PrivateEndpoint{
						ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2"),
					},
					PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
						Status: ptr.To("Pending"),
					},
				},
			},
		}, nil)
		task.PrivateLinkService.Spec.Connections = []v1alpha1.Connection{
			{Endpoint: "/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"},
		}
		result := task.PostCreate(context.Background())
		assert.Equal(t, deploy.Done(), result)
		pls := &v1alpha1.PrivateLinkService{}
		assert.Nil(t, task.Local.Get(context.Background(), client.ObjectKeyFromObject(task.PrivateLinkService), pls))
		assert.Equal(t, []v1alpha1.ConnectionStatus{
			{
				Endpoint: "/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1",
				Phase:    v1alpha1.ConnectionPhaseActive,
			},
			{
				Endpoint: "/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2",
				Phase:    v1alpha1.ConnectionPhasePending,
			},
		}, pls.Status.Connections)
	})
}

func TestGetPendingAndNotAllowedConns(t *testing.T) {
	testData := []struct {
		name     string
		allowed  []v1alpha1.Connection
		conns    map[string]*armnetwork.PrivateEndpointConnection
		expected []v1alpha1.ConnectionStatus
	}{
		{
			name:    "all accepted",
			allowed: []v1alpha1.Connection{},
			conns: map[string]*armnetwork.PrivateEndpointConnection{
				"/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1": {
					ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1.x1"),
					Name: ptr.To("conn1.x1"),
					Properties: &armnetwork.PrivateEndpointConnectionProperties{
						PrivateEndpoint: &armnetwork.PrivateEndpoint{
							ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"),
						},
						PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
							Status: ptr.To("Approved"),
						},
					},
				},
				"/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2": {
					ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2.x2"),
					Name: ptr.To("conn2.x2"),
					Properties: &armnetwork.PrivateEndpointConnectionProperties{
						PrivateEndpoint: &armnetwork.PrivateEndpoint{
							ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2"),
						},
						PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
							Status: ptr.To("Approved"),
						},
					},
				},
			},
			expected: []v1alpha1.ConnectionStatus{},
		},
		{
			name: "2 pending and in allowed",
			allowed: []v1alpha1.Connection{
				{Endpoint: "/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"},
				{Endpoint: "/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2"},
			},
			conns: map[string]*armnetwork.PrivateEndpointConnection{
				"/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1": {
					ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1.x1"),
					Name: ptr.To("conn1.x1"),
					Properties: &armnetwork.PrivateEndpointConnectionProperties{
						PrivateEndpoint: &armnetwork.PrivateEndpoint{
							ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"),
						},
						PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
							Status: ptr.To("Pending"),
						},
					},
				},
				"/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2": {
					ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2.x2"),
					Name: ptr.To("conn2.x2"),
					Properties: &armnetwork.PrivateEndpointConnectionProperties{
						PrivateEndpoint: &armnetwork.PrivateEndpoint{
							ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2"),
						},
						PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
							Status: ptr.To("Pending"),
						},
					},
				},
			},
			expected: []v1alpha1.ConnectionStatus{},
		},
		{
			name:    "2 pending and not in allowed",
			allowed: []v1alpha1.Connection{},
			conns: map[string]*armnetwork.PrivateEndpointConnection{
				"/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1": {
					ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1.x1"),
					Name: ptr.To("conn1.x1"),
					Properties: &armnetwork.PrivateEndpointConnectionProperties{
						PrivateEndpoint: &armnetwork.PrivateEndpoint{
							ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"),
						},
						PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
							Status: ptr.To("Pending"),
						},
					},
				},
				"/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2": {
					ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2.x2"),
					Name: ptr.To("conn2.x2"),
					Properties: &armnetwork.PrivateEndpointConnectionProperties{
						PrivateEndpoint: &armnetwork.PrivateEndpoint{
							ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2"),
						},
						PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
							Status: ptr.To("Pending"),
						},
					},
				},
			},
			expected: []v1alpha1.ConnectionStatus{
				{
					Endpoint: "/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1",
					Phase:    v1alpha1.ConnectionPhasePending,
				},
				{
					Endpoint: "/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2",
					Phase:    v1alpha1.ConnectionPhasePending,
				},
			},
		},
	}

	for _, c := range testData {
		t.Run(c.name, func(t *testing.T) {
			status := getPendingAndNotAllowedConns(c.allowed, c.conns)
			sort.Slice(status, func(i, j int) bool {
				return status[i].Endpoint < status[j].Endpoint
			})
			assert.Equal(t, c.expected, status)
		})
	}
}

func TestPrivateLinkEndpoint_Delete(t *testing.T) {
	t.Run("not private link service", func(t *testing.T) {
		task := fakePrivateLinkEndpoint(t)
		result := task.Delete(context.Background())
		assert.Equal(t, deploy.Done(), result)
	})
	t.Run("privatelinkservice don't include externalname", func(t *testing.T) {
		task := fakePrivateLinkEndpoint(t)
		ctl := gomock.NewController(t)
		plsCli := mockazure.NewMockPrivateLinkServiceClient(ctl)
		task.PLSCli = plsCli
		ves := &v1beta1.PrivateLinkService{
			ObjectMeta: metav1.ObjectMeta{
				Name: getPrivateLinkServiceResourceName(task.PrivateLinkService),
			},
		}
		assert.Nil(t, task.Local.Create(context.Background(), ves))
		result := task.Delete(context.Background())
		assert.Equal(t, deploy.Done(), result)
	})
	t.Run("list connections return not found", func(t *testing.T) {
		task := fakePrivateLinkEndpoint(t)
		ctl := gomock.NewController(t)
		plsCli := mockazure.NewMockPrivateLinkServiceClient(ctl)
		task.PLSCli = plsCli
		ves := &v1beta1.PrivateLinkService{
			ObjectMeta: metav1.ObjectMeta{
				Name: getPrivateLinkServiceResourceName(task.PrivateLinkService),
				Annotations: map[string]string{
					meta.AnnotationKeyExternalName: getPrivateLinkServiceResourceName(task.PrivateLinkService),
				},
			},
		}
		assert.Nil(t, task.Local.Create(context.Background(), ves))
		resourceGroupName := controllers.GetResourceGroupName(task.VPC)
		serviceName := meta.GetExternalName(ves)
		plsCli.EXPECT().ListPrivateLinkEndpointConnections(gomock.Any(), resourceGroupName, serviceName).Return(nil, runtime.NewResponseErrorWithErrorCode(&http.Response{
			Status:     "404 Not Found",
			StatusCode: 404,
		}, "ResourceNotFound"))
		result := task.Delete(context.Background())
		assert.Equal(t, deploy.Done(), result)
	})
	t.Run("remove all connections", func(t *testing.T) {
		task := fakePrivateLinkEndpoint(t)
		ctl := gomock.NewController(t)
		plsCli := mockazure.NewMockPrivateLinkServiceClient(ctl)
		task.PLSCli = plsCli
		ves := &v1beta1.PrivateLinkService{
			ObjectMeta: metav1.ObjectMeta{
				Name: getPrivateLinkServiceResourceName(task.PrivateLinkService),
				Annotations: map[string]string{
					meta.AnnotationKeyExternalName: getPrivateLinkServiceResourceName(task.PrivateLinkService),
				},
			},
		}
		assert.Nil(t, task.Local.Create(context.Background(), ves))
		resourceGroupName := controllers.GetResourceGroupName(task.VPC)
		task.PrivateLinkService.Status.AzurePrivateLinkService.Name = getPrivateLinkServiceResourceName(task.PrivateLinkService)
		assert.Nil(t, task.PrivateLinkServiceRW.UpdateStatus(context.Background(), task.PrivateLinkService))
		serviceName := task.PrivateLinkService.Status.AzurePrivateLinkService.Name
		plsCli.EXPECT().ListPrivateLinkEndpointConnections(gomock.Any(), resourceGroupName, serviceName).Return([]*armnetwork.PrivateEndpointConnection{
			{
				ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1.x1"),
				Name: ptr.To("conn1.x1"),
				Properties: &armnetwork.PrivateEndpointConnectionProperties{
					PrivateEndpoint: &armnetwork.PrivateEndpoint{
						ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"),
					},
					PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
						Status: ptr.To("Approved"),
					},
				},
			},
			{
				ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2.x2"),
				Name: ptr.To("conn2.x2"),
				Properties: &armnetwork.PrivateEndpointConnectionProperties{
					PrivateEndpoint: &armnetwork.PrivateEndpoint{
						ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2"),
					},
					PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
						Status: ptr.To("Pending"),
					},
				},
			},
		}, nil)
		plsCli.EXPECT().RemovePrivateLinkEndpointConnection(gomock.Any(), resourceGroupName, serviceName, &armnetwork.PrivateEndpointConnection{
			ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1.x1"),
			Name: ptr.To("conn1.x1"),
			Properties: &armnetwork.PrivateEndpointConnectionProperties{
				PrivateEndpoint: &armnetwork.PrivateEndpoint{
					ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"),
				},
				PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
					Status: ptr.To("Approved"),
				},
			},
		}).Return(nil)
		plsCli.EXPECT().RemovePrivateLinkEndpointConnection(gomock.Any(), resourceGroupName, serviceName, &armnetwork.PrivateEndpointConnection{
			ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2.x2"),
			Name: ptr.To("conn2.x2"),
			Properties: &armnetwork.PrivateEndpointConnectionProperties{
				PrivateEndpoint: &armnetwork.PrivateEndpoint{
					ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn2"),
				},
				PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
					Status: ptr.To("Pending"),
				},
			},
		}).Return(nil)
		result := task.Delete(context.Background())
		assert.Equal(t, deploy.Done(), result)
	})
}

func TestPrivateLInkENdpoint_PostDelete(t *testing.T) {
	t.Run("not private link service", func(t *testing.T) {
		task := fakePrivateLinkEndpoint(t)
		result := task.PostDelete(context.Background())
		assert.Equal(t, deploy.Done(), result)
	})
	t.Run("privatelinkservice don't include externalname", func(t *testing.T) {
		task := fakePrivateLinkEndpoint(t)
		ctl := gomock.NewController(t)
		plsCli := mockazure.NewMockPrivateLinkServiceClient(ctl)
		task.PLSCli = plsCli
		ves := &v1beta1.PrivateLinkService{
			ObjectMeta: metav1.ObjectMeta{
				Name: getPrivateLinkServiceResourceName(task.PrivateLinkService),
			},
		}
		assert.Nil(t, task.Local.Create(context.Background(), ves))
		result := task.PostDelete(context.Background())
		assert.Equal(t, deploy.Done(), result)
	})
	t.Run("list connections return not found", func(t *testing.T) {
		task := fakePrivateLinkEndpoint(t)
		ctl := gomock.NewController(t)
		plsCli := mockazure.NewMockPrivateLinkServiceClient(ctl)
		task.PLSCli = plsCli
		ves := &v1beta1.PrivateLinkService{
			ObjectMeta: metav1.ObjectMeta{
				Name: getPrivateLinkServiceResourceName(task.PrivateLinkService),
				Annotations: map[string]string{
					meta.AnnotationKeyExternalName: getPrivateLinkServiceResourceName(task.PrivateLinkService),
				},
			},
		}
		assert.Nil(t, task.Local.Create(context.Background(), ves))
		resourceGroupName := controllers.GetResourceGroupName(task.VPC)
		serviceName := meta.GetExternalName(ves)
		plsCli.EXPECT().ListPrivateLinkEndpointConnections(gomock.Any(), resourceGroupName, serviceName).Return(nil, runtime.NewResponseErrorWithErrorCode(&http.Response{
			Status:     "404 Not Found",
			StatusCode: 404,
		}, "ResourceNotFound"))
		result := task.PostDelete(context.Background())
		assert.Equal(t, deploy.Done(), result)
	})
	t.Run("private link service exists", func(t *testing.T) {
		task := fakePrivateLinkEndpoint(t)
		ctl := gomock.NewController(t)
		plsCli := mockazure.NewMockPrivateLinkServiceClient(ctl)
		task.PLSCli = plsCli
		ves := &v1beta1.PrivateLinkService{
			ObjectMeta: metav1.ObjectMeta{
				Name: getPrivateLinkServiceResourceName(task.PrivateLinkService),
				Annotations: map[string]string{
					meta.AnnotationKeyExternalName: getPrivateLinkServiceResourceName(task.PrivateLinkService),
				},
			},
		}
		task.PrivateLinkService.Status.AzurePrivateLinkService.Name = getPrivateLinkServiceResourceName(task.PrivateLinkService)
		plsCli.EXPECT().ListPrivateLinkEndpointConnections(gomock.Any(), controllers.GetResourceGroupName(task.VPC), getPrivateLinkServiceResourceName(task.PrivateLinkService)).Return(nil, nil)
		assert.Nil(t, task.Local.Create(context.Background(), ves))
		result := task.PostDelete(context.Background())
		assert.Equal(t, deploy.Done(), result)
	})
	t.Run("private link service exists and has connections", func(t *testing.T) {
		task := fakePrivateLinkEndpoint(t)
		ctl := gomock.NewController(t)
		plsCli := mockazure.NewMockPrivateLinkServiceClient(ctl)
		task.PLSCli = plsCli
		ves := &v1beta1.PrivateLinkService{
			ObjectMeta: metav1.ObjectMeta{
				Name: getPrivateLinkServiceResourceName(task.PrivateLinkService),
				Annotations: map[string]string{
					meta.AnnotationKeyExternalName: getPrivateLinkServiceResourceName(task.PrivateLinkService),
				},
			},
		}
		resourceGroupName := controllers.GetResourceGroupName(task.VPC)
		serviceName := meta.GetExternalName(ves)
		plsCli.EXPECT().ListPrivateLinkEndpointConnections(gomock.Any(), resourceGroupName, serviceName).Return([]*armnetwork.PrivateEndpointConnection{
			{
				ID:   ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1.x1"),
				Name: ptr.To("conn1.x1"),
				Properties: &armnetwork.PrivateEndpointConnectionProperties{
					PrivateEndpoint: &armnetwork.PrivateEndpoint{
						ID: ptr.To("/subscriptions/123/resourceGroups/rg/providers/Microsoft.Network/privateLinkServices/pls/privateEndpointConnections/conn1"),
					},
					PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
						Status: ptr.To("Approved"),
					},
				},
			},
		}, nil)
		assert.Nil(t, task.Local.Create(context.Background(), ves))
		result := task.PostDelete(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.ErrorContains(t, retriableErr, fmt.Sprintf("the privatelink service %s still has %d connections", serviceName, 1))
		assert.Equal(t, defaultRequeuInterval, res.RequeueAfter)
	})
}
