package vpc

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/constants"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/models"
	"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"
	"gopkg.in/yaml.v3"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	fake "sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func fakeData(t *testing.T) client.Client {
	accountList := []models.DataPlaneAccountInfo{
		{
			AWSAccountInfo: &models.AWSAccountInfo{
				AWSAccount:  "account1",
				AWSRole:     "role1",
				CMEKEnabled: true,
			},
		},
		{
			AWSAccountInfo: &models.AWSAccountInfo{
				AWSAccount:  "account2",
				AWSRole:     "role2",
				CMEKEnabled: true,
			},
		},
		{
			GCPAccountInfo: &models.GCPAccountInfo{
				ProjectID: "project1",
			},
		},
	}

	listYaml, err := yaml.Marshal(accountList)
	assert.Nil(t, err)
	configData := map[string]string{}
	configData[constants.DataPlaneAccountConfigMapDefaultKey] = "account2"
	configData[constants.DataPlaneAccountConfigMapListKey] = string(listYaml)

	configMap := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: constants.DataPlaneAccountNamespace,
			Name:      constants.DataPlaneAccountConfigMapName,
		},

		Data: configData,
	}

	s := runtime.NewScheme()
	v1alpha1.AddToScheme(s)
	corev1.AddToScheme(s)
	mockcli := fake.NewClientBuilder().WithScheme(s).Build()
	err = mockcli.Create(context.TODO(), configMap)
	assert.Nil(t, err)
	return mockcli
}

func TestCreateVPC(t *testing.T) {
	mockcli := fakeData(t)
	gin.SetMode(gin.TestMode)
	eng := gin.Default()
	handler := NewVPCHandler(mockcli, config.RootConfig{
		ControllerConfig: config.ControllerConfig{
			Provider: consts.ProviderTypeEKS,
		},
	})
	in := &regionalapiv1.CreateVpcRequest{
		RequestId: "1",
		Vpc: &regionalapiv1.Vpc{
			TenantIds:  []string{"2"},
			ProjectIds: []string{"3"},
			CidrBlock:  "10.15.0.0/21",
		},
	}
	t.Run("request body is not valid", func(t *testing.T) {
		in := &regionalapiv1.CreateVpcRequest{
			RequestId: "1",
			Vpc:       &regionalapiv1.Vpc{},
		}
		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("POST", "/api/dedicated/v1/vpcs", body)
		handler.CreateVPC(ctx)
		assert.Equal(t, http.StatusBadRequest, w.Result().StatusCode)
	})
	t.Run("create vpc success", func(t *testing.T) {
		w := httptest.NewRecorder()
		buf, err := protojson.Marshal(in)
		assert.Nil(t, err)
		body := bytes.NewReader(buf)
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest("POST", "/api/dedicated/v1/vpcs", body)
		handler.CreateVPC(ctx)
		assert.Equal(t, http.StatusAccepted, w.Result().StatusCode)
		vpcList := &v1alpha1.VPCList{}
		assert.Nil(t, mockcli.List(context.TODO(), vpcList))
		assert.Equal(t, 1, len(vpcList.Items))
		// repeat create
		handler.CreateVPC(ctx)
		assert.Equal(t, http.StatusAccepted, w.Result().StatusCode)
		assert.Equal(t, 1, len(vpcList.Items))
	})
	t.Run("failed to assign account", func(t *testing.T) {
		assert.Nil(t, mockcli.Delete(context.TODO(), &corev1.ConfigMap{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: constants.DataPlaneAccountNamespace,
				Name:      constants.DataPlaneAccountConfigMapName,
			},
		}))
		w := httptest.NewRecorder()
		buf, err := protojson.Marshal(in)
		assert.Nil(t, err)
		body := bytes.NewReader(buf)
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest("POST", "/api/dedicated/v1/vpcs", body)
		handler.CreateVPC(ctx)
		assert.Equal(t, http.StatusInternalServerError, w.Result().StatusCode)
	})
}

func TestDeleteVPC(t *testing.T) {
	mockcli := fakeData(t)
	gin.SetMode(gin.TestMode)
	eng := gin.Default()
	handler := NewVPCHandler(mockcli, config.RootConfig{})
	t.Run("vpcId is empty", func(t *testing.T) {
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest("DELETE", "/api/dedicated/v1/vpcs/", nil)
		handler.DeleteVPC(ctx)
		assert.Equal(t, http.StatusBadRequest, w.Result().StatusCode)
	})
	t.Run("delete vpc success", func(t *testing.T) {
		vpc := &v1alpha1.VPC{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: "default",
				Name:      "vpc1",
				Labels: map[string]string{
					v1alpha1.VPCIDKey: "vpc-1",
				},
			},
		}
		assert.Nil(t, mockcli.Create(context.Background(), vpc))
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest("DELETE", "/api/dedicated/v1/vpcs/", nil)
		ctx.Params = []gin.Param{
			{
				Key:   "vpcId",
				Value: "vpc-1",
			},
		}
		handler.DeleteVPC(ctx)
		assert.Equal(t, http.StatusOK, w.Result().StatusCode)
		vpcList := &v1alpha1.VPCList{}
		assert.Nil(t, mockcli.List(context.Background(), vpcList))
		assert.Equal(t, 0, len(vpcList.Items))
	})
}

func TestGetVPC(t *testing.T) {
	mockcli := fakeData(t)
	gin.SetMode(gin.TestMode)
	eng := gin.Default()
	handler := NewVPCHandler(mockcli, config.RootConfig{
		ControllerConfig: config.ControllerConfig{
			Provider: consts.ProviderTypeEKS,
		},
	})
	t.Run("vpcId is empty", func(t *testing.T) {
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest("GET", "/api/dedicated/v1/vpcs/", nil)
		handler.GetVPC(ctx)
		assert.Equal(t, http.StatusBadRequest, w.Result().StatusCode)
	})
	t.Run("get vpc success", func(t *testing.T) {
		vpc := &v1alpha1.VPC{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: "default",
				Name:      "vpc1",
				Labels: map[string]string{
					v1alpha1.VPCIDKey: "vpc-1",
				},
			},
		}
		assert.Nil(t, mockcli.Create(context.Background(), vpc))
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest("GET", "/api/dedicated/v1/vpcs/", nil)
		ctx.Params = []gin.Param{
			{
				Key:   "vpcId",
				Value: "vpc-1",
			},
		}
		handler.GetVPC(ctx)
		assert.Equal(t, http.StatusOK, w.Result().StatusCode)
	})
}

func TestListVPC(t *testing.T) {
	mockcli := fakeData(t)
	gin.SetMode(gin.TestMode)
	eng := gin.Default()
	handler := NewVPCHandler(mockcli, config.RootConfig{
		ControllerConfig: config.ControllerConfig{
			Provider: consts.ProviderTypeEKS,
		},
	})
	t.Run("list vpc success", func(t *testing.T) {
		vpc := &v1alpha1.VPC{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: "default",
				Name:      "vpc1",
				Labels: map[string]string{
					utils.GenTenantLabelKey("2"): "true",
				},
			},
			Status: v1alpha1.VPCStatus{
				Phase: v1alpha1.VPCPhasePending,
			},
		}
		assert.Nil(t, mockcli.Create(context.Background(), vpc))
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest("GET", "/api/dedicated/v1/vpcs?tenantId=2", nil)
		handler.ListVPC(ctx)
		assert.Equal(t, http.StatusOK, w.Result().StatusCode)
		res := &regionalapiv1.ListVpcsResponse{}
		err := protojson.Unmarshal(w.Body.Bytes(), res)
		assert.Nil(t, err)
		assert.Equal(t, 1, len(res.Vpcs))
	})

	t.Run("list vpc with status filters, return empty", func(t *testing.T) {
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest("GET", "/api/dedicated/v1/vpcs?tenantId=2&statuses=active", nil)
		handler.ListVPC(ctx)
		assert.Equal(t, http.StatusOK, w.Result().StatusCode)
		res := &regionalapiv1.ListVpcsResponse{}
		err := protojson.Unmarshal(w.Body.Bytes(), res)
		assert.Nil(t, err)
		assert.Equal(t, 0, len(res.Vpcs))
	})
	t.Run("list vpc with status filters, return one", func(t *testing.T) {
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest("GET", "/api/dedicated/v1/vpcs?tenantId=2&statuses=active&statuses=creating", nil)
		handler.ListVPC(ctx)
		assert.Equal(t, http.StatusOK, w.Result().StatusCode)
		res := &regionalapiv1.ListVpcsResponse{}
		err := protojson.Unmarshal(w.Body.Bytes(), res)
		assert.Nil(t, err)
		assert.Equal(t, 1, len(res.Vpcs))
	})
	t.Run("filter deleting vpc", func(t *testing.T) {
		vpc := &v1alpha1.VPC{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: "default",
				Name:      "vpc2",
				Labels: map[string]string{
					utils.GenTenantLabelKey("3"): "true",
				},
			},
			Status: v1alpha1.VPCStatus{
				Phase: v1alpha1.VPCPhaseDeleting,
			},
		}
		assert.Nil(t, mockcli.Create(context.Background(), vpc))
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest("GET", "/api/dedicated/v1/vpcs?tenantId=3&statuses=active", nil)
		handler.ListVPC(ctx)
		assert.Equal(t, http.StatusOK, w.Result().StatusCode)
		res := &regionalapiv1.ListVpcsResponse{}
		err := protojson.Unmarshal(w.Body.Bytes(), res)
		assert.Nil(t, err)
		assert.Equal(t, 0, len(res.Vpcs))
	})
}
