package main

import (
	"context"
	"flag"
	"net/http"
	"os/signal"
	"syscall"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/log"
	"github.com/tidbcloud/tiinf/sdk/rbac/helperimpl"
	"go.uber.org/zap"

	"github.com/pingcap-inc/clinic/pkg/db"
	"github.com/pingcap-inc/clinic/pkg/ngm"
	"github.com/pingcap-inc/clinic/pkg/ngm/config"
	"github.com/pingcap-inc/clinic/pkg/ngm/conprof"
	"github.com/pingcap-inc/clinic/pkg/ngm/file_download/oom_record"
	"github.com/pingcap-inc/clinic/pkg/ngm/file_download/plan_replayer"
	"github.com/pingcap-inc/clinic/pkg/ngm/keyviz"
	"github.com/pingcap-inc/clinic/pkg/ngm/slowquery"
	"github.com/pingcap-inc/clinic/pkg/ngm/statements"
	"github.com/pingcap-inc/clinic/pkg/ngm/storage"
	"github.com/pingcap-inc/clinic/pkg/ngm/topru"
	"github.com/pingcap-inc/clinic/pkg/ngm/topsql"
	"github.com/pingcap-inc/clinic/pkg/ngm/utils"
	"github.com/pingcap-inc/clinic/pkg/o11y"
)

const ServiceName = "ngm_api"

var cfgPath string

func init() {
	gin.SetMode(gin.ReleaseMode)
}

//	@title			Ngm API
//	@version		1.0
//	@description	This is a Ngm API

func main() {
	flag.StringVar(&cfgPath, "c", "./", "ngm_api -c=/path/to/config.yaml")
	flag.Parse()

	// Init and validate config.
	cfg, err := config.InitConfig(cfgPath)
	if err != nil {
		panic(err)
	}

	// Init O11y global client.
	if err := cfg.CentralO11yConfig.Validate(); err != nil {
		log.Fatal("central o11y config invalid", zap.Error(err))
	}
	o11ySharedOpts := []o11y.Option{o11y.TimeoutOption(120 * time.Second)}
	o11yCli, err := o11y.New(
		cfg.CentralO11yConfig.GSEndpoint,
		cfg.CentralO11yConfig.DSEndpointTPL,
		cfg.CentralO11yConfig.ClientId,
		cfg.CentralO11yConfig.ClientSecret,
		cfg.CentralO11yConfig.Env,
		append(o11ySharedOpts, o11y.ByocDSEndpointOption(cfg.CentralO11yConfig.ByocDSEndpoint))...,
	)
	if err != nil {
		log.Fatal("failed to init central o11y client", zap.Error(err))
	}

	var stores storage.StorageFactory
	if cfg.IsRegion() {
		stores, err = storage.NewStorageFactoryForCN(cfg.S3AssumeRoleArn)
		if err != nil {
			log.Fatal("failed to create storage factory", zap.Error(err))
		}
	} else {
		stores, err = storage.NewStorageFactory(cfg.S3AssumeRoleArn)
		if err != nil {
			log.Fatal("failed to create storage factory", zap.Error(err))
		}
	}

	var tsql *topsql.Service
	var topRu *topru.Service
	var cprof *conprof.Service
	var sq *slowquery.Service
	var oom *oom_record.Service
	var pr *plan_replayer.Service
	var stmt *statements.Service

	if cfg.IsServiceEnabled(config.TopSQL) {
		tsql, err = topsql.NewTopSQLService(http.DefaultClient, cfg.Env, o11yCli)
		if err != nil {
			log.Fatal("failed to create topsql", zap.Error(err))
		}
	}

	if cfg.IsServiceEnabled(config.TopRu) {
		topRu = topru.NewTopRuService(cfg.Env, o11yCli)
	}

	if cfg.IsServiceEnabled(config.ConProf) {
		cprof = conprof.NewConProfService(cfg.Env, stores, cfg.CacheDir, o11yCli)
	}

	if cfg.IsServiceEnabled(config.SlowQuery) {
		sq = slowquery.NewSlowQueryService(cfg.Env, cfg.CacheDir, cfg.TenantDBConfig, stores, o11yCli)
	}

	if cfg.IsServiceEnabled(config.OOMRecord) {
		oom = oom_record.NewOOMRecordService(cfg.Env, stores)
	}

	if cfg.IsServiceEnabled(config.PlanReplayer) {
		pr = plan_replayer.NewPlanReplayerService(cfg.Env, stores)
	}

	if cfg.IsServiceEnabled(config.Statements) {
		stmt = statements.NewStatementsService(cfg.Env, stores, o11yCli)
	}

	keyvizSvc := keyviz.NewService(cfg.Env, stores)

	// Init gin.
	engine := gin.New()

	// Init db
	dbClient, err := db.Connect(cfg.DBConfig)
	if err != nil {
		log.Fatal("failed to connect db", zap.Error(err))
	}

	clinicClient := utils.NewClinicClient(cfg.DBaasConfig.ClinicInternalServerAddr)
	// Init permHelper
	permHelper := helperimpl.NewDefaultPermissionHelper()
	permHelper.SetEndpoint(cfg.DBaasConfig.AccountSvcAddr)
	// Register api routes.
	ngm.InitNgmApi(engine, ServiceName, tsql, topRu, cprof, sq, oom, pr, stmt, keyvizSvc, permHelper, dbClient, clinicClient)

	// Create context that listens for the interrupt signal from the OS.
	ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
	defer stop()

	// Start the server.
	go func() {
		log.Info("ngm_api is serving", zap.String("addr", cfg.Address))
		if err := engine.Run(cfg.Address); err != nil && err != http.ErrServerClosed {
			log.Panic("start server failed", zap.Error(err))
		}
	}()

	// Listen for the interrupt signal.
	<-ctx.Done()

	// Restore default behavior on the interrupt signal and notify user of shutdown.
	stop()
	log.Info("shutting down gracefully, press Ctrl+C again to force")
}
