package main

import (
	"context"
	"fmt"
	"runtime/debug"

	"braces.dev/errtrace"
	"github.com/gin-contrib/pprof"
	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"
	"github.com/pingcap/errcode"
	"github.com/spf13/cobra"
	grpcsentry "github.com/tidbcloud/pkgv2/grpc/middleware/sentry"
	grpctrace "github.com/tidbcloud/pkgv2/grpc/middleware/trace"
	"github.com/tidbcloud/pkgv2/logid"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/tiinf"
	tigrpc "github.com/tidbcloud/tiinf/transport/tigrpc/server"
	_ "go.uber.org/automaxprocs"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"

	globalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/app/regional_proxy"
)

var (
	inf        tiinf.TiInf
	configPath string

	cmd      = &cobra.Command{}
	serveCmd = &cobra.Command{
		Use:   "serve",
		Short: "serve a regional proxy gRPC server",
		Run: func(cmd *cobra.Command, args []string) {
			pprof.Register(inf.GinEngine())
			cobra.CheckErr(inf.Run())
		},
	}
)

const (
	defaultConfigPath = "/etc/regional-proxy/config.yaml"
)

func init() {
	cobra.OnInitialize(func() {
		cobra.CheckErr(setupGrpcServer())
	})
	cmd.PersistentFlags().StringVar(&configPath, "config", defaultConfigPath, "config file")
	cmd.AddCommand(serveCmd)
}

func main() {
	if err := cmd.Execute(); err != nil {
		tilog.PlainFatal("serve return err", zap.Error(err))
	}
}

func panicHandler(ctx context.Context, p any) error {
	logger := tilog.WithContextS(ctx)
	err, ok := p.(error)
	if ok {
		logger.Warnf("panic, err: %+v", err)
	}

	// In case the usual stack trace is not working, put debug.Stack() into the message
	if !ok {
		err = fmt.Errorf("%+vn%s", p, debug.Stack())
		return errtrace.Wrap(errcode.NewInternalErr(err))
	}

	return errtrace.Wrap(errcode.NewInternalErr(err))
}

func setupGrpcServer() error {
	inf = tiinf.NewInf()
	if err := inf.Init(
		tiinf.WithConfigFile(configPath),
		tiinf.WithGrpcOptions(tigrpc.WithUnaryInterceptors(setupGrpcMiddlewares()...)),
		tiinf.WithGrpcOptions(tigrpc.WithRecovery(grpcrecovery.WithRecoveryHandlerContext(panicHandler))),
	); err != nil {
		return errtrace.Wrap(err)
	}
	reflection.Register(inf.GrpcServer())
	globalserverlessv1.RegisterRegionalServerProxyServiceServer(inf.GrpcServer(), regional_proxy.NewService())

	return nil
}

func setupGrpcMiddlewares() []grpc.UnaryServerInterceptor {
	return []grpc.UnaryServerInterceptor{
		// 2. provides Prometheus monitoring for Unary RPCs
		grpcprometheus.UnaryServerInterceptor,
		logid.UnaryServerInterceptor(),
		grpcsentry.UnaryServerInterceptor(),
		grpcvalidator.UnaryServerInterceptor(),
		grpctrace.UnaryServerInterceptor(),
	}
}
