package dataapi_executor

import (
	"context"
	"dataservices/commons/config"
	"dataservices/commons/metrics"
	"dataservices/commons/sentry"
	"dataservices/dbhubsdk"
	"dataservices/ddb"
	controller "dataservices/internal/controller/dataapi_executor"
	"dataservices/internal/entity"
	"dataservices/internal/repository_ddb"
	"fmt"
	"net"
	"testing"

	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/dataservices/idl/pbgen/proto/dataapi"
	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	tigrpc "github.com/tidbcloud/tiinf/transport/tigrpc/server"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

/*
//prepare data in test TiDB cluster.

use test;
create table t1 (id int key, name varchar(100));
create table t2 (id int key, name varchar(100));
insert into t1 values (1,'a'),(2,'b');
insert into t2 values (1, 'a\'b'),(2, 'a\"b');
create database sample_data;
use sample_data;
create table github_events (id int);
insert into github_events values (1);
*/

func TestExecutorGrpc(t *testing.T) {
	setupExecutor("/Users/cs/.infra-api/regional_dataapi_executor.yaml")

	conn, err := grpc.Dial("0.0.0.0:"+config.Global().ServerConf.GrpcPort, grpc.WithTransportCredentials(insecure.NewCredentials()))
	//conn, err := grpc.Dial("0.0.0.0:10000", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		t.Fatal(err)
	}
	executorClient := dataapi.NewDataAPIExecutorServiceClient(conn)

	ctx := context.Background()
	projectId := uint64(43563)
	clusterId := uint64(270048)
	databaseName := "test"

	// Store cluster info into ddb.
	err = repository_ddb.PutUserClusterInfo(ctx, ddb.Client(), &entity.UserClusterInfo{
		ProjectId:       projectId,
		ClusterId:       clusterId,
		IsDedicate:      false,
		EndpointService: "",
		EndpointId:      "",
		PrivateDNS:      "",
		Host:            "0.0.0.0",
		Port:            4000,
	})
	require.NoError(t, err)

	endpointReq := &dataapi.BasicEndpointReqV2{
		ProjectId:  projectId,
		ClusterId:  clusterId,
		Database:   databaseName,
		Sql:        "select * from t1 where id = 1;",
		Args:       nil,
		SessionId:  "",
		LimitRows:  10,
		Credential: nil,
	}
	res, err := executorClient.ExecSqlDirectly(ctx, endpointReq)
	require.NoError(t, err)
	require.Equal(t, 1, len(res.StmtResults))
	require.Equal(t, int64(1), res.StmtResults[0].RowNum)
	require.Equal(t, []string{"1", "a"}, res.StmtResults[0].Rows[0].Items)

	res, err = executorClient.ExecSqlWithSession(ctx, endpointReq)
	require.NoError(t, err)
	require.Equal(t, int64(49900001), res.BaseResp.ErrCode)
	require.Equal(t, "Session id is required", res.BaseResp.ErrMsg)

	endpointReq.SessionId = "1"
	res, err = executorClient.ExecSqlWithSession(ctx, endpointReq)
	require.NoError(t, err)
	require.Equal(t, int64(49900001), res.BaseResp.ErrCode)
	require.Equal(t, "Sorry, your session has expired, please refresh the page and try again ^_^!", res.BaseResp.ErrMsg)

	resp, err := executorClient.CreateSession(ctx, &dataapi.CreateSessionReq{
		ProjectId:  projectId,
		ClusterId:  clusterId,
		Database:   databaseName,
		ExpireSec:  0,
		Credential: nil,
	})
	require.NoError(t, err)
	require.NotEqual(t, "", resp.SessionId)

	endpointReq.SessionId = resp.SessionId
	res, err = executorClient.ExecSqlWithSession(ctx, endpointReq)
	require.NoError(t, err)
	require.Equal(t, int64(1), res.StmtResults[0].RowNum)
	require.Equal(t, []string{"1", "a"}, res.StmtResults[0].Rows[0].Items)

	resp2, err := executorClient.CloseSession(ctx, &dataapi.CloseSessionReq{SessionId: resp.SessionId})
	require.NoError(t, err)
	require.Equal(t, int64(0), resp2.BaseResp.ErrCode)
	require.Equal(t, "", resp2.BaseResp.ErrMsg)
}

func setupExecutor(configPath string) {
	err := config.InitConfig(configPath)
	if err != nil {
		panic(fmt.Sprintf("Initialize config failed: %+v", err))
	}

	err = sentry.SetupLoggerAndSentry(config.Global().SentryConf.DSN, config.Global().SentryConf.Environment, config.Global().SentryConf.LogLevel)
	if err != nil {
		panic(fmt.Sprintf("Initialize logger and sentry failed: %+v", err))
	}
	config := config.Global()

	metrics.RegisterDataApiExecutorMetrics()

	err = ddb.Init(context.Background(), config)
	if err != nil {
		panic(fmt.Sprintf("Initialize ddb failed: %+v", err))
	}

	dbhubsdk.InitRegionalSdk(ddb.Client())

	go setupExecutorGRPC()
}

func setupExecutorGRPC() {
	grpcServer := newGRPCServer()
	dataapi.RegisterDataAPIExecutorServiceServer(grpcServer, controller.NewDataAPIExecutorService())
	addr := ":" + config.Global().ServerConf.GrpcPort
	tilog.Info(context.Background(), "start executor rpc", zap.String("addr", addr))
	listen, err := net.Listen("tcp", addr)
	if err != nil {
		panic(err)
	}
	err = grpcServer.Serve(listen)
	if err != nil {
		panic(err)
	}
}

func newGRPCServer() *grpc.Server {
	ctx := context.Background()
	server := tigrpc.NewServer(
		tigrpc.WithDefault(tilog.WithContextL(ctx)),
		tigrpc.WithUnaryInterceptors(
			grpc_middleware.ChainUnaryServer(
				metrics.UnaryServerInterceptor(),
				errs.UnaryServerInterceptor(),
			),
		),
	)
	return server
}
