package main

import (
	"context"
	"flag"
	"fmt"
	"net"

	httpapi "github.com/tidbcloud/aipower/api"
	"github.com/tidbcloud/aipower/commons/config"
	"github.com/tidbcloud/aipower/commons/database"
	"github.com/tidbcloud/aipower/commons/measure_report"
	"github.com/tidbcloud/aipower/commons/metrics"
	"github.com/tidbcloud/aipower/commons/version"
	api_controller "github.com/tidbcloud/aipower/internal/controller/api"
	"github.com/tidbcloud/aipower/internal/service/cachesvc"
	"github.com/tidbcloud/aipower/internal/service/openaisvc"

	"google.golang.org/grpc/reflection"

	"github.com/gin-gonic/gin"
	"github.com/prometheus/client_golang/prometheus/promhttp"

	"github.com/tidbcloud/aipower/idl/pbgen/proto/api"

	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/monitor/tisentry"
	"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     = "aipower-api"
	defaultConfigPath = "/etc/dbaas/config.yaml"
)

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

	ctx := context.Background()

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

	if err := openaisvc.InitOpenAIClient(); err != nil {
		tilog.Panic(ctx, "failed to init openai", zap.Error(err))
	}
	if err := cachesvc.InitRedisClient(); err != nil {
		tilog.Panic(ctx, "failed to init redis", zap.Error(err))
	}

	tilog.Info(context.Background(), fmt.Sprintf("Starting %s service", componentName),
		zap.String("gitHash", version.GitHash),
		zap.String("logLevel", config.Global().ServerConf.LogLevel))

	tilog.Info(ctx, "Connect to database.")
	conf := config.Global()
	// Connect database
	database.Connect(ctx,
		conf.MysqlConf.User,
		conf.MysqlConf.Password,
		conf.MysqlConf.Host,
		conf.MysqlConf.Port,
		conf.MysqlConf.Database,
		conf.MysqlConf.CharSet,
		conf.MysqlConf.TimeZone,
	)

	if conf.ServerConf.RunMigrate {
		tilog.Info(ctx, "start to apply migrations...")
		if err := database.Migrate(); err != nil {
			tilog.Panic(ctx, "failed to migrate", zap.Error(err))
		}
	}

	err = measure_report.InitMeasure(config.Global().SentryConf.Environment, "")
	if err != nil {
		panic(fmt.Sprintf("Initialize measure_report  failed: %+v", err))
	}

	startup(ctx)
}

func startupHTTP() {
	eng := tigin.Default()

	metrics.RegisterCentralMetrics()

	httpapi.RouteCentralAPI(eng)

	eng.Run("0.0.0.0:" + config.Global().ServerConf.HttpPort) //nolint:errcheck
}

func startup(ctx context.Context) {
	setupSentry(ctx)

	go startupTracing()
	go startupMetrics()
	go startupHTTP()

	startupGRPC()
}

func setupSentry(ctx context.Context) {
	sentryConfig := config.Global().SentryConf
	if err := tisentry.SetupLoggerAndSentry(config.Global().ServerConf.LogLevel, sentryConfig.LogLevel, sentryConfig.DSN, sentryConfig.Environment); err != nil {
		tilog.Error(ctx, "failed to init sentry", zap.Error(err))
	}
}

func startupTracing() {
	// jaeger
	if _, err := titracing.InitJaeger(componentName); err != nil {
		panic(fmt.Sprintf("failed to init tracing, err=%v", err))
	}
}

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

func startupMetrics() {
	/* metrics */
	mEng := tigin.Default()
	mEng.Any("/metrics", prometheusHandler())
	mEng.Run("0.0.0.0:" + config.Global().ServerConf.MetricsPort) //nolint:errcheck
}

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

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

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

func registerGRPCServices(s *grpc.Server) {
	api.RegisterChatBotServiceServer(s, api_controller.NewChatBotService())
}
