package main

import (
	"context"
	"dataapps/api"
	"dataapps/commons/config"
	"dataapps/commons/database"
	"dataapps/commons/eventbus"
	"dataapps/commons/sentry"
	sqleditorCtrl "dataapps/internal/controller/sqleditor"
	"dataapps/internal/events"
	"flag"
	"fmt"
	"net"
	"time"

	"github.com/tidbcloud/pkgv2/version"

	auditlogsdk "github.com/tidbcloud/tiinf/sdk/auditlog"

	"github.com/gin-gonic/gin"
	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	"github.com/pingcap/log"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/tidbcloud/dataapps/idl/pbgen/proto/sqleditor"
	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/monitor/titracing"
	"github.com/tidbcloud/tiinf/transport/http/tigin"
	tigrpc "github.com/tidbcloud/tiinf/transport/tigrpc/server"
	"go.uber.org/zap"
	"google.golang.org/grpc"
)

const (
	componentName     = "sqleditor"
	defaultConfigPath = "/etc/dbaas/config.yaml"
)

// @title SQL Editor API
// @version 1.0
// @description This is sql editor server

// @host 127.0.0.1:8080
// @BasePath /api/v1/dataapps/sqleditor
func main() {
	var configPath string
	flag.StringVar(&configPath, "config-path", defaultConfigPath, "DBaaS configuration file path")
	flag.Parse()
	ctx := context.Background()

	// Load config
	err := config.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))
	}

	setup(ctx)

	// Connect database
	centralConfig := config.Global()
	database.Connect(ctx, centralConfig)

	startup(ctx, centralConfig)
}

func startup(ctx context.Context, centralConfig *config.CentralConfig) {
	serverConfig := centralConfig.ServerConfig

	//eventbus
	if centralConfig.GardenerConfig.Env != "local" {
		if err := events.RegisterUserSettingsEventHandler(ctx); err != nil {
			tilog.Fatal(ctx, "RegisterUserSettingsEventHandler failed", zap.Error(err))
		}
		err := eventbus.Get().Run()
		if err != nil {
			tilog.Fatal(ctx, "Initialize event bus handler failed", zap.Error(err))
		}
	}

	// audit log
	if !serverConfig.LocalMode {
		err := auditlogsdk.InitAuditLogSDK(ctx, time.Second)
		if err != nil {
			tilog.Panic(ctx, "failed to init auditlog sdk", zap.Error(err))
		}
	}

	/* metrics */
	mEng := gin.Default()
	mEng.Any("/metrics", prometheusHandler())
	go mEng.Run("0.0.0.0:9090") //nolint:errcheck // no need check error

	// GRPC Server
	go startupGRPC()

	ginEng := tigin.Default()
	api.Routers(ginEng)
	addr := serverConfig.HttpListenAddr
	tilog.Info(ctx, "serving HTTP Service", zap.String("address", addr))
	panic(fmt.Sprintf("HTTP Service died %+v", ginEng.Run(addr)))
}

func setup(ctx context.Context) {
	//sentry
	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))
	}
	tilog.Info(context.Background(), fmt.Sprintf("Starting %s service", componentName),
		zap.String("gitHash", version.GitHash),
		zap.String("logLevel", config.Global().ServerConfig.LogLevel))

	// jaeger
	if _, err := titracing.InitJaeger(componentName); err != nil {
		tilog.Error(ctx, "init tiinf jaeger provider failed", zap.Error(err))
		panic(fmt.Sprintf("failed to init tracing, err=%v", err))
	}
}

func prometheusHandler() gin.HandlerFunc {
	h := promhttp.Handler()

	return func(c *gin.Context) {
		h.ServeHTTP(c.Writer, c.Request)
	}
}

func startupGRPC() {
	grpcServer := newGRPCServer()
	registerGRPCServices(grpcServer)
	grpcPort := config.Global().ServerConfig.GrpcPort
	tilog.Info(context.Background(), "register grpc services, port is "+grpcPort)
	listen, err := net.Listen("tcp", ":"+grpcPort)
	if err != nil {
		panic(err)
	}
	err = grpcServer.Serve(listen)
	if err != nil {
		panic(err)
	}
}

func newGRPCServer() *grpc.Server {
	server := tigrpc.NewServer(
		tigrpc.WithDefault(),
		tigrpc.WithUnaryInterceptors(
			grpc_middleware.ChainUnaryServer(
				errs.UnaryServerInterceptor(),
			),
		),
	)
	return server
}

func registerGRPCServices(s *grpc.Server) {
	sqleditor.RegisterSqlEditorServiceServer(s, sqleditorCtrl.NewSqleditorGrpcService())
}
