package aks

import (
	"context"
	"errors"
	"net/http"
	"testing"

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v8"
	"github.com/stretchr/testify/assert"
	"go.uber.org/mock/gomock"
	"k8s.io/utils/ptr"

	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/cloud/azure"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/privatelinkservice/task"
)

func Test_convertAzureConnection(t *testing.T) {
	buildConn := func(status, desc string) *armnetwork.PrivateEndpointConnection {
		return &armnetwork.PrivateEndpointConnection{
			Name: ptr.To("connection-id"),
			Properties: &armnetwork.PrivateEndpointConnectionProperties{
				PrivateEndpoint: &armnetwork.PrivateEndpoint{
					ID: ptr.To("endpoint-id"),
				},
				PrivateLinkServiceConnectionState: &armnetwork.PrivateLinkServiceConnectionState{
					Status:      ptr.To(status),
					Description: ptr.To(desc),
				},
			},
		}
	}

	cases := []struct {
		name    string
		status  string
		desc    string
		phase   v1alpha1.ConnectionPhase
		message string
	}{
		{
			name:    "approved",
			status:  "Approved",
			desc:    "approved by owner",
			phase:   v1alpha1.ConnectionPhaseActive,
			message: "approved by owner",
		},
		{
			name:    "rejected",
			status:  "Rejected",
			desc:    "rejected by owner",
			phase:   v1alpha1.ConnectionPhaseFailed,
			message: "rejected by owner",
		},
		{
			name:    "pending",
			status:  "Pending",
			desc:    "",
			phase:   v1alpha1.ConnectionPhasePending,
			message: "",
		},
		{
			name:    "disconnected",
			status:  "Disconnected",
			desc:    "",
			phase:   v1alpha1.ConnectionPhaseDeleting,
			message: "",
		},
		{
			name:    "unknown-with-message",
			status:  "Unknown",
			desc:    "custom message",
			phase:   v1alpha1.ConnectionPhaseFailed,
			message: "custom message",
		},
		{
			name:    "unknown-empty-message",
			status:  "Unknown",
			desc:    "",
			phase:   v1alpha1.ConnectionPhaseFailed,
			message: "private endpoint connection is in unknown state unknown",
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			out := convertAzureConnection(buildConn(tc.status, tc.desc))
			assert.Equal(t, &task.VpcEndpointConnection{
				EndpointID:   "endpoint-id",
				ConnectionID: "connection-id",
				Phase:        tc.phase,
				Message:      tc.message,
			}, out)
		})
	}
}

func TestEndpoint_getAzureClientMeta(t *testing.T) {
	endpoint := &Endpoint{
		Context:  &task.Context{},
		azureCli: nil,
	}
	_, _, _, err := endpoint.getAzureClientMeta()
	assert.ErrorContains(t, err, "azure client is not configured")
}

func TestEndpoint_updateConnections(t *testing.T) {
	ctl := gomock.NewController(t)
	azureCli := azure.NewMockClient(ctl)
	plsCli := azure.NewMockPrivateLinkServicesAPI(ctl)

	azureCli.EXPECT().PrivateLinkServices("sub-1").Return(plsCli, nil)
	plsCli.EXPECT().UpdatePrivateEndpointConnection(gomock.Any(), "rg-1", "pls-1", "conn-1", gomock.Any()).Return(nil)
	plsCli.EXPECT().UpdatePrivateEndpointConnection(gomock.Any(), "rg-1", "pls-1", "conn-2", gomock.Any()).Return(nil)

	endpoint := &Endpoint{
		Context: &task.Context{
			VPC: &v1alpha1.VPC{
				Spec: v1alpha1.VPCSpec{
					AccountRef: &v1alpha1.AccountReference{ProviderConfigName: "sub-1"},
				},
				Status: v1alpha1.VPCStatus{
					ResourceGroup: v1alpha1.ResourceGroup{ID: "rg-1"},
				},
			},
			PrivateLinkService: &v1alpha1.PrivateLinkService{
				Status: v1alpha1.PrivateLinkServiceStatus{
					AzurePrivateLinkService: v1alpha1.AzurePrivateLinkServiceStatus{Name: "pls-1"},
				},
			},
		},
		azureCli: azureCli,
	}

	err := endpoint.updateConnections(context.Background(), []task.VpcEndpointConnection{
		{ConnectionID: "conn-1"},
		{ConnectionID: "conn-2"},
	}, privateLinkServiceConnectionApproved)
	assert.NoError(t, err)
}

func TestEndpoint_listConnections(t *testing.T) {
	ctl := gomock.NewController(t)
	azureCli := azure.NewMockClient(ctl)
	plsCli := azure.NewMockPrivateLinkServicesAPI(ctl)

	expected := []*armnetwork.PrivateEndpointConnection{
		{Name: ptr.To("conn-1")},
	}
	azureCli.EXPECT().PrivateLinkServices("sub-1").Return(plsCli, nil)
	plsCli.EXPECT().ListPrivateEndpointConnections(gomock.Any(), "rg-1", "pls-1").Return(expected, nil)

	endpoint := &Endpoint{
		Context: &task.Context{
			VPC: &v1alpha1.VPC{
				Spec: v1alpha1.VPCSpec{
					AccountRef: &v1alpha1.AccountReference{ProviderConfigName: "sub-1"},
				},
				Status: v1alpha1.VPCStatus{
					ResourceGroup: v1alpha1.ResourceGroup{ID: "rg-1"},
				},
			},
			PrivateLinkService: &v1alpha1.PrivateLinkService{
				Status: v1alpha1.PrivateLinkServiceStatus{
					AzurePrivateLinkService: v1alpha1.AzurePrivateLinkServiceStatus{Name: "pls-1"},
				},
			},
		},
		azureCli: azureCli,
	}

	conns, err := endpoint.listConnections(context.Background())
	assert.NoError(t, err)
	assert.Equal(t, expected, conns)
}

func TestEndpoint_listConnectionsNotFound(t *testing.T) {
	ctl := gomock.NewController(t)
	azureCli := azure.NewMockClient(ctl)
	plsCli := azure.NewMockPrivateLinkServicesAPI(ctl)

	notFoundErr := &azcore.ResponseError{StatusCode: http.StatusNotFound}
	azureCli.EXPECT().PrivateLinkServices("sub-1").Return(plsCli, nil)
	plsCli.EXPECT().ListPrivateEndpointConnections(gomock.Any(), "rg-1", "pls-1").Return(nil, notFoundErr)

	endpoint := &Endpoint{
		Context: &task.Context{
			VPC: &v1alpha1.VPC{
				Spec: v1alpha1.VPCSpec{
					AccountRef: &v1alpha1.AccountReference{ProviderConfigName: "sub-1"},
				},
				Status: v1alpha1.VPCStatus{
					ResourceGroup: v1alpha1.ResourceGroup{ID: "rg-1"},
				},
			},
			PrivateLinkService: &v1alpha1.PrivateLinkService{
				Status: v1alpha1.PrivateLinkServiceStatus{
					AzurePrivateLinkService: v1alpha1.AzurePrivateLinkServiceStatus{Name: "pls-1"},
				},
			},
		},
		azureCli: azureCli,
	}

	conns, err := endpoint.listConnections(context.Background())
	assert.NoError(t, err)
	assert.Empty(t, conns)
}

func TestEndpoint_DeleteVPCEndpoints(t *testing.T) {
	ctl := gomock.NewController(t)
	azureCli := azure.NewMockClient(ctl)
	plsCli := azure.NewMockPrivateLinkServicesAPI(ctl)

	azureCli.EXPECT().PrivateLinkServices("sub-1").Return(plsCli, nil)
	plsCli.EXPECT().DeletePrivateEndpointConnection(gomock.Any(), "rg-1", "pls-1", "conn-1").Return(nil)
	plsCli.EXPECT().DeletePrivateEndpointConnection(gomock.Any(), "rg-1", "pls-1", "conn-2").Return(nil)

	endpoint := &Endpoint{
		Context: &task.Context{
			VPC: &v1alpha1.VPC{
				Spec: v1alpha1.VPCSpec{
					AccountRef: &v1alpha1.AccountReference{ProviderConfigName: "sub-1"},
				},
				Status: v1alpha1.VPCStatus{
					ResourceGroup: v1alpha1.ResourceGroup{ID: "rg-1"},
				},
			},
			PrivateLinkService: &v1alpha1.PrivateLinkService{
				Status: v1alpha1.PrivateLinkServiceStatus{
					AzurePrivateLinkService: v1alpha1.AzurePrivateLinkServiceStatus{Name: "pls-1"},
				},
			},
		},
		azureCli: azureCli,
	}

	err := endpoint.DeleteVPCEndpoints(context.Background(), []task.VpcEndpointConnection{
		{ConnectionID: "conn-1"},
		{ConnectionID: "conn-2"},
	})
	assert.NoError(t, err)
}

func TestEndpoint_DeleteVPCEndpointsContinuesAfterError(t *testing.T) {
	ctl := gomock.NewController(t)
	azureCli := azure.NewMockClient(ctl)
	plsCli := azure.NewMockPrivateLinkServicesAPI(ctl)

	firstErr := errors.New("delete conn-1 failed")
	azureCli.EXPECT().PrivateLinkServices("sub-1").Return(plsCli, nil)
	plsCli.EXPECT().DeletePrivateEndpointConnection(gomock.Any(), "rg-1", "pls-1", "conn-1").Return(firstErr)
	plsCli.EXPECT().DeletePrivateEndpointConnection(gomock.Any(), "rg-1", "pls-1", "conn-2").Return(nil)

	endpoint := &Endpoint{
		Context: &task.Context{
			VPC: &v1alpha1.VPC{
				Spec: v1alpha1.VPCSpec{
					AccountRef: &v1alpha1.AccountReference{ProviderConfigName: "sub-1"},
				},
				Status: v1alpha1.VPCStatus{
					ResourceGroup: v1alpha1.ResourceGroup{ID: "rg-1"},
				},
			},
			PrivateLinkService: &v1alpha1.PrivateLinkService{
				Status: v1alpha1.PrivateLinkServiceStatus{
					AzurePrivateLinkService: v1alpha1.AzurePrivateLinkServiceStatus{Name: "pls-1"},
				},
			},
		},
		azureCli: azureCli,
	}

	err := endpoint.DeleteVPCEndpoints(context.Background(), []task.VpcEndpointConnection{
		{ConnectionID: "conn-1"},
		{ConnectionID: "conn-2"},
	})
	assert.ErrorContains(t, err, "delete private endpoint connection conn-1")
	assert.ErrorContains(t, err, "delete conn-1 failed")
}
