package privateendpoints

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

	"cloud.google.com/go/compute/apiv1/computepb"
	azerrors "github.com/Azure/azure-sdk-for-go-extensions/pkg/errors"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6"
	ec2api "github.com/aws/aws-sdk-go-v2/service/ec2"
	ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
	. "github.com/onsi/gomega"
	observabilitypingcapcomv1 "github.com/tidbcloud/observability/api/regionalserver/resourceworker/v1"
	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/metrics"
	"github.com/tidbcloud/observability/pkg/common/structs"
	"github.com/tidbcloud/observability/pkg/common/utils"
	"github.com/tidbcloud/observability/pkg/resourceworker/controllers/fake"
	mock_manager "github.com/tidbcloud/observability/pkg/resourceworker/controllers/fake/manager"
	"github.com/tidbcloud/observability/pkg/resourceworker/controllers/reconciler"
	"github.com/tidbcloud/observability/pkg/resourceworker/providers"
	mocks "github.com/tidbcloud/observability/test/mockresourceworker/providers"
	"go.uber.org/mock/gomock"
	"k8s.io/apimachinery/pkg/runtime"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	ctrlconfig "sigs.k8s.io/controller-runtime/pkg/config"
)

var errBoom = errors.New("boom")

type args struct {
	cli      func(g Gomega) client.Client
	pecli    func(ctrl *gomock.Controller, g Gomega) providers.PrivateEndpointClient
	gcpcli   func(ctrl *gomock.Controller, g Gomega) providers.PSCClient
	azurecli func(ctrl *gomock.Controller, g Gomega) providers.PLSEndpointClient
	resource func() client.Object
	vendor   string
}

type want struct {
	result  interface{}
	err     error
	paniced bool
}
type caseType map[string]struct {
	args args
	want want
}

func TestEPControllerBuildStruct(t *testing.T) {
	g := NewGomegaWithT(t)
	ctl := gomock.NewController(t)
	defer ctl.Finish()
	manager := mock_manager.NewMockManager(ctl)
	mockCli := &fake.MockClient{
		MockGet: fake.MockGetFn(func(ctx context.Context, key client.ObjectKey, obj runtime.Object) error {
			return errBoom
		}),
	}
	scheme := runtime.NewScheme()
	log := ctrl.Log.WithName("controllers").WithName("PrivateEndpoint")
	region := "testregion"
	serviceinfo := structs.ServiceInfo{
		Region: region,
	}
	manager.EXPECT().GetClient().AnyTimes().Return(mockCli)
	eps := NewPrivateEndpointReconciler(manager, log, serviceinfo)
	g.Expect(eps.Log).Should(Equal(log))
	g.Expect(eps.reconciler.Client).Should(Equal(mockCli))
	utilruntime.Must(observabilitypingcapcomv1.AddToScheme(scheme))
	// manager.EXPECT().SetFields(gomock.Any()).AnyTimes().Return(nil)
	manager.EXPECT().Add(gomock.Any()).AnyTimes().Return(nil)
	manager.EXPECT().GetControllerOptions().Return(ctrlconfig.Controller{}).AnyTimes()
	manager.EXPECT().GetScheme().Return(scheme)
	manager.EXPECT().GetCache().Return(nil)
	manager.EXPECT().GetLogger().Return(log)
	g.Expect(eps.SetupWithManager(manager)).Should(Succeed())
	g.Expect(eps.Reconcile(context.TODO(), ctrl.Request{})).Error().Should(HaveOccurred())
}

func TestEPControllerConnect(t *testing.T) {
	os.Setenv("AWS_ACCESS_KEY_ID", "test1")
	os.Setenv("AWS_SECRET_ACCESS_KEY", "testsec")
	os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "../../../../test/files/application_default_credentials.json")
	defer os.Unsetenv("GOOGLE_APPLICATION_CREDENTIALS")
	defer os.Unsetenv("AWS_SECRET_ACCESS_KEY")
	defer os.Unsetenv("AWS_ACCESS_KEY_ID")
	g := NewGomegaWithT(t)
	var connect = privateLinkEndpointConnector{
		serviceInfo: structs.ServiceInfo{
			Vendor: string(constants.AWS),
		},
	}
	g.Expect(connect.Connect(context.TODO(), &observabilitypingcapcomv1.PrivateEndpoint{})).Error().Should(Succeed())
	connect.serviceInfo.Vendor = string(constants.GCP)
	g.Expect(connect.Connect(context.TODO(), &observabilitypingcapcomv1.PrivateEndpoint{})).Error().Should(Succeed())
	connect.serviceInfo.Vendor = string(constants.Azure)
	g.Expect(connect.Connect(context.TODO(), &observabilitypingcapcomv1.PrivateEndpoint{})).Error().Should(HaveOccurred())
	g.Expect(connect.Connect(context.TODO(), &observabilitypingcapcomv1.PrivateEndpoint{
		Spec: observabilitypingcapcomv1.PrivateEndpointSpec{
			Azure: &observabilitypingcapcomv1.AzureSpec{
				SubscriptionID: "fake",
				ResourceGroup:  "fake",
			},
		},
	})).Error().Should(Succeed())

	connect.serviceInfo.Vendor = "unknown"
	g.Expect(connect.Connect(context.TODO(), &observabilitypingcapcomv1.PrivateEndpoint{})).Error().Should(HaveOccurred())
}

func TestEPControllerObserve(t *testing.T) {
	metrics.SetupMetrics()
	cases := caseType{
		"ObserveErrorResourceType": {
			args: args{
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					return mocks.NewMockPrivateEndpointClient(ctl)
				},
				resource: func() client.Object {
					return &observabilitypingcapcomv1.S3{}
				},
			},
			want: want{
				result: reconciler.ExternalObservation{Droped: true},
				err:    errors.New(errNotPrivateEndpoint),
			},
		},
		"ObserveFinalStatus": {
			args: args{
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					return mocks.NewMockPrivateEndpointClient(ctl)
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Status.Phase = constants.CREATION_FAILED
					return res
				},
			},
			want: want{
				result: reconciler.ExternalObservation{ResourceUpToDate: true, ResourceExists: true},
			},
		},
		"ObserveSuccess": {
			args: args{
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockGet: fake.MockGetFn(func(ctx context.Context, key client.ObjectKey, obj runtime.Object) error {
							es := obj.(*observabilitypingcapcomv1.EndpointService)
							es.Status.Phase = constants.ACTIVE
							return nil
						}),
					}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					return mocks.NewMockPrivateEndpointClient(ctl)
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Name = "test1"
					res.Spec.PrivateEndpointServiceName = "test1-svc"
					res.Status.EndpointID = "test1"
					return res
				},
			},
			want: want{
				result: reconciler.ExternalObservation{ResourceExists: true},
			},
		},
		"ObserveActive": {
			args: args{
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockGet: fake.MockGetFn(func(ctx context.Context, key client.ObjectKey, obj runtime.Object) error {
							es := obj.(*observabilitypingcapcomv1.EndpointService)
							es.Status.Phase = constants.ACTIVE
							return nil
						}),
					}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					return mocks.NewMockPrivateEndpointClient(ctl)
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Name = "test1"
					res.Spec.PrivateEndpointServiceName = "test1-svc"
					res.Status.EndpointID = "test1"
					res.Status.Phase = constants.ACTIVE
					return res
				},
			},
			want: want{
				result: reconciler.ExternalObservation{ResourceUpToDate: true, ResourceExists: true},
			},
		},
	}
	for name, tc := range cases {
		t.Run(name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			ctl := gomock.NewController(t)
			defer ctl.Finish()
			r := PrivateEndpoint{
				cli:    tc.args.cli(g),
				awscli: tc.args.pecli(ctl, g),
			}
			if tc.want.paniced {
				g.Expect(func() { r.Observe(context.Background(), tc.args.resource()) }).Should(PanicWith(errNotPrivateEndpoint))
			} else {
				got, err := r.Observe(context.Background(), tc.args.resource())
				if tc.want.err == nil {
					g.Expect(err).Should(BeNil())
				} else {
					g.Expect(err).Should(Equal(tc.want.err))
				}
				g.Expect(got).Should(Equal(tc.want.result))
			}

		})
	}
}

func TestEPControllerCreate(t *testing.T) {
	metrics.SetupMetrics()
	errBoom := errors.New("boom")
	cases := caseType{
		"CreateErrorResourceType": {
			args: args{
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					return mocks.NewMockPrivateEndpointClient(ctl)
				},
				gcpcli: func(ctl *gomock.Controller, g Gomega) providers.PSCClient {
					return mocks.NewMockPSCClient(ctl)
				},
				resource: func() client.Object {
					return &observabilitypingcapcomv1.S3{}
				},
			},
			want: want{
				err: errors.New(errNotPrivateEndpoint),
			},
		},

		"CreateWaitReadyNoEndpointService": {
			args: args{
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							eps := obj.(*observabilitypingcapcomv1.PrivateEndpoint)
							g.Expect(eps.Status.Phase).Should(Equal(constants.CREATING))
							return errBoom
						}),
					}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					return mocks.NewMockPrivateEndpointClient(ctl)
				},
				gcpcli: func(ctl *gomock.Controller, g Gomega) providers.PSCClient {
					return mocks.NewMockPSCClient(ctl)
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Name = "test1"
					res.Spec.PrivateEndpointServiceName = ""
					res.Status.EndpointID = ""
					return res
				},
			},
			want: want{
				err: errBoom,
			},
		},
		"CreateWaitError": {
			args: args{
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockGet: fake.MockGetFn(func(ctx context.Context, key client.ObjectKey, obj runtime.Object) error {
							if _, ok := obj.(*observabilitypingcapcomv1.EndpointService); ok {
								return errBoom
							}
							return nil
						}),
					}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					return mocks.NewMockPrivateEndpointClient(ctl)
				},
				gcpcli: func(ctl *gomock.Controller, g Gomega) providers.PSCClient {
					return mocks.NewMockPSCClient(ctl)
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Name = "test1"
					res.Spec.PrivateEndpointServiceName = "test1-svc"
					return res
				},
			},
			want: want{
				err: errBoom,
			},
		},
		"CreateWaitNotReady": {
			args: args{
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockGet: fake.MockGetFn(func(ctx context.Context, key client.ObjectKey, obj runtime.Object) error {
							es := obj.(*observabilitypingcapcomv1.EndpointService)
							es.Status.Phase = constants.CREATING
							return nil
						}),
					}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					return mocks.NewMockPrivateEndpointClient(ctl)
				},
				gcpcli: func(ctl *gomock.Controller, g Gomega) providers.PSCClient {
					return mocks.NewMockPSCClient(ctl)
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Name = "test1"
					res.Spec.PrivateEndpointServiceName = "test1-svc"
					return res
				},
			},
			want: want{
				err: errors.New("private endpoint service not ready"),
			},
		},
		"CreateAWSDescribeFailed": {
			args: args{
				vendor: string(constants.AWS),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							eps := obj.(*observabilitypingcapcomv1.PrivateEndpoint)
							g.Expect(eps.Status.Phase).Should(Equal(constants.CREATING))
							return nil
						}),
					}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					cli := mocks.NewMockPrivateEndpointClient(ctl)
					cli.EXPECT().DescribeVpcEndpointsByName(gomock.Any(), gomock.Any()).Return(nil, errBoom)
					return cli
				},
				gcpcli: func(ctl *gomock.Controller, g Gomega) providers.PSCClient {
					return mocks.NewMockPSCClient(ctl)
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Spec.AWS = &observabilitypingcapcomv1.AWSSpec{
						AccountID:      "",
						ServiceDNSName: "test1",
					}
					res.Name = "test1"
					res.Namespace = "test1"
					res.Spec.NamePerfix = "ut"
					return res
				},
			},
			want: want{
				err: errBoom,
			},
		},
		"CreateAWSEndpointExist": {
			args: args{
				vendor: string(constants.AWS),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							return nil
						}),
					}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					cli := mocks.NewMockPrivateEndpointClient(ctl)
					cli.EXPECT().DescribeVpcEndpointsByName(gomock.Any(), gomock.Any()).Return(&ec2api.DescribeVpcEndpointsOutput{
						VpcEndpoints: []ec2types.VpcEndpoint{
							{
								VpcEndpointId: utils.StringPtr("test1"),
							},
						},
					}, nil)
					return cli
				},
				gcpcli: func(ctl *gomock.Controller, g Gomega) providers.PSCClient {
					return mocks.NewMockPSCClient(ctl)
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Spec.AWS = &observabilitypingcapcomv1.AWSSpec{
						AccountID:      "",
						ServiceDNSName: "test1",
					}
					res.Name = "test1"
					res.Namespace = "test1"
					res.Spec.NamePerfix = "ut"
					return res
				},
			},
			want: want{
				err: nil,
			},
		},
		"CreateAWSEndpointAssumeRoleFailed": {
			args: args{
				vendor: string(constants.AWS),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							return nil
						}),
					}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					cli := mocks.NewMockPrivateEndpointClient(ctl)
					return cli
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Spec.AWS = &observabilitypingcapcomv1.AWSSpec{
						AccountID:      "test1",
						ServiceDNSName: "test1",
					}
					res.Name = "test1"
					res.Namespace = "test1"
					res.Spec.NamePerfix = "ut"
					return res
				},
			},
			want: want{
				err: fmt.Errorf("not supported biz type %s", ""),
			},
		},
		"CreateAWSEndpointSuccess": {
			args: args{
				vendor: string(constants.AWS),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							return nil
						}),
					}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					cli := mocks.NewMockPrivateEndpointClient(ctl)
					cli.EXPECT().DescribeVpcEndpointsByName(gomock.Any(), gomock.Any()).Return(&ec2api.DescribeVpcEndpointsOutput{}, nil)
					cli.EXPECT().DescribeVpcEndpointServices(gomock.Any(), gomock.Any()).Return(&ec2api.DescribeVpcEndpointServicesOutput{
						ServiceDetails: []ec2types.ServiceDetail{
							{
								AvailabilityZones: []string{"us-west-2-a"},
							},
						},
					}, nil)
					cli.EXPECT().DescribeSubnets(gomock.Any(), gomock.Any()).Return(&ec2api.DescribeSubnetsOutput{
						Subnets: []ec2types.Subnet{
							{
								SubnetId:         utils.StringPtr("test1"),
								AvailabilityZone: utils.StringPtr("us-west-2-a"),
							},
						},
					}, nil)
					cli.EXPECT().CreateVpcEndpoint(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&ec2api.CreateVpcEndpointOutput{
						VpcEndpoint: &ec2types.VpcEndpoint{
							VpcEndpointId: utils.StringPtr("test1"),
						},
					}, nil)
					return cli
				},
				gcpcli: func(ctl *gomock.Controller, g Gomega) providers.PSCClient {
					return mocks.NewMockPSCClient(ctl)
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Spec.AWS = &observabilitypingcapcomv1.AWSSpec{
						AccountID:      "",
						ServiceDNSName: "test1",
						SubnetIDs:      []string{"test1"},
					}
					res.Name = "test1"
					res.Namespace = "test1"
					res.Spec.NamePerfix = "ut"
					return res
				},
			},
			want: want{
				err: nil,
			},
		},
		"CreateGCPEndpointExist": {
			args: args{
				vendor: string(constants.GCP),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							return nil
						}),
					}
				},
				gcpcli: func(ctl *gomock.Controller, g Gomega) providers.PSCClient {
					cli := mocks.NewMockPSCClient(ctl)
					cli.EXPECT().CheckNamespaceNetworkAssociation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(false, nil)
					cli.EXPECT().GetPublishedServicePscEndpoint(gomock.Any(), gomock.Any(), gomock.Any()).Return(&computepb.ForwardingRule{
						Name: utils.StringPtr("test1"),
					}, nil)
					return cli
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Spec.GCP = &observabilitypingcapcomv1.GCPSpec{
						ProjectID:      "",
						ServiceDNSName: "test1",
						ServiceName:    "test1",
						Region:         "test1",
						Subnet:         "test1",
						Network:        "test1",
					}
					res.Name = "test1"
					res.Namespace = "test1"
					res.Spec.NamePerfix = "ut"
					return res
				},
			},
			want: want{
				err: nil,
			},
		},
		"CreateGCPEndpointSuccess": {
			args: args{
				vendor: string(constants.GCP),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							return nil
						}),
					}
				},
				gcpcli: func(ctl *gomock.Controller, g Gomega) providers.PSCClient {
					cli := mocks.NewMockPSCClient(ctl)
					cli.EXPECT().CheckNamespaceNetworkAssociation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(false, nil)
					cli.EXPECT().GetPublishedServicePscEndpoint(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil)
					cli.EXPECT().CreatePublishedServicePscEndpoint(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
					return cli
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Spec.GCP = &observabilitypingcapcomv1.GCPSpec{
						ProjectID:      "",
						ServiceDNSName: "test1",
						ServiceName:    "test1",
						Region:         "test1",
						Subnet:         "test1",
						Network:        "test1",
					}
					res.Name = "test1"
					res.Namespace = "test1"
					res.Spec.NamePerfix = "ut"
					return res
				},
			},
			want: want{
				err: nil,
			},
		},
		"CreateAzureEndpointGetEPError": {
			args: args{
				vendor: string(constants.Azure),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							return nil
						}),
					}
				},
				azurecli: func(ctrl *gomock.Controller, g Gomega) providers.PLSEndpointClient {
					cli := mocks.NewMockPLSEndpointClient(ctrl)
					cli.EXPECT().Get(gomock.Any(), gomock.Any()).Return(nil, errBoom)
					return cli
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Spec.Azure = &observabilitypingcapcomv1.AzureSpec{
						ResourceGroup:  "test1",
						SubscriptionID: "test1",
						ServiceName:    "test1",
					}
					res.Name = "test1"
					res.Namespace = "test1"
					res.Spec.NamePerfix = "ut"
					return res
				},
			},
			want: want{
				err: errBoom,
			},
		},

		"CreateAzureEndpointGetEPPrivateIPError": {
			args: args{
				vendor: string(constants.Azure),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							return nil
						}),
					}
				},
				azurecli: func(ctrl *gomock.Controller, g Gomega) providers.PLSEndpointClient {
					cli := mocks.NewMockPLSEndpointClient(ctrl)
					cli.EXPECT().Get(gomock.Any(), gomock.Any()).Return(&armnetwork.PrivateEndpoint{
						Name: to.Ptr("test1"),
					}, nil)
					cli.EXPECT().GetPrivateIP(gomock.Any(), gomock.Any()).Return(nil, errBoom)
					return cli
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Spec.Azure = &observabilitypingcapcomv1.AzureSpec{
						ResourceGroup:  "test1",
						SubscriptionID: "test1",
						ServiceName:    "test1",
					}
					res.Name = "test1"
					res.Namespace = "test1"
					res.Spec.NamePerfix = "ut"
					return res
				},
			},
			want: want{
				err: errBoom,
			},
		},
		"CreateAzureEndpointExist": {
			args: args{
				vendor: string(constants.Azure),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							return nil
						}),
					}
				},
				azurecli: func(ctrl *gomock.Controller, g Gomega) providers.PLSEndpointClient {
					cli := mocks.NewMockPLSEndpointClient(ctrl)
					cli.EXPECT().Get(gomock.Any(), gomock.Any()).Return(&armnetwork.PrivateEndpoint{
						Name: to.Ptr("test1"),
					}, nil)

					cli.EXPECT().GetPrivateIP(gomock.Any(), gomock.Any()).Return(to.Ptr("1.2.3.4"), nil)
					return cli
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Spec.Azure = &observabilitypingcapcomv1.AzureSpec{
						ResourceGroup:  "test1",
						SubscriptionID: "test1",
						ServiceName:    "test1",
					}
					res.Name = "test1"
					res.Namespace = "test1"
					res.Spec.NamePerfix = "ut"
					return res
				},
			},
			want: want{
				err: nil,
			},
		},
		"CreateAzureEndpointError": {
			args: args{
				vendor: string(constants.Azure),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							return nil
						}),
					}
				},
				azurecli: func(ctrl *gomock.Controller, g Gomega) providers.PLSEndpointClient {
					cli := mocks.NewMockPLSEndpointClient(ctrl)
					cli.EXPECT().Get(gomock.Any(), gomock.Any()).Return(nil, &azcore.ResponseError{
						ErrorCode:  azerrors.ResourceNotFound,
						StatusCode: http.StatusNotFound,
					})
					cli.EXPECT().Create(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errBoom)
					return cli
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Spec.Azure = &observabilitypingcapcomv1.AzureSpec{
						ResourceGroup:  "test1",
						SubscriptionID: "test1",
						ServiceName:    "test1",
					}
					res.Name = "test1"
					res.Namespace = "test1"
					res.Spec.NamePerfix = "ut"
					return res
				},
			},
			want: want{
				err: errBoom,
			},
		},
		"CreateAzureEndpointSuccess": {
			args: args{
				vendor: string(constants.Azure),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							return nil
						}),
					}
				},
				azurecli: func(ctrl *gomock.Controller, g Gomega) providers.PLSEndpointClient {
					cli := mocks.NewMockPLSEndpointClient(ctrl)
					cli.EXPECT().Get(gomock.Any(), gomock.Any()).Return(nil, &azcore.ResponseError{
						ErrorCode:  azerrors.ResourceNotFound,
						StatusCode: http.StatusNotFound,
					})
					cli.EXPECT().Create(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil)
					cli.EXPECT().GetPrivateIP(gomock.Any(), gomock.Any()).Return(to.Ptr("1.2.3.4"), nil)
					return cli
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Spec.Azure = &observabilitypingcapcomv1.AzureSpec{
						ResourceGroup:  "test1",
						SubscriptionID: "test1",
						ServiceName:    "test1",
					}
					res.Name = "test1"
					res.Namespace = "test1"
					res.Spec.NamePerfix = "ut"
					return res
				},
			},
			want: want{
				err: nil,
			},
		},
	}
	for name, tc := range cases {
		t.Run(name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			ctl := gomock.NewController(t)
			defer ctl.Finish()
			r := PrivateEndpoint{
				cli:         tc.args.cli(g),
				serviceInfo: structs.ServiceInfo{Vendor: tc.args.vendor},
			}
			if tc.args.gcpcli != nil {
				r.gcpcli = tc.args.gcpcli(ctl, g)
			}
			if tc.args.pecli != nil {
				r.awscli = tc.args.pecli(ctl, g)
			}
			if tc.args.azurecli != nil {
				r.azurecli = tc.args.azurecli(ctl, g)
			}

			if tc.want.paniced {
				g.Expect(func() { r.Create(context.Background(), tc.args.resource()) }).Should(PanicWith(errNotPrivateEndpoint))
			} else {
				err := r.Create(context.Background(), tc.args.resource())
				if tc.want.err == nil {
					g.Expect(err).Should(BeNil())
				} else {
					g.Expect(errors.Is(err, tc.want.err) || err.Error() == tc.want.err.Error()).Should(BeTrue())
				}
			}

		})
	}
}

func TestEPControllerUpdate(t *testing.T) {
	metrics.SetupMetrics()
	cases := caseType{
		"UpdateErrorResourceType": {
			args: args{
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					return mocks.NewMockPrivateEndpointClient(ctl)
				},
				gcpcli: func(ctl *gomock.Controller, g Gomega) providers.PSCClient {
					cli := mocks.NewMockPSCClient(ctl)
					return cli
				},
				resource: func() client.Object {
					return &observabilitypingcapcomv1.S3{}
				},
			},
			want: want{
				err: errors.New(errNotPrivateEndpoint),
			},
		},
		"UpdateSuccess": {
			args: args{
				vendor: string(constants.AWS),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							eps := obj.(*observabilitypingcapcomv1.PrivateEndpoint)
							g.Expect(eps.Status.Phase).Should(Equal(constants.ACTIVE))
							return nil
						}),
					}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					return mocks.NewMockPrivateEndpointClient(ctl)
				},
				gcpcli: func(ctl *gomock.Controller, g Gomega) providers.PSCClient {
					cli := mocks.NewMockPSCClient(ctl)
					return cli
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					return res
				},
			},
			want: want{
				err: nil,
			},
		},
	}
	for name, tc := range cases {
		t.Run(name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			ctl := gomock.NewController(t)
			defer ctl.Finish()
			r := PrivateEndpoint{
				awscli:      tc.args.pecli(ctl, g),
				gcpcli:      tc.args.gcpcli(ctl, g),
				cli:         tc.args.cli(g),
				serviceInfo: structs.ServiceInfo{Vendor: tc.args.vendor},
			}
			if tc.want.paniced {
				g.Expect(func() { r.Update(context.Background(), tc.args.resource()) }).Should(PanicWith(errNotPrivateEndpoint))
			} else {
				err := r.Update(context.Background(), tc.args.resource())
				if tc.want.err == nil {
					g.Expect(err).Should(BeNil())
				} else {
					g.Expect(err).Should(Equal(tc.want.err))
				}
			}

		})
	}
}

func TestEPSControllerDelete(t *testing.T) {
	metrics.SetupMetrics()
	cases := caseType{
		"DeleteErrorResourceType": {
			args: args{
				vendor: string(constants.AWS),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					return mocks.NewMockPrivateEndpointClient(ctl)
				},
				gcpcli: func(ctl *gomock.Controller, g Gomega) providers.PSCClient {
					cli := mocks.NewMockPSCClient(ctl)
					return cli
				},
				resource: func() client.Object {
					return &observabilitypingcapcomv1.S3{}
				},
			},
			want: want{
				result: reconciler.ExternalDeletion{},
				err:    errors.New(errNotPrivateEndpoint),
			},
		},
		"DeleteAWSEndpointEmpty": {
			args: args{
				vendor: string(constants.AWS),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							eps := obj.(*observabilitypingcapcomv1.PrivateEndpoint)
							g.Expect(eps.Status.Phase).Should(Equal(constants.DELETING))
							return nil
						}),
					}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					return mocks.NewMockPrivateEndpointClient(ctl)
				},
				gcpcli: func(ctl *gomock.Controller, g Gomega) providers.PSCClient {
					cli := mocks.NewMockPSCClient(ctl)
					return cli
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Spec.AWS = &observabilitypingcapcomv1.AWSSpec{
						AccountID: "",
					}
					res.Status.EndpointID = ""
					res.Name = "test1"
					return res
				},
			},
			want: want{
				result: reconciler.ExternalDeletion{ResourceDeleted: true},
				err:    nil,
			},
		},
		"DeleteAWSEndpointAssumeRoleFailed": {
			args: args{
				vendor: string(constants.AWS),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							eps := obj.(*observabilitypingcapcomv1.PrivateEndpoint)
							g.Expect(eps.Status.Phase).Should(Equal(constants.DELETING))
							return nil
						}),
					}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					return mocks.NewMockPrivateEndpointClient(ctl)
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Spec.AWS = &observabilitypingcapcomv1.AWSSpec{
						AccountID: "test1",
					}
					res.Status.EndpointID = ""
					res.Name = "test1"
					return res
				},
			},
			want: want{
				result: reconciler.ExternalDeletion{},
				err:    fmt.Errorf("not supported biz type %s", ""),
			},
		},
		"DeleteAWSEndpointSuccess": {
			args: args{
				vendor: string(constants.AWS),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							eps := obj.(*observabilitypingcapcomv1.PrivateEndpoint)
							g.Expect(eps.Status.Phase).Should(Equal(constants.DELETING))
							return nil
						}),
					}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					cli := mocks.NewMockPrivateEndpointClient(ctl)
					cli.EXPECT().DeleteVpcEndpoints(gomock.Any(), gomock.Any()).Return(&ec2api.DeleteVpcEndpointsOutput{}, nil)
					return cli
				},
				gcpcli: func(ctl *gomock.Controller, g Gomega) providers.PSCClient {
					cli := mocks.NewMockPSCClient(ctl)
					return cli
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Spec.AWS = &observabilitypingcapcomv1.AWSSpec{
						AccountID: "",
					}
					res.Status.EndpointID = "test1"
					res.Name = "test1"
					return res
				},
			},
			want: want{
				result: reconciler.ExternalDeletion{ResourceDeleted: true},
				err:    nil,
			},
		},
		"DeleteAWSEndpointFromAWSSuccess": {
			args: args{
				vendor: string(constants.AWS),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							eps := obj.(*observabilitypingcapcomv1.PrivateEndpoint)
							g.Expect(eps.Status.Phase).Should(Equal(constants.DELETING))
							return nil
						}),
					}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					cli := mocks.NewMockPrivateEndpointClient(ctl)
					cli.EXPECT().DescribeVpcEndpointsByName(gomock.Any(), gomock.Any()).Return(&ec2api.DescribeVpcEndpointsOutput{
						VpcEndpoints: []ec2types.VpcEndpoint{
							{
								VpcEndpointId: utils.StringPtr("test1"),
							},
						},
					}, nil)
					cli.EXPECT().DeleteVpcEndpoints(gomock.Any(), gomock.Any()).Return(&ec2api.DeleteVpcEndpointsOutput{}, nil)
					return cli
				},
				gcpcli: func(ctl *gomock.Controller, g Gomega) providers.PSCClient {
					cli := mocks.NewMockPSCClient(ctl)
					return cli
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Spec.AWS = &observabilitypingcapcomv1.AWSSpec{
						AccountID: "",
					}
					res.Status.EndpointID = ""
					res.Status.Phase = constants.ACTIVE
					res.Name = "test1"
					return res
				},
			},
			want: want{
				result: reconciler.ExternalDeletion{ResourceDeleted: true},
				err:    nil,
			},
		},
		"DeleteGCPEndpointEmpty": {
			args: args{
				vendor: string(constants.GCP),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							eps := obj.(*observabilitypingcapcomv1.PrivateEndpoint)
							g.Expect(eps.Status.Phase).Should(Equal(constants.DELETING))
							return nil
						}),
					}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					return mocks.NewMockPrivateEndpointClient(ctl)
				},
				gcpcli: func(ctl *gomock.Controller, g Gomega) providers.PSCClient {
					cli := mocks.NewMockPSCClient(ctl)
					return cli
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Spec.GCP = &observabilitypingcapcomv1.GCPSpec{
						ProjectID: "",
					}
					res.Status.EndpointID = ""
					res.Name = "test1"
					return res
				},
			},
			want: want{
				result: reconciler.ExternalDeletion{ResourceDeleted: true},
				err:    nil,
			},
		},
		"DeleteGCPEndpointSuccess": {
			args: args{
				vendor: string(constants.GCP),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							eps := obj.(*observabilitypingcapcomv1.PrivateEndpoint)
							g.Expect(eps.Status.Phase).Should(Equal(constants.DELETING))
							return nil
						}),
					}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					cli := mocks.NewMockPrivateEndpointClient(ctl)
					return cli
				},
				gcpcli: func(ctl *gomock.Controller, g Gomega) providers.PSCClient {
					cli := mocks.NewMockPSCClient(ctl)
					cli.EXPECT().DeletePublishedServicePscEndpoint(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
					return cli
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Spec.GCP = &observabilitypingcapcomv1.GCPSpec{
						ProjectID: "",
					}
					res.Status.EndpointID = "test1"
					res.Name = "test1"
					return res
				},
			},
			want: want{
				result: reconciler.ExternalDeletion{ResourceDeleted: true},
				err:    nil,
			},
		},
		"DeleteGCPEndpointFromGCPSuccess": {
			args: args{
				vendor: string(constants.GCP),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							eps := obj.(*observabilitypingcapcomv1.PrivateEndpoint)
							g.Expect(eps.Status.Phase).Should(Equal(constants.DELETING))
							return nil
						}),
					}
				},
				pecli: func(ctl *gomock.Controller, g Gomega) providers.PrivateEndpointClient {
					cli := mocks.NewMockPrivateEndpointClient(ctl)
					return cli
				},
				gcpcli: func(ctl *gomock.Controller, g Gomega) providers.PSCClient {
					cli := mocks.NewMockPSCClient(ctl)
					cli.EXPECT().GetPublishedServicePscEndpoint(gomock.Any(), gomock.Any(), gomock.Any()).Return(&computepb.ForwardingRule{}, nil)
					cli.EXPECT().DeletePublishedServicePscEndpoint(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
					return cli
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Spec.GCP = &observabilitypingcapcomv1.GCPSpec{
						ProjectID: "",
					}
					res.Status.EndpointID = ""
					res.Status.Phase = constants.ACTIVE
					res.Name = "test1"
					return res
				},
			},
			want: want{
				result: reconciler.ExternalDeletion{ResourceDeleted: true},
				err:    nil,
			},
		},
		"DeleteAzureEndpointEmpty": {
			args: args{
				vendor: string(constants.Azure),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							eps := obj.(*observabilitypingcapcomv1.PrivateEndpoint)
							g.Expect(eps.Status.Phase).Should(Equal(constants.DELETING))
							return nil
						}),
					}
				},
				azurecli: func(ctrl *gomock.Controller, g Gomega) providers.PLSEndpointClient {
					return mocks.NewMockPLSEndpointClient(ctrl)
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Status.EndpointID = ""
					res.Name = "test1"
					return res
				},
			},
			want: want{
				result: reconciler.ExternalDeletion{ResourceDeleted: true},
				err:    nil,
			},
		},
		"DeleteAzureEndpointSuccess": {
			args: args{
				vendor: string(constants.Azure),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							eps := obj.(*observabilitypingcapcomv1.PrivateEndpoint)
							g.Expect(eps.Status.Phase).Should(Equal(constants.DELETING))
							return nil
						}),
					}
				},
				azurecli: func(ctrl *gomock.Controller, g Gomega) providers.PLSEndpointClient {
					cli := mocks.NewMockPLSEndpointClient(ctrl)
					cli.EXPECT().Delete(gomock.Any(), gomock.Any()).Return(nil)
					return cli
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Status.EndpointID = "test1"
					res.Name = "test1"
					return res
				},
			},
			want: want{
				result: reconciler.ExternalDeletion{ResourceDeleted: true},
				err:    nil,
			},
		},
		"DeleteAzureEndpointError": {
			args: args{
				vendor: string(constants.Azure),
				cli: func(g Gomega) client.Client {
					return &fake.MockClient{
						MockStatusUpdate: fake.MockStatusUpdateFn(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
							eps := obj.(*observabilitypingcapcomv1.PrivateEndpoint)
							g.Expect(eps.Status.Phase).Should(Equal(constants.DELETING))
							return nil
						}),
					}
				},
				azurecli: func(ctrl *gomock.Controller, g Gomega) providers.PLSEndpointClient {
					cli := mocks.NewMockPLSEndpointClient(ctrl)
					cli.EXPECT().Delete(gomock.Any(), gomock.Any()).Return(errBoom)
					return cli
				},
				resource: func() client.Object {
					res := &observabilitypingcapcomv1.PrivateEndpoint{}
					res.Status.EndpointID = "test1"
					res.Name = "test1"
					return res
				},
			},
			want: want{
				result: reconciler.ExternalDeletion{ResourceDeleted: false},
				err:    errBoom,
			},
		},
	}
	for name, tc := range cases {
		t.Run(name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			ctl := gomock.NewController(t)
			defer ctl.Finish()
			r := PrivateEndpoint{
				cli:         tc.args.cli(g),
				serviceInfo: structs.ServiceInfo{Vendor: tc.args.vendor},
			}
			if tc.args.pecli != nil {
				r.awscli = tc.args.pecli(ctl, g)
			}
			if tc.args.gcpcli != nil {
				r.gcpcli = tc.args.gcpcli(ctl, g)
			}
			if tc.args.azurecli != nil {
				r.azurecli = tc.args.azurecli(ctl, g)
			}

			if tc.want.paniced {
				g.Expect(func() { r.Delete(context.Background(), tc.args.resource()) }).Should(PanicWith(errNotPrivateEndpoint))
			} else {
				got, err := r.Delete(context.Background(), tc.args.resource())
				if tc.want.err == nil {
					g.Expect(err).Should(BeNil())
				} else {
					g.Expect(err).Should(Equal(tc.want.err))
				}
				g.Expect(got).Should(Equal(tc.want.result))
			}

		})
	}
}
