package sqluser

import (
	"strconv"
	"testing"

	"github.com/smartystreets/goconvey/convey"
	"go.uber.org/mock/gomock"

	globalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
	regionalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/regional/serverless/v1"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/adapter/mock_adapter"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/repository/mock_repository"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/testdata"
	"github.com/tidbcloud/tidb-management-service/utilities/clusterproxy/mock_clusterproxy"
)

func TestCreateRoleSvc(t *testing.T) {
	convey.Convey("TestCreateRoleSvc", t, func() {
		ctrl := gomock.NewController(t)
		defer ctrl.Finish()

		clusterRepo := mock_repository.NewMockClusterRepo(ctrl)
		clusterProxy := mock_clusterproxy.NewMockClusterProxy(ctrl)
		clusterProxyProvider := mock_clusterproxy.NewMockProvider(ctrl)
		cluster := testdata.GetCluster()
		regionalCluster := testdata.GetRegionalCluster()
		encryptSvc := mock_adapter.NewMockEncryptorService(ctrl)
		regionalSvc := mock_adapter.NewMockRegionalService(ctrl)
		sys9Lifecycle := mock_adapter.NewMockSys9Lifecycle(ctrl)

		clusterIdInt, err := strconv.ParseUint(cluster.ClusterId, 10, 64)
		convey.So(err, convey.ShouldBeNil)

		req := &globalserverlessv1.SqlUserServiceCreateSqlRoleRequest{
			Operator: &globalserverlessv1.SqlUserOperator{
				Username:   "u1",
				AuthMethod: globalserverlessv1.SqlUser_AUTH_METHOD_TOKEN,
				Credential: "token",
			},
			RoleName:   "role1",
			Grants:     "GRANT SELECT ON *.* TO 'role1'",
			Revocation: "REVOKE DELETE ON *.* FROM 'role1'; REVOKE UPDATE ON *.* FROM 'role1'",
			ClusterId:  cluster.ClusterId,
		}
		params := &CreateRoleParams{
			Operator:   req.Operator,
			Cluster:    regionalCluster,
			RoleName:   req.RoleName,
			Grants:     req.Grants,
			Revocation: req.Revocation,
		}

		// enable token-based auth
		cluster.Attributes.TokenBasedAuthEnabled = true
		svc := NewSqlUserService(encryptSvc, clusterProxyProvider, clusterRepo, regionalSvc, sys9Lifecycle, nil, nil)

		convey.Convey("Create", func() {
			clusterRepo.EXPECT().Get(gomock.Any(), req.ClusterId, false).Return(cluster, nil)
			regionalSvc.EXPECT().
				ListCluster(gomock.Any(), gomock.Any(), gomock.Any()).
				Return([]*regionalserverlessv1.ServerlessRegionalServiceClusterInfo{regionalCluster}, nil)
			clusterProxyProvider.EXPECT().
				GetClusterProxy(gomock.Any(), clusterIdInt, regionalCluster.ProxyEndpoint, regionalCluster.Version, BuildClusterOperator(params.Operator)).
				Return(clusterProxy, nil)
			clusterProxy.EXPECT().ExecuteMultiSQL(gomock.Any(), []string{
				"CREATE ROLE IF NOT EXISTS 'role1';",
				"GRANT SELECT ON *.* TO 'role1'",
				"REVOKE DELETE ON *.* FROM 'role1'",
				"REVOKE UPDATE ON *.* FROM 'role1'",
			})

			err := svc.CreateSqlUserRole(testdata.Ctx, req)
			convey.So(err, convey.ShouldBeNil)
		})
		convey.Convey("checkParams", func() {
			convey.Convey("should be ok if all is well", func() {
				err := createRoleCheck(testdata.Ctx, params, cluster)
				convey.So(err, convey.ShouldBeNil)
			})
			convey.Convey("should return err if roleName is empty", func() {
				params.RoleName = ""

				err := createRoleCheck(testdata.Ctx, params, cluster)
				convey.So(err, convey.ShouldNotBeNil)
			})
			convey.Convey("should return err if roleName is builtin role", func() {
				params.RoleName = BuiltinRoleNameAdmin

				err := createRoleCheck(testdata.Ctx, params, cluster)
				convey.So(err, convey.ShouldNotBeNil)
				convey.So(err.Error(), convey.ShouldContainSubstring, "builtin role")
			})
			convey.Convey("should return err if grants is invalid", func() {
				params.Grants = "invalid sql"

				err := createRoleCheck(testdata.Ctx, params, cluster)
				convey.So(err, convey.ShouldNotBeNil)
			})
			convey.Convey("should return err if revocation is invalid", func() {
				params.Revocation = "invalid sql"

				err := createRoleCheck(testdata.Ctx, params, cluster)
				convey.So(err, convey.ShouldNotBeNil)
			})
		})
	})
}
