package account

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/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: false,
			},
		},
		{
			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 TestPreAssignAccount(t *testing.T) {
	mockcli := fakeData(t)
	gin.SetMode(gin.TestMode)
	eng := gin.Default()
	handler := NewAccountHandler(mockcli, config.RootConfig{})
	t.Run("request body is not valid", func(t *testing.T) {
		in := &regionalapiv1.AssignAccountRequest{}
		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/accounts/assignAccount", body)
		handler.PreAssignAccount(ctx)
		assert.Equal(t, http.StatusBadRequest, w.Result().StatusCode)
	})
	t.Run("pre assign account succeed", func(t *testing.T) {
		handler := NewAccountHandler(mockcli, config.RootConfig{
			ControllerConfig: config.ControllerConfig{
				Provider: consts.ProviderTypeEKS,
			},
		})
		w := httptest.NewRecorder()
		in := &regionalapiv1.AssignAccountRequest{
			ProjectId:   "project1",
			CmekEnabled: true,
		}
		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/accounts/assignAccount", body)
		handler.PreAssignAccount(ctx)
		assert.Equal(t, http.StatusOK, w.Result().StatusCode)
		assert.JSONEq(t, "{\"accountId\":\"account2\", \"azureAppRegistration\":null}", w.Body.String())

		w = httptest.NewRecorder()
		in = &regionalapiv1.AssignAccountRequest{
			ProjectId: "project1",
		}
		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/accounts/assignAccount", body)
		handler.PreAssignAccount(ctx)
		assert.Equal(t, http.StatusOK, w.Result().StatusCode)

		assert.JSONEq(t, "{\"accountId\":\"account2\", \"azureAppRegistration\":null}", w.Body.String())
	})
}

func TestGetAccount(t *testing.T) {
	mockcli := fakeData(t)
	gin.SetMode(gin.TestMode)
	eng := gin.Default()
	handler := NewAccountHandler(mockcli, config.RootConfig{})
	w := httptest.NewRecorder()
	in := &regionalapiv1.AssignAccountRequest{
		ProjectId:   "project1",
		CmekEnabled: true,
	}
	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/accounts/assignAccount", body)
	handler.PreAssignAccount(ctx)
	assert.Equal(t, http.StatusOK, w.Result().StatusCode)

	t.Run("request param is not valid", func(t *testing.T) {
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest("GET", "/api/dedicated/v1/accounts", nil)
		handler.GetAccount(ctx)
		assert.Equal(t, http.StatusBadRequest, w.Result().StatusCode)
	})
	t.Run("get account succeed", func(t *testing.T) {
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest("GET", "/api/dedicated/v1/accounts?projectId=project1", nil)
		handler.GetAccount(ctx)
		assert.Equal(t, http.StatusOK, w.Result().StatusCode)
		assert.JSONEq(t, "{\"accountId\":\"account2\", \"azureAppRegistration\":null}", w.Body.String())
	})
	t.Run("get account failed", func(t *testing.T) {
		w := httptest.NewRecorder()
		ctx := gin.CreateTestContextOnly(w, eng)
		ctx.Request = httptest.NewRequest("GET", "/api/dedicated/v1/accounts?projectId=project2", nil)
		handler.GetAccount(ctx)
		assert.Equal(t, http.StatusNotFound, w.Result().StatusCode)
	})
}
