package privatelinkservice

import (
	"bytes"
	"context"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
	regionalapiv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/utils"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"google.golang.org/protobuf/encoding/protojson"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func fakeData(*testing.T) client.Client {
	approute := &v1alpha1.APPRoute{
		ObjectMeta: metav1.ObjectMeta{
			Name: "test-approute",
			Labels: map[string]string{
				v1alpha1.ClusterIDKey: "test-cluster-id",
			},
		},
	}
	s := runtime.NewScheme()
	v1alpha1.AddToScheme(s)
	mockcli := fake.NewClientBuilder().WithScheme(s).WithObjects(approute).Build()
	return mockcli
}

func TestCreatePrivateLinkService(t *testing.T) {
	mockcli := fakeData(t)
	gin.SetMode(gin.TestMode)
	eng := gin.Default()
	handler := NewPrivateLinkServiceHandler(mockcli, config.RootConfig{})

	t.Run("request body is not valid", func(t *testing.T) {
		in := &regionalapiv1.CreatePrivateLinkServiceRequest{}
		buf, err := protojson.Marshal(in)
		assert.Nil(t, err)
		body := bytes.NewReader(buf)
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest(http.MethodPost, "/api/dedicated/v1/clusters/test-cluster-id/privateLinkServices", body)
		handler.CreatePrivateLinkService(ctx)
		assert.Equal(t, http.StatusBadRequest, w.Code)
	})

	t.Run("create private link service success", func(t *testing.T) {
		in := &regionalapiv1.PrivateLinkService{
			Id:          "test-id",
			ClusterId:   "test-cluster-id",
			TidbGroupId: "test-loadbalancer-id",
		}
		buf, err := protojson.Marshal(in)
		assert.Nil(t, err)
		body := bytes.NewReader(buf)
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest(http.MethodPost, "/api/dedicated/v1/clusters/test-cluster-id/privateLinkServices", body)
		handler.CreatePrivateLinkService(ctx)
		assert.Equal(t, http.StatusAccepted, w.Code)
	})
}

func TestGetPrivateLinkService(t *testing.T) {
	mockcli := fakeData(t)
	gin.SetMode(gin.TestMode)
	eng := gin.Default()
	rootConfig := config.RootConfig{
		ControllerConfig: config.ControllerConfig{
			Provider: consts.ProviderTypeEKS,
		},
	}
	handler := NewPrivateLinkServiceHandler(mockcli, rootConfig)
	namer := utils.NewNamerWithClusterID("test-cluster-id", rootConfig.RegionConfig.Name, rootConfig.Env)
	pls := &v1alpha1.PrivateLinkService{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test-id",
			Namespace: namer.GetNamespace(),
			Labels: map[string]string{
				v1alpha1.ClusterIDKey: "test-cluster-id",
			},
		},
		Spec: v1alpha1.PrivateLinkServiceSpec{
			Provider: consts.ProviderTypeEKS,
		},
	}
	assert.Nil(t, mockcli.Create(context.Background(), pls))

	t.Run("invalid request", func(t *testing.T) {
		// Prepare the request
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest(http.MethodGet, "/api/dedicated/v1/clusters/test-cluster-id/privateLinkServices/non-existent-id", nil)
		ctx.Params = []gin.Param{
			{Key: "clusterId", Value: "test-cluster-id"},
		}
		// Call the function
		handler.GetPrivateLinkService(ctx)
		// Check the response
		assert.Equal(t, http.StatusBadRequest, w.Code)
	})

	t.Run("get privatelinkservice success", func(t *testing.T) {
		// Prepare the request
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest(http.MethodGet, "/api/dedicated/v1/clusters/test-cluster-id/privateLinkServices/test-id", nil)
		ctx.Params = []gin.Param{
			{Key: "clusterId", Value: "test-cluster-id"},
			{Key: "privateLinkServiceId", Value: "test-id"},
		}
		// Call the function
		handler.GetPrivateLinkService(ctx)
		// Check the response
		assert.Equal(t, http.StatusOK, w.Code)
	})

	t.Run("privatelinkservice not found", func(t *testing.T) {
		// Prepare the request
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest(http.MethodGet, "/api/dedicated/v1/clusters/test-cluster-id/privateLinkServices/non-existent-id", nil)
		ctx.Params = []gin.Param{
			{Key: "clusterId", Value: "test-cluster-id"},
			{Key: "privateLinkServiceId", Value: "test-id-2"},
		}
		// Call the function
		handler.GetPrivateLinkService(ctx)
		// Check the response
		assert.Equal(t, http.StatusNotFound, w.Code)
	})
}

func TestListPrivateLinkServices(t *testing.T) {
	mockcli := fakeData(t)
	gin.SetMode(gin.TestMode)
	eng := gin.Default()
	rootConfig := config.RootConfig{}
	handler := NewPrivateLinkServiceHandler(mockcli, rootConfig)
	namer := utils.NewNamerWithClusterID("test-cluster-id", rootConfig.RegionConfig.Name, rootConfig.Env)

	t.Run("invalid request", func(t *testing.T) {
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest(http.MethodGet, "/api/dedicated/v1/clusters//privateLinkServices", nil)
		handler.ListPrivateLinkServices(ctx)
		assert.Equal(t, http.StatusBadRequest, w.Code)
	})

	t.Run("list privatelinkservice success", func(t *testing.T) {
		pls := &v1alpha1.PrivateLinkService{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "test-id",
				Namespace: namer.GetNamespace(),
				Labels: map[string]string{
					v1alpha1.ClusterIDKey: "test-cluster-id",
				},
			},
			Spec: v1alpha1.PrivateLinkServiceSpec{
				Provider: consts.ProviderTypeEKS,
			},
		}
		assert.Nil(t, mockcli.Create(context.Background(), pls))

		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest(http.MethodGet, "/api/dedicated/v1/clusters/test-cluster-id/privateLinkServices", nil)
		ctx.Params = []gin.Param{
			{Key: "clusterId", Value: "test-cluster-id"},
		}
		handler.ListPrivateLinkServices(ctx)
		assert.Equal(t, http.StatusOK, w.Code)
	})
}

func TestUpdatePrivateLinkService(t *testing.T) {
	mockcli := fakeData(t)
	gin.SetMode(gin.TestMode)
	eng := gin.Default()
	rootConfig := config.RootConfig{}
	handler := NewPrivateLinkServiceHandler(mockcli, rootConfig)
	namer := utils.NewNamerWithClusterID("test-cluster-id", rootConfig.RegionConfig.Name, rootConfig.Env)

	t.Run("privatelinkservice not found", func(t *testing.T) {
		in := &regionalapiv1.UpdatePrivateLinkServiceRequest{
			ClusterId:            "test-cluster-id",
			PrivateLinkServiceId: "test-id",
		}
		buf, err := protojson.Marshal(in)
		assert.Nil(t, err)
		body := bytes.NewReader(buf)
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest(http.MethodPatch, "/api/dedicated/v1/clusters/test-cluster-id/privateLinkServices/test-id", body)
		ctx.Params = []gin.Param{
			{Key: "clusterId", Value: "test-cluster-id"},
			{Key: "privateLinkServiceId", Value: "test-id"},
		}
		handler.UpdatePrivateLinkService(ctx)
		assert.Equal(t, http.StatusNotFound, w.Code)
	})
	t.Run("update privatelinkservice success", func(t *testing.T) {
		pls := &v1alpha1.PrivateLinkService{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "test-id",
				Namespace: namer.GetNamespace(),
				Labels: map[string]string{
					v1alpha1.ClusterIDKey: "test-cluster-id",
				},
			},
			Spec: v1alpha1.PrivateLinkServiceSpec{
				Provider: consts.ProviderTypeEKS,
			},
		}
		assert.Nil(t, mockcli.Create(context.Background(), pls))
		in := &regionalapiv1.UpdatePrivateLinkServiceRequest{
			ClusterId:            "test-cluster-id",
			PrivateLinkServiceId: "test-id",
		}
		buf, err := protojson.Marshal(in)
		assert.Nil(t, err)
		body := bytes.NewReader(buf)
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest(http.MethodPatch, "/api/dedicated/v1/clusters/test-cluster-id/privateLinkServices/test-id", body)
		ctx.Params = []gin.Param{
			{Key: "clusterId", Value: "test-cluster-id"},
			{Key: "privateLinkServiceId", Value: "test-id"},
		}
		handler.UpdatePrivateLinkService(ctx)
		assert.Equal(t, http.StatusAccepted, w.Code)
	})
}

func TestDeletePrivateLinkService(t *testing.T) {
	mockcli := fakeData(t)
	gin.SetMode(gin.TestMode)
	eng := gin.Default()
	rootConfig := config.RootConfig{}
	handler := NewPrivateLinkServiceHandler(mockcli, rootConfig)
	namer := utils.NewNamerWithClusterID("test-cluster-id", rootConfig.RegionConfig.Name, rootConfig.Env)
	pls := &v1alpha1.PrivateLinkService{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test-id",
			Namespace: namer.GetNamespace(),
			Labels: map[string]string{
				v1alpha1.ClusterIDKey: "test-cluster-id",
			},
		},
		Spec: v1alpha1.PrivateLinkServiceSpec{
			Provider: consts.ProviderTypeEKS,
		},
	}
	assert.Nil(t, mockcli.Create(context.Background(), pls))

	t.Run("invalid request", func(t *testing.T) {
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest(http.MethodDelete, "/api/dedicated/v1/clusters//privateLinkServices", nil)
		handler.DeletePrivateLinkService(ctx)
		assert.Equal(t, http.StatusBadRequest, w.Code)
	})

	t.Run("delete private link service success", func(t *testing.T) {
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest(http.MethodDelete, "/api/dedicated/v1/clusters/test-cluster-id/privateLinkServices/test-id", nil)
		ctx.Params = []gin.Param{
			{Key: "clusterId", Value: "test-cluster-id"},
			{Key: "privateLinkServiceId", Value: "test-id"},
		}
		handler.DeletePrivateLinkService(ctx)
		assert.Equal(t, http.StatusOK, w.Code)
	})

	t.Run("delete private link service not found", func(t *testing.T) {
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest(http.MethodDelete, "/api/dedicated/v1/clusters/test-cluster-id/privateLinkServices/non-existent-id", nil)
		ctx.Params = []gin.Param{
			{Key: "clusterId", Value: "test-cluster-id"},
			{Key: "privateLinkServiceId", Value: "non-existent-id"},
		}
		handler.DeletePrivateLinkService(ctx)
		assert.Equal(t, http.StatusNotFound, w.Code)
	})
}
