package main

import (
	"context"
	"flag"
	"fmt"
	"time"

	"github.com/getsentry/sentry-go"
	"github.com/pingcap/errors"
	"github.com/pingcap/log"
	"github.com/tidbcloud/internal-portal/api"
	"github.com/tidbcloud/internal-portal/apiserver/auth"
	"github.com/tidbcloud/internal-portal/core-portal/central/config"
	"github.com/tidbcloud/internal-portal/core-portal/central/db"
	infraapi "github.com/tidbcloud/internal-portal/core-portal/central/infra-api"
	"github.com/tidbcloud/internal-portal/version"
	"github.com/tidbcloud/pkgv2/infra/aws"
	"github.com/tidbcloud/pkgv2/secret/encrypt"
	secretprovider "github.com/tidbcloud/pkgv2/secret/provider"
	"github.com/tidbcloud/pkgv2/zapext/zapsentry"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

const (
	defaultConfigPath = "/etc/dbaas/config.yaml"

	defaultQPS        = 500
	defaultBurst      = 1000
	FailedStatus      = "failed"
	TokenExpiredError = "expired"
	SystemError       = "system"
	ErrorType         = "err_type"
	defaultTimeout    = 5 * time.Second

	allRole     = "all"
	centralRole = "CENTRAL_ROLE"
)

var (
	configPath = flag.String("config-path", defaultConfigPath, "dbaas configuration file path")
	encryptor  encrypt.Encryptor
	infraAPI   *infraapi.API
)

func setupLogger(opts ...zap.Option) error {
	cfg := &log.Config{
		Level: config.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 := config.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 main() {
	log.Info("starting server", zap.String("version", version.GetVersion()))

	flag.Parse()

	// init config
	if err := config.InitConfig(*configPath); err != nil {
		log.Fatal("Initialize configuration failed", zap.Error(err))
	}

	// Initialize global database.
	mysqlConfig := config.Global().MysqlConfig
	log.Info("Initialize global database connection", zap.String("user", mysqlConfig.User))
	_, err := db.InitializeWithoutMigrate(mysqlConfig)
	if err != nil {
		panic(errors.Annotate(err, "initialize global database connection failed"))
	}

	infraAPI = infraapi.InitInfraAPI()
	log.Info("initialize infra API successful")

	CMKArn := config.Global().InfraConfig.CMKArn
	encryptor, err = secretprovider.NewSecretEncryptor(CMKArn)
	if err != nil {
		panic(errors.Trace(err))
	}
	log.Info("get secret encryptor successful")

	// auth
	aesCrypt, err := encrypt.NewAESCrypt([]byte(config.Global().ServerConfig.SaltSecret))
	if err != nil {
		panic(err)
	}

	auth0Config := config.Global().Auth0Config
	auth, err := auth.InitAuth(auth0Config, aesCrypt, infraAPI.GetK8sCli())
	if err != nil {
		panic(err)
	}

	tlsConfig, err := aws.ProxyTlsConfig(config.Global().AwsConfig.PCAArn)
	if err != nil {
		panic(errors.Annotate(err, "failed to get proxy TLS config"))
	}

	if err := setupSentry(); err != nil {
		panic("setupSentry failed")
	}
	var level zapcore.Level
	if err := level.UnmarshalText([]byte(config.Global().ServerConfig.SentryConf.LogLevel)); err != nil {
		panic("invalid sentry.LogLevel")
	}
	sentryOpt := zap.WrapCore(func(core zapcore.Core) zapcore.Core {
		return zapcore.NewTee(core, zapsentry.NewCore(level, sentry.CurrentHub().Client()))
	})
	if err := setupLogger(sentryOpt); err != nil {
		panic("initialize logger failed")
	}
	// flush all buffering log
	defer func() {
		if err := log.L().Sync(); err != nil {
			fmt.Println(err.Error())
		}
	}()

	err = auditlog.InitAuditLogSDK(context.TODO(), time.Second)
	if err != nil {
		log.Fatal("failed to init auditlog sdk", zap.Error(err))
	}

	centralChan := make(chan error, 1)
	go func() {
		centralChan <- initCentralServer()
	}()

	// listen and serve on 0.0.0.0:8081 by default
	r := api.InitRouter(auth, tlsConfig, infraAPI, encryptor)
	portalChan := make(chan error, 1)
	go func() {
		portalChan <- r.Run(config.Global().ServerConfig.InternalPortalListenAddr)
	}()

	select {
	case err := <-centralChan:
		if err != nil {
			// TODO: unexpected error like this, should we throw a alert/page, just log seems not enough ?
			log.Fatal("failed to start central API server", zap.Error(err))
		}
		return
	case err := <-portalChan:
		if err != nil {
			log.Fatal("failed to start portal server", zap.Error(err))
		}
		return
	}
}
