package azure

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

	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/arm"
	azfake "github.com/Azure/azure-sdk-for-go/sdk/azcore/fake"
	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6"
	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6/fake"
	. "github.com/onsi/gomega"
)

func TestNewPrivateEndpointClient(t *testing.T) {
	g := NewWithT(t)
	_, err := NewPrivateEndpointClient("fake", "fake", &azfake.TokenCredential{})
	g.Expect(err).To(Succeed())
}

func TestPrivateEndpointGet(t *testing.T) {
	g := NewWithT(t)
	fServer := fake.PrivateEndpointsServer{
		Get: func(ctx context.Context, resourceGroupName, privateEndpointName string, options *armnetwork.PrivateEndpointsClientGetOptions) (resp azfake.Responder[armnetwork.PrivateEndpointsClientGetResponse], errResp azfake.ErrorResponder) {
			if privateEndpointName == "error" {
				errResp.SetResponseError(http.StatusBadRequest, "bad req")
			} else if privateEndpointName != "fake" {
				errResp.SetError(&azcore.ResponseError{
					ErrorCode:  azerrors.ResourceNotFound,
					StatusCode: http.StatusNotFound,
				})
			} else {
				resp.SetResponse(http.StatusOK, armnetwork.PrivateEndpointsClientGetResponse{}, nil)
			}
			return
		},
	}
	cli, err := armnetwork.NewPrivateEndpointsClient("fake", &azfake.TokenCredential{}, &arm.ClientOptions{
		ClientOptions: azcore.ClientOptions{
			Transport: fake.NewPrivateEndpointsServerTransport(&fServer),
		},
	})
	peCli := &PrivateEndpointClient{
		cli:           cli,
		resourceGroup: "fake",
	}

	t.Run("error", func(t *testing.T) {
		g.Expect(err).To(Succeed())
		pe, err := peCli.Get(context.TODO(), "error")
		g.Expect(err).NotTo(Succeed())
		g.Expect(pe).To(BeNil())
	})

	t.Run("successfully", func(t *testing.T) {
		g.Expect(err).To(Succeed())
		pe, err := peCli.Get(context.TODO(), "fake")
		g.Expect(err).To(Succeed())
		g.Expect(pe).ToNot(BeNil())
	})
}

func TestPrivateEndpointCreate(t *testing.T) {
	g := NewWithT(t)

	f := func(name string, server *fake.PrivateEndpointsServer, expected error) {
		t.Run(name, func(t *testing.T) {
			cli, err := armnetwork.NewPrivateEndpointsClient("fake", &azfake.TokenCredential{}, &arm.ClientOptions{
				ClientOptions: azcore.ClientOptions{
					Transport: fake.NewPrivateEndpointsServerTransport(server),
				},
			})
			g.Expect(err).To(Succeed())
			pe := PrivateEndpointClient{
				cli:           cli,
				resourceGroup: "fake",
			}
			_, err = pe.Create(context.TODO(), "fake", "fake", "fake", "fake", "fake", map[string]string{
				"observability.pingcap.com/tenant": "123",
			})
			g.Expect(errors.Is(err, expected)).To(BeTrue())
		})
	}

	f("error when creating pe", &fake.PrivateEndpointsServer{
		BeginCreateOrUpdate: func(ctx context.Context, resourceGroupName, privateEndpointName string, parameters armnetwork.PrivateEndpoint, options *armnetwork.PrivateEndpointsClientBeginCreateOrUpdateOptions) (resp azfake.PollerResponder[armnetwork.PrivateEndpointsClientCreateOrUpdateResponse], errResp azfake.ErrorResponder) {
			errResp.SetError(errBoom)
			return
		},
	}, errBoom)

	f("error polling when creating pe", &fake.PrivateEndpointsServer{
		BeginCreateOrUpdate: func(ctx context.Context, resourceGroupName, privateEndpointName string, parameters armnetwork.PrivateEndpoint, options *armnetwork.PrivateEndpointsClientBeginCreateOrUpdateOptions) (resp azfake.PollerResponder[armnetwork.PrivateEndpointsClientCreateOrUpdateResponse], errResp azfake.ErrorResponder) {
			resp.AddNonTerminalResponse(http.StatusOK, nil)
			resp.AddPollingError(errBoom)
			return
		},
	}, errBoom)

	f("successful when creating pe", &fake.PrivateEndpointsServer{
		BeginCreateOrUpdate: func(ctx context.Context, resourceGroupName, privateEndpointName string, parameters armnetwork.PrivateEndpoint, options *armnetwork.PrivateEndpointsClientBeginCreateOrUpdateOptions) (resp azfake.PollerResponder[armnetwork.PrivateEndpointsClientCreateOrUpdateResponse], errResp azfake.ErrorResponder) {
			resp.SetTerminalResponse(http.StatusOK, armnetwork.PrivateEndpointsClientCreateOrUpdateResponse{}, nil)
			return
		},
	}, nil)

}

func TestPrivateEndpointDelete(t *testing.T) {
	g := NewWithT(t)

	f := func(name string, server *fake.PrivateEndpointsServer, expected error) {
		t.Run(name, func(t *testing.T) {
			cli, err := armnetwork.NewPrivateEndpointsClient("fake", &azfake.TokenCredential{}, &arm.ClientOptions{
				ClientOptions: azcore.ClientOptions{
					Transport: fake.NewPrivateEndpointsServerTransport(server),
				},
			})
			g.Expect(err).To(Succeed())
			pe := PrivateEndpointClient{
				cli:           cli,
				resourceGroup: "fake",
			}
			err = pe.Delete(context.TODO(), "fake")
			g.Expect(errors.Is(err, expected)).To(BeTrue())
		})
	}

	f("error when deleting pe", &fake.PrivateEndpointsServer{
		BeginDelete: func(ctx context.Context, resourceGroupName, privateEndpointName string, options *armnetwork.PrivateEndpointsClientBeginDeleteOptions) (resp azfake.PollerResponder[armnetwork.PrivateEndpointsClientDeleteResponse], errResp azfake.ErrorResponder) {
			errResp.SetError(errBoom)
			return
		},
	}, errBoom)

	f("error polling when deleting pe", &fake.PrivateEndpointsServer{
		BeginDelete: func(ctx context.Context, resourceGroupName, privateEndpointName string, options *armnetwork.PrivateEndpointsClientBeginDeleteOptions) (resp azfake.PollerResponder[armnetwork.PrivateEndpointsClientDeleteResponse], errResp azfake.ErrorResponder) {
			resp.AddPollingError(errBoom)
			return
		},
	}, errBoom)

	f("succeessful when deleting pe", &fake.PrivateEndpointsServer{
		BeginDelete: func(ctx context.Context, resourceGroupName, privateEndpointName string, options *armnetwork.PrivateEndpointsClientBeginDeleteOptions) (resp azfake.PollerResponder[armnetwork.PrivateEndpointsClientDeleteResponse], errResp azfake.ErrorResponder) {
			resp.SetTerminalResponse(http.StatusOK, armnetwork.PrivateEndpointsClientDeleteResponse{}, nil)
			return
		},
	}, nil)

}

func TestPrivateEndpointGetPrivateIP(t *testing.T) {
	g := NewWithT(t)

	f := func(name string, peServer *fake.PrivateEndpointsServer, iServer *fake.InterfacesServer, expected error) {
		t.Run(name, func(t *testing.T) {
			cli, err := armnetwork.NewPrivateEndpointsClient("fake", &azfake.TokenCredential{}, &arm.ClientOptions{
				ClientOptions: azcore.ClientOptions{
					Transport: fake.NewPrivateEndpointsServerTransport(peServer),
				},
			})
			g.Expect(err).To(Succeed())
			nicCli, err := armnetwork.NewInterfacesClient("fake", &azfake.TokenCredential{}, &arm.ClientOptions{
				ClientOptions: azcore.ClientOptions{
					Transport: fake.NewInterfacesServerTransport(iServer),
				},
			})
			g.Expect(err).To(Succeed())

			pe := PrivateEndpointClient{
				cli:           cli,
				nicCli:        nicCli,
				resourceGroup: "fake",
			}
			_, err = pe.GetPrivateIP(context.TODO(), "fake")
			g.Expect(errors.Is(err, expected) || err.Error() == expected.Error()).To(BeTrue())
		})
	}

	f("error getting private endpoint when getting private ip", &fake.PrivateEndpointsServer{
		Get: func(ctx context.Context, resourceGroupName, privateEndpointName string, options *armnetwork.PrivateEndpointsClientGetOptions) (resp azfake.Responder[armnetwork.PrivateEndpointsClientGetResponse], errResp azfake.ErrorResponder) {
			errResp.SetError(errBoom)
			return
		},
	}, nil, errBoom)

	f("error finding nic when getting private ip", &fake.PrivateEndpointsServer{
		Get: func(ctx context.Context, resourceGroupName, privateEndpointName string, options *armnetwork.PrivateEndpointsClientGetOptions) (resp azfake.Responder[armnetwork.PrivateEndpointsClientGetResponse], errResp azfake.ErrorResponder) {
			resp.SetResponse(http.StatusOK, armnetwork.PrivateEndpointsClientGetResponse{
				PrivateEndpoint: armnetwork.PrivateEndpoint{
					Name: to.Ptr("fake"),
				},
			}, nil)
			return
		},
	}, nil, fmt.Errorf("no nic in pe fake"))

	f("error getting nic when getting private ip", &fake.PrivateEndpointsServer{
		Get: func(ctx context.Context, resourceGroupName, privateEndpointName string, options *armnetwork.PrivateEndpointsClientGetOptions) (resp azfake.Responder[armnetwork.PrivateEndpointsClientGetResponse], errResp azfake.ErrorResponder) {
			resp.SetResponse(http.StatusOK, armnetwork.PrivateEndpointsClientGetResponse{
				PrivateEndpoint: armnetwork.PrivateEndpoint{
					Name: to.Ptr("fake"),
					Properties: &armnetwork.PrivateEndpointProperties{
						NetworkInterfaces: []*armnetwork.Interface{
							{
								ID: to.Ptr("/subscriptions/fake/resourceGroups/fake/providers/Microsoft.Network/networkInterfaces/nic-fake"),
							},
						},
					},
				},
			}, nil)
			return
		},
	}, &fake.InterfacesServer{
		Get: func(ctx context.Context, resourceGroupName, networkInterfaceName string, options *armnetwork.InterfacesClientGetOptions) (resp azfake.Responder[armnetwork.InterfacesClientGetResponse], errResp azfake.ErrorResponder) {
			errResp.SetError(errBoom)
			return
		},
	}, errBoom)

	f("error getting nic ipconfig when getting private ip", &fake.PrivateEndpointsServer{
		Get: func(ctx context.Context, resourceGroupName, privateEndpointName string, options *armnetwork.PrivateEndpointsClientGetOptions) (resp azfake.Responder[armnetwork.PrivateEndpointsClientGetResponse], errResp azfake.ErrorResponder) {
			resp.SetResponse(http.StatusOK, armnetwork.PrivateEndpointsClientGetResponse{
				PrivateEndpoint: armnetwork.PrivateEndpoint{
					Name: to.Ptr("fake"),
					Properties: &armnetwork.PrivateEndpointProperties{
						NetworkInterfaces: []*armnetwork.Interface{
							{
								ID: to.Ptr("/subscriptions/fake/resourceGroups/fake/providers/Microsoft.Network/networkInterfaces/nic-fake"),
							},
						},
					},
				},
			}, nil)
			return
		},
	}, &fake.InterfacesServer{
		Get: func(ctx context.Context, resourceGroupName, networkInterfaceName string, options *armnetwork.InterfacesClientGetOptions) (resp azfake.Responder[armnetwork.InterfacesClientGetResponse], errResp azfake.ErrorResponder) {
			resp.SetResponse(http.StatusOK, armnetwork.InterfacesClientGetResponse{}, nil)
			return
		},
	}, fmt.Errorf("no ip config in nic nic-fake"))

	f("successfully getting private ip", &fake.PrivateEndpointsServer{
		Get: func(ctx context.Context, resourceGroupName, privateEndpointName string, options *armnetwork.PrivateEndpointsClientGetOptions) (resp azfake.Responder[armnetwork.PrivateEndpointsClientGetResponse], errResp azfake.ErrorResponder) {
			resp.SetResponse(http.StatusOK, armnetwork.PrivateEndpointsClientGetResponse{
				PrivateEndpoint: armnetwork.PrivateEndpoint{
					Name: to.Ptr("fake"),
					Properties: &armnetwork.PrivateEndpointProperties{
						NetworkInterfaces: []*armnetwork.Interface{
							{
								ID: to.Ptr("/subscriptions/fake/resourceGroups/fake/providers/Microsoft.Network/networkInterfaces/nic-fake"),
							},
						},
					},
				},
			}, nil)
			return
		},
	}, &fake.InterfacesServer{
		Get: func(ctx context.Context, resourceGroupName, networkInterfaceName string, options *armnetwork.InterfacesClientGetOptions) (resp azfake.Responder[armnetwork.InterfacesClientGetResponse], errResp azfake.ErrorResponder) {
			resp.SetResponse(http.StatusOK, armnetwork.InterfacesClientGetResponse{
				Interface: armnetwork.Interface{
					Properties: &armnetwork.InterfacePropertiesFormat{
						IPConfigurations: []*armnetwork.InterfaceIPConfiguration{
							{
								Properties: &armnetwork.InterfaceIPConfigurationPropertiesFormat{
									PrivateIPAddress: to.Ptr("1.2.3.4"),
								},
							},
						},
					},
				},
			}, nil)
			return
		},
	}, nil)

}
