package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"net/http"
	"net/http/pprof"
	"reflect"
	"runtime/debug"
	"time"

	"github.com/getsentry/sentry-go"
	sentryhttp "github.com/getsentry/sentry-go/http"
	gincors "github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
	grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
	grpcRecovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
	grpcValidator "github.com/grpc-ecosystem/go-grpc-middleware/validator"
	grpcprometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
	grpcruntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"github.com/pingcap/errcode"
	"github.com/pingcap/errors"
	"github.com/pingcap/log"
	configServiceV1 "github.com/tidbcloud/config-service/api/v1alpha1"
	api2 "github.com/tidbcloud/core-portal/internal/admin/api"
	"github.com/tidbcloud/core-portal/internal/admin/models"
	config2 "github.com/tidbcloud/core-portal/internal/central/config"
	"github.com/tidbcloud/core-portal/internal/central/db"
	grpchttp "github.com/tidbcloud/core-portal/internal/central/grpc/http"
	api "github.com/tidbcloud/core-portal/internal/central/infra-api"
	"github.com/tidbcloud/core-portal/internal/central/mesh"
	metrics2 "github.com/tidbcloud/core-portal/internal/central/metrics"
	clustermetrics "github.com/tidbcloud/core-portal/internal/central/metrics/cluster"
	clustermodel "github.com/tidbcloud/core-portal/internal/central/model/cluster"
	"github.com/tidbcloud/core-portal/internal/central/model/testtoken"
	grpc_errcode "github.com/tidbcloud/core-portal/internal/central/pkg/grpc/middleware/errcode"
	"github.com/tidbcloud/core-portal/internal/central/pkg/grpc/middleware/ratelimit"
	grpc_sentry "github.com/tidbcloud/core-portal/internal/central/pkg/grpc/middleware/sentry"
	"github.com/tidbcloud/core-portal/internal/central/pkg/http/proxy"
	"github.com/tidbcloud/core-portal/internal/central/pkg/version"
	"github.com/tidbcloud/core-portal/internal/central/service"
	"github.com/tidbcloud/core-portal/internal/central/service/cluster"
	coresvc "github.com/tidbcloud/core-portal/internal/central/service/core"
	testokenservice "github.com/tidbcloud/core-portal/internal/central/service/testtoken"
	openapiservice "github.com/tidbcloud/core-portal/internal/openapi/service"
	openapiCluster "github.com/tidbcloud/core-portal/internal/openapi/service/cluster"
	"github.com/tidbcloud/core-portal/pkg/auth"
	"github.com/tidbcloud/core-portal/pkg/rbac"
	"github.com/tidbcloud/core-portal/pkg/singleton"
	"github.com/tidbcloud/core-svc/idl/pbgen/proto/central"
	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
	"github.com/tidbcloud/core-svc/knife"
	"github.com/tidbcloud/idl/pbgen/proto/openapi/cm"
	"github.com/tidbcloud/pkgv2/auth/auth0"
	"github.com/tidbcloud/pkgv2/auth/util"
	"github.com/tidbcloud/pkgv2/bizerr"
	grpcbizerr "github.com/tidbcloud/pkgv2/grpc/middleware/bizerr"
	grpctrace "github.com/tidbcloud/pkgv2/grpc/middleware/trace"
	httpmiddleware "github.com/tidbcloud/pkgv2/http/middleware"
	pkglog "github.com/tidbcloud/pkgv2/log"
	"github.com/tidbcloud/tiinf/monitor/titracing"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"github.com/tidbcloud/tiinf/tiinf"
	tigrpcc "github.com/tidbcloud/tiinf/transport/tigrpc/client"
	tigrpc "github.com/tidbcloud/tiinf/transport/tigrpc/server"
	"github.com/uptrace/opentelemetry-go-extra/otelgorm"
	"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"k8s.io/apimachinery/pkg/runtime"
	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
)

const (
	defaultConfigPath = "/etc/core-portal/config.yaml"
)

var (
	scheme    = runtime.NewScheme()
	inf       tiinf.TiInf
	tiinfConf *tiinf.Config
)

func init() {
	_ = clientgoscheme.AddToScheme(scheme)
	_ = configServiceV1.AddToScheme(scheme)
}

// server represents the central server instance
type server struct {
	centralAuth0 auth0.Conf
	portalAuth0  auth0.Conf
	stage        string
}

func main() {
	var configPath string
	flag.StringVar(&configPath, "config-path", defaultConfigPath, "dbaas configuration file path")
	flag.Parse()

	err := config2.InitConfig(configPath)
	if err != nil {
		// TODO: unexpected error like this, should we throw a alert/page, just log seems not enough ?
		log.Fatal("Initialize configuration failed", zap.Error(err))
	}

	inf = tiinf.NewInf()
	// initialize
	tiinfConf, err = tiinf.ParseConfigFromFile(configPath)
	if err != nil {
		panic(fmt.Sprintf("parse tiinf config file: %s", err))
	}
	if err := inf.Init(
		tiinf.WithConfigFile(configPath),
		tiinf.WithGrpcOptions(tigrpc.WithUnaryInterceptors(setupGrpcMiddlewares()...)),
		tiinf.WithGrpcOptions(tigrpc.WithRecovery(grpcRecovery.WithRecoveryHandlerContext(panicHandler))),
	); err != nil {
		panic(fmt.Sprintf("init TiInf: %s", err))
	}

	s := &server{
		centralAuth0: singleton.GetCentralAuth0(),
		portalAuth0:  singleton.GetManagementPortalAuth0(),
		stage:        config2.Global().ServerConfig.Stage,
	}
	err = s.serve()
	if err != nil {
		// TODO: unexpected error like this, should we throw a alert/page, just log seems not enough ?
		log.Fatal("failed to serve", zap.Error(err))
	}
}

func (s *server) serve() error {
	//if err := setupSentry(); err != nil {
	//	return errors.Annotate(err, "setupSentry failed")
	//}
	//var level zapcore.Level
	//if err := level.UnmarshalText([]byte(config2.Global().ServerConfig.SentryConf.LogLevel)); err != nil {
	//	return errors.Annotate(err, "invalid sentry.LogLevel")
	//}
	//sentryOpt := zap.WrapCore(func(core zapcore.Core) zapcore.Core {
	//	return zapcore.NewTee(core, zapsentry.NewCore(level, sentry.CurrentHub().Client()))
	//})

	//err := setupLogger(sentryOpt)
	//if err != nil {
	//	return errors.Annotate(err, "initialize logger failed")
	//}

	// flush all buffering log
	defer func() {
		if err := log.L().Sync(); err != nil {
			fmt.Println(err.Error())
		}
	}()

	return s.startup()
}

//func setupLogger(opts ...zap.Option) error {
//	cfg := &log.Config{
//		Level: config2.Global().ServerConfig.LogLevel,
//	}
//	gl, props, err := log.InitLogger(cfg, append(opts, zap.AddStacktrace(zapcore.ErrorLevel))...)
//	if err != nil {
//		return errors.Trace(err)
//	}
//	log.ReplaceGlobals(gl, props)
//
//	return nil
//}

//func setupSentry() error {
//	cfg := config2.Global().ServerConfig.SentryConf
//	opts := sentry.ClientOptions{
//		Dsn:         cfg.DSN,
//		Environment: cfg.Environment,
//	}
//	if err := sentry.Init(opts); err != nil {
//		log.Error("setupSentry failed", zap.String("dsn", cfg.DSN), zap.String("env", cfg.Environment))
//		return err
//	}
//	return nil
//}

func getGrpcGatewayConn() (*grpc.ClientConn, error) {
	grpcEndpoint := grpchttp.PortToAddress(int(tiinfConf.TiInfConfig.GrpcServerConfig.Port))
	//grpcEndpoint = fmt.Sprintf("passthrough://0.0.0.0/%s", grpcEndpoint)
	return tigrpcc.NewClientConn(grpcEndpoint, tigrpcc.WithDefault())
}

//func registerCentralHandler(ctx context.Context, mux *grpcruntime.ServeMux, conn *grpc.ClientConn) error {
//	if err := central.RegisterClusterServiceHandler(ctx, mux, conn); err != nil {
//		return err
//	}
//
//	if err := central.RegisterTenantServiceHandler(ctx, mux, conn); err != nil {
//		return err
//	}
//	return central.RegisterTestTokenServiceHandler(ctx, mux, conn)
//}

//func registerOpenapiHandler(ctx context.Context, mux *grpcruntime.ServeMux, conn *grpc.ClientConn) error {
//	return cm.RegisterClusterHandler(ctx, mux, conn)
//}

func registerCentralHandler(ctx context.Context, mux *grpcruntime.ServeMux) error {
	conn, err := getGrpcGatewayConn()
	if err != nil {
		return err
	}

	if err := central.RegisterClusterServiceHandler(ctx, mux, conn); err != nil {
		return err
	}

	if err := central.RegisterTenantServiceHandler(ctx, mux, conn); err != nil {
		return err
	}
	return central.RegisterTestTokenServiceHandler(ctx, mux, conn)
}

func registerOpenapiHandler(ctx context.Context, mux *grpcruntime.ServeMux) error {
	conn, err := getGrpcGatewayConn()
	if err != nil {
		return err
	}

	return cm.RegisterClusterHandler(ctx, mux, conn)
}

func (s *server) startup() error {
	log.Info("starting central service",
		zap.String("gitHash", version.GitHash))

	var err error

	// Initialize global database
	mysqlConfig := config2.Global().MysqlConfig
	log.Info("Initialize global database connection", zap.String("user", mysqlConfig.User))
	globalDB, err := db.InitializeWithoutMigrate(mysqlConfig)
	if err != nil {
		return errors.Annotate(err, "initialize global database connection failed")
	}
	if err := globalDB.Use(otelgorm.NewPlugin()); err != nil {
		return errors.Annotate(err, "gorm use otelgorm plugin")
	}

	// Initialize console audit log SDK
	if config2.Global().ServerConfig.MockAuditLogSDK {
		err = auditlog.InitMockAuditLogSDK(context.TODO(), time.Second)
	} else {
		err = auditlog.InitAuditLogSDK(context.TODO(), time.Second)
	}
	if err != nil {
		return errors.Annotate(err, "failed to init console audit log sdk")
	}

	// init tracing
	if _, err = titracing.InitJaeger("core-portal"); err != nil {
		log.Error("init tiinf jaeger provider failed", zap.Error(err))
	}

	// start metric and pprof
	registerMetricsPprofHandler()

	// warmup singletons
	singleton.WarmupSingleton()

	setupGrpcServer()
	setupGinServer()
	log.Info("setup gRPC server and gin Server done")
	//go func() {
	//	log.Info("serving gRPC", zap.String("address", grpcAddr))
	//	lis, err := net.Listen("tcp", grpcAddr)
	//	if err != nil {
	//		panic(err)
	//	}
	//	err = grpcServer.Serve(lis)
	//	if err != nil {
	//		panic(err)
	//	}
	//}()
	ginEng := inf.GinEngine()
	_, err = grpchttp.ServeHTTPGateway(ginEng, registerCentralHandler, registerOpenapiHandler)
	if err != nil {
		return errors.Trace(err)
	}
	ginEng.Any("/healthz", func(c *gin.Context) {
		c.Status(200)
	})
	ginEng.Any("/version", gin.WrapF(versionHandler))
	// Serve Proxy API
	if err := setupProxyAPI(ginEng); err != nil {
		return errors.Trace(err)
	}
	// Serve Admin API
	if err := setupAdminAPI(ginEng); err != nil {
		return errors.Trace(err)
	}

	// Run
	log.S().Infof("serving HTTP gRPC-Gateway on %d, serving gRPC-server on %d", tiinfConf.TiInfConfig.GinConfig.Port, tiinfConf.TiInfConfig.GrpcServerConfig.Port)
	//panic(fmt.Sprintf("grpc HTTP gateway died %+v", ginEng.Run(gatewayHttpAddr)))
	panic(fmt.Sprintf("grpc HTTP gateway died %+v", inf.Run()))
}

type middleware func(http.Handler) http.Handler

func Middlewares(h http.Handler, m ...middleware) http.Handler {
	if len(m) < 1 {
		return h
	}
	wrapped := h
	// loop in reverse to preserve middleware order
	for i := len(m) - 1; i >= 0; i-- {
		wrapped = m[i](wrapped)
	}
	return wrapped
}

// Auth middleware do not authenticate OPTIONS request, which violates the requirements of tenant proxy, so
// we handle CORS of options in prior
func mwCORS(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == http.MethodOptions {
			w.Header().Set("Access-Control-Allow-Headers", "accept, content-type, authorization")
			w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
			w.Header().Set("Access-Control-Allow-Origin", "*")
			w.WriteHeader(200)
			return
		}
		w.Header().Set("Access-Control-Allow-Origin", "*")
		handler.ServeHTTP(w, r)
	})
}

func mwSentry(next http.Handler) http.Handler {
	sentryHandler := sentryhttp.New(sentryhttp.Options{})
	return sentryHandler.HandleFunc(func(writer http.ResponseWriter, request *http.Request) {
		next.ServeHTTP(writer, request)
	})
}

type versionInfo struct {
	Hash             string `json:"hash"`
	CentralDBVersion int    `json:"central_db_version"`
	TenantDBVersion  int    `json:"tenant_db_version"`
}

// versionHandler outputs the current central server version information.
func versionHandler(w http.ResponseWriter, _ *http.Request) {
	data, _ := json.Marshal(versionInfo{
		Hash: version.GitHash,
	})
	_, _ = w.Write(data)
}

func newProxyMetricsHandler() func(handler http.Handler) http.Handler {
	return func(handler http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			tBegin := time.Now()
			rl := &grpchttp.ResponseLogger{W: w, Status: 200}
			handler.ServeHTTP(rl, r)
			statusCodeStr := fmt.Sprintf("%d", rl.Status)
			duration := float64(time.Since(tBegin)) / float64(time.Millisecond)
			orgID, _, err := auth.ParseOrgIDAndClusterID(r.URL.Path)
			if err == nil {
				metrics2.ProxyRequestDuration.WithLabelValues(fmt.Sprintf("%d", orgID.Uint64()), statusCodeStr).Observe(duration)
			}
			if rl.Status >= 500 {
				url := r.URL.String()
				//TODO(Sentry)
				log.Info("proxy API failed", zap.Int("status code", rl.Status), zap.String("url", url))
			}
		})
	}
}

func panicHandler(ctx context.Context, p interface{}) error {
	logger := pkglog.WithContextL(ctx)
	err, ok := p.(error)
	if ok && errors.HasStack(err) {
		logger.Sugar().Warnf("panic, err: %s, stack: %s", err, errors.GetStackTracer(err).StackTrace())
	} else {
		logger.Sugar().Warnf("panic, err: %s, stack: %s", p, debug.Stack())
	}

	// I don't know why the usual stack trace is not working
	// So putting debug.Stack() into the message for now
	if !ok {
		err = fmt.Errorf("%+v\n%s", p, debug.Stack())
		return errcode.NewInternalErr(err)
	}

	// TODO(chenfei): Should replaced by grpc_sentry.UnaryServerInterceptor
	// report panic to sentry
	sentry.CurrentHub().Recover(err)
	return errcode.NewInternalErr(err)
}

// configureServer is an opinionated common configuration for a grpc Server.
//func configureServer(logInterceptor grpc.UnaryServerInterceptor, ratelimitInterceptor grpc.UnaryServerInterceptor) *grpc.Server {
//	opts := []grpcRecovery.Option{
//		grpcRecovery.WithRecoveryHandlerContext(panicHandler),
//	}
//
//	tokenValidator := singleton.GetTokenValidator()
//	authFunc := auth.NewAuthFunc(tokenValidator)
//	return grpc.NewServer(
//		// NO TSL for gRPC methods now, as we don't want to use `github.com/gogo/grpc-example/insecure`
//		// grpc.Creds(credentials.NewServerTLSFromCert(&insecure.Cert)),
//		grpc.UnaryInterceptor(
//			grpcMiddleware.ChainUnaryServer(
//				// 3. set err to nil if err is BizError and response contains BaseResp, so the http status seen by the front end is HTTP 200
//				grpcbizerr.UnaryServerInterceptor(func(resp interface{}, err bizerr.BizError) (resp_ interface{}, err_ error) {
//					typed := getBaseRespFromResp(resp)
//					if typed == nil {
//						return resp, err
//					}
//					return resp, nil
//				}),
//				// 2. provides Prometheus monitoring for Unary RPCs
//				grpcprometheus.UnaryServerInterceptor,
//				// 1. set BaseResp.ErrCode and BaseResp.ErrMsg if err is BizError and response contains BaseResp
//				//    return err if err is internal or exception error, otherwise return nil, so that the grpcprometheus.UnaryServerInterceptor can record the correct status code.
//				grpcbizerr.UnaryServerInterceptor(func(resp interface{}, err bizerr.BizError) (resp_ interface{}, err_ error) {
//					typed := getBaseRespFromResp(resp)
//					if typed == nil {
//						return resp, err
//					}
//					typed.ErrCode = int64(err.ErrCode())
//					typed.ErrMsg = err.ErrMsg()
//
//					if bizerr.IsInternalErr(err.ErrCode()) || bizerr.IsException(err.ErrCode()) {
//						return resp, err
//					}
//
//					return resp, nil
//				}),
//				logid.UnaryServerInterceptor(),
//				otelgrpc.UnaryServerInterceptor(),
//				grpc_sentry.UnaryServerInterceptor(),
//				grpctrace.UnaryServerInterceptor(),
//				grpc_errcode.UnaryServerInterceptor(),
//				grpcValidator.UnaryServerInterceptor(),
//				grpcAuth.UnaryServerInterceptor(authFunc),
//				rbac.GrpcGatewayPermissionCheckUnaryServerInterceptor(),
//				logInterceptor,
//				ratelimitInterceptor,
//				grpcRecovery.UnaryServerInterceptor(opts...),
//			),
//		),
//		grpc.StreamInterceptor(
//			grpcMiddleware.ChainStreamServer(
//				logid.StreamServerInterceptor(),
//				grpc_sentry.StreamServerInterceptor(),
//				grpc_errcode.StreamServerInterceptor(),
//				grpcValidator.StreamServerInterceptor(),
//				grpcAuth.StreamServerInterceptor(authFunc),
//				grpcprometheus.StreamServerInterceptor,
//				// otgrpc.OpenTracingStreamServerInterceptor(tracer),
//				grpcRecovery.StreamServerInterceptor(opts...),
//			),
//		),
//	)
//}

func setupGrpcMiddlewares() []grpc.UnaryServerInterceptor {

	tokenValidator := singleton.GetTokenValidator()
	authFunc := auth.NewAuthFunc(tokenValidator)
	return []grpc.UnaryServerInterceptor{
		// 3. set err to nil if err is BizError and response contains BaseResp, so the http status seen by the front end is HTTP 200
		grpcbizerr.UnaryServerInterceptor(func(resp interface{}, err bizerr.BizError) (resp_ interface{}, err_ error) {
			typed := getBaseRespFromResp(resp)
			if typed == nil {
				return resp, err
			}
			return resp, nil
		}),
		// 2. provides Prometheus monitoring for Unary RPCs
		grpcprometheus.UnaryServerInterceptor,
		// 1. set BaseResp.ErrCode and BaseResp.ErrMsg if err is BizError and response contains BaseResp
		//    return err if err is internal or exception error, otherwise return nil, so that the grpcprometheus.UnaryServerInterceptor can record the correct status code.
		grpcbizerr.UnaryServerInterceptor(func(resp interface{}, err bizerr.BizError) (resp_ interface{}, err_ error) {
			typed := getBaseRespFromResp(resp)
			if typed == nil {
				return resp, err
			}
			typed.ErrCode = int64(err.ErrCode())
			typed.ErrMsg = err.ErrMsg()

			if bizerr.IsInternalErr(err.ErrCode()) || bizerr.IsException(err.ErrCode()) {
				return resp, err
			}

			return resp, nil
		}),
		//logid.UnaryServerInterceptor(),
		//otelgrpc.UnaryServerInterceptor(),
		grpc_sentry.UnaryServerInterceptor(),
		grpctrace.UnaryServerInterceptor(),
		grpc_errcode.UnaryServerInterceptor(),
		grpcValidator.UnaryServerInterceptor(),
		grpcAuth.UnaryServerInterceptor(authFunc),
		rbac.GrpcGatewayPermissionCheckUnaryServerInterceptor(),
		grpchttp.LogUnaryServerInterceptor(),
		ratelimit.UnaryServerInterceptor(ratelimit.GlobalLimiters),
		knife.DegradationInterceptor(func() knife.DegradationConfig {
			return config2.Global().Degradation
		}),
	}
}

func registerMetricsPprofHandler() {
	clustermetrics.RegisterMetrics()

	setupPprofHandler(http.NewServeMux())
	// Start metrics server.
	//if config2.Global().ServerConfig.MetricsPort > 0 {
	//	mux := http.NewServeMux()
	//	mux.Handle("/metrics", promhttp.Handler())
	//	setupPprofHandler(mux)
	//	go func() {
	//		err := http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", config2.Global().ServerConfig.MetricsPort), mux)
	//		if err != nil {
	//			panic(fmt.Sprintf("metrics server died %+v", err))
	//		}
	//	}()
	//}
}

// TODO(chenfei): merge with idl/pbgen/util/helper.go:GetBaseRespFromResp
func getBaseRespFromResp(resp interface{}) *central.BaseResp {
	if resp == nil {
		return nil
	}

	baseResp := reflect.ValueOf(resp).Elem().FieldByName("BaseResp")
	if !baseResp.IsValid() { // don't have BaseReq field
		return nil
	}
	if baseResp.IsNil() {
		baseResp.Set(reflect.New(baseResp.Type().Elem()))
	}

	typed, ok := baseResp.Interface().(*central.BaseResp)
	if !ok {
		return nil
	}
	return typed
}

//func setTraceIdMiddleware(c *gin.Context) {
//	spanctx := trace.SpanContextFromContext(c.Request.Context())
//	traceId := spanctx.TraceID().String()
//
//	c.Header("x-pingcap-traceid", traceId)
//
//	c.Next()
//}

func setupGinServer() {
	eng := inf.GinEngine()
	// NOTE(csuzhangxc):  if we don't add this cors here, "/api/v1/admin" can't get cors headers for "OPTIONS" method (and 404 returned). I don't know why.
	eng.Use(
		gincors.New(gincors.Config{
			AllowOrigins: []string{"*"},
			AllowMethods: []string{"HEAD", "PUT", "PATCH", "GET", "POST", "DELETE"},
			AllowHeaders: []string{"*"},
			// ExposeHeaders:    []string{"Content-Length"},
			AllowCredentials: true,
			AllowOriginFunc: func(origin string) bool {
				return true
			},
			MaxAge: 12 * time.Hour,
		}),
		//ctxinfo.ExtractCtxInfoMW,
		//logid.InjectLogid4GRPCGatewayMW,

		otelgin.Middleware("core-portal"),

		//setTraceIdMiddleware,
		httpmiddleware.TraceRequest,
	)
}

func setupGrpcServer() {
	infraAPI := api.InitInfraAPI()
	aesCrypt := singleton.GetAesCrypt()
	auth0Config := config2.Global().Auth0Config

	clusterModel := clustermodel.NewModel(log.L(), infraAPI)
	testTokenModel := testtoken.NewModel(log.L(), auth0Config.Auth0Domain, auth0Config.Auth0ManagementClientID, auth0Config.Auth0ManagementClientSecret, aesCrypt, infraAPI.GetK8sCli())

	//server := configureServer(grpchttp.LogUnaryServerInterceptor(), ratelimit.UnaryServerInterceptor(ratelimit.GlobalLimiters))
	server := inf.GrpcServer()
	centralService := service.New()
	central.RegisterTenantServiceServer(server, cluster.NewTenantService(centralService, clusterModel)) // still needed for `GetProjectTrafficFilter` and `UpdateProjectTrafficFilter`
	central.RegisterClusterServiceServer(server, cluster.NewService(centralService, clusterModel))
	central.RegisterTestTokenServiceServer(server, testokenservice.NewService(testTokenModel)) // put in core-portal temporarily
	// APIs for billing service, no need to register for HTTP API
	core.RegisterCoreServiceServer(server, coresvc.NewService(log.L(), infraAPI))
	// Register Open API Server
	openAPIService := openapiservice.New()
	cm.RegisterClusterServer(server, openapiCluster.NewService(openAPIService, infraAPI, clusterModel))

	//grpcprometheus.EnableHandlingTimeHistogram()
	//grpcprometheus.Register(server)
}

// copied from admin/cmd/server/main.go
func setupAdminAPI(ginEng *gin.Engine) error {
	portalAuth0 := auth0.Conf{
		Audience:                 config2.Global().Auth0Config.ManagementPortalAuth0Audience,
		Secret:                   []byte(config2.Global().Auth0Config.ManagementPortalAuth0Secret),
		Domain:                   config2.Global().Auth0Config.ManagementPortalAuth0Domain,
		ClientCredentialAudience: config2.Global().Auth0Config.ManagementPortalAuth0ClientAudience,
	}
	if err := api2.InitTokenValidator(portalAuth0); err != nil {
		return err
	}

	if err := models.InitAdminModel(); err != nil {
		return err
	}

	api2.InitAdminRouter(ginEng)
	return nil
}

func setupProxyAPI(ginEng *gin.Engine) error {
	getClaim := auth.NewGetClaim()
	// tenantProxy proxies service from the cluster of target tenant.
	// The proxy interface called from Central UI.
	mwb := auth0.NewHTTPMiddlewareBuilder(singleton.GetTokenValidator(), getClaim).
		AcceptToken(auth0.TokenRawFromHTTPHeader).
		AcceptToken(auth0.TokenFromParameter("jwt")).
		AcceptToken(auth0.TokenFromCookie).
		AcceptValidator(auth0.AuthValidatorIDCentral)
	proxyConfig := config2.Global().ProxyConfig
	proxyMetrics := newProxyMetricsHandler()
	proxyAuthenticator := proxy.NewAuth0M2MAuth(proxyConfig.Auth0Domain, proxyConfig.Auth0ClientID, proxyConfig.Auth0ClientSecret, proxyConfig.Auth0Audience)
	tenantProxy := proxy.NewReverseProxy(proxyAuthenticator, mesh.ProxyShootEndpoint())
	tenantProxy.SetTransport(&http.Transport{
		TLSClientConfig: singleton.GetTlsConfig(),
	})

	middlewares := []middleware{
		mwSentry,
		auth.NewTestTokenAuth().Auth,
		mwb.MWTryAuth,
		auth.MWRewriteOldTiDBDashboardPath,
		singleton.GetDebugSessionManager().MWTryAuth,
		util.MWRequireAuth,
		util.MWRemoveAuthHeader,
		proxyMetrics,
		mwCORS,
		auth.NWForbidMatchedURLIfNotDebugSession(),
		auth.MWForbidMatchedURL(),
	}

	mesh.HandleRouteStripGin(ginEng, "/proxy/", Middlewares(
		tenantProxy,
		middlewares...,
	))
	return nil
}

func setupPprofHandler(mux *http.ServeMux) {
	mux.HandleFunc("/debug/pprof/", pprof.Index)
	mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
	mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
	mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
	mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
}
