package main

import (
	"context"
	"flag"
	"fmt"
	"os"
	"path/filepath"

	"dataservices/api"
	"dataservices/commons/config"
	"dataservices/commons/logger"
	"dataservices/commons/metrics"
	"dataservices/commons/redisutil"
	"dataservices/commons/sentry"
	"dataservices/ddb"
	"dataservices/worker"
	ddbcacheworker "dataservices/worker/ddbcache"
	_ "dataservices/worker/ddbcache/handlers"

	controller "dataservices/internal/controller/dataapi_control"
	service "dataservices/internal/service/dataapi_control"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/log"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"

	"github.com/tidbcloud/pkgv2/version"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/monitor/titracing"
	"github.com/tidbcloud/tiinf/transport/http/tigin"
	"go.uber.org/zap"
)

const (
	componentName     = "dataservices-dataapi-control"
	defaultConfigPath = "/etc/dbaas/config.yaml"
)

func main() {
	var configPath string
	var kubeconfig string
	var masterURL string
	flag.StringVar(&configPath, "config-path", defaultConfigPath, "DBaaS configuration file path")
	flag.StringVar(&kubeconfig, "kube-config", defaultKubeconfig(), "Path to a kubeconfig. Only required if out-of-cluster.")
	flag.StringVar(&masterURL, "master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.")
	flag.Parse()
	cfg, err := rest.InClusterConfig()
	if err != nil {
		cfg, err = clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)
		if err != nil {
			tilog.PlainFatal("Error building kubeconfig", zap.Error(err))
		}
	}
	err = service.InitClient(cfg)
	if err != nil {
		tilog.Fatal(context.TODO(), "failed to init kube client", zap.Error(err))
	}

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

	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().ServerConf.LogLevel))

	config := config.Global()
	tilog.Info(context.Background(), "service config", logger.Redact("config", config), zap.String("env", config.CurrentEnv()))

	ctx := context.Background()
	err = redisutil.NewRedisClient(ctx, config)
	if err != nil {
		tilog.Fatal(ctx, "Initialize redis client failed.", zap.Error(err))
	}
	err = service.InitDataAppLogService(&config.CustomLogQueryConf)
	if err != nil {
		tilog.Fatal(ctx, "InitDataAppLogService failed.", zap.Error(err))
	}
	service.InitDataAppMetricsService(&config.DataAppMetricsQueryConf)
	metrics.RegisterDataApiControlMetrics()

	ddb.Init(ctx, config)
	workerManager := worker.NewWorkerManager(
		ddbcacheworker.NewSyncer(ctx),
	)
	workerManager.Process()

	startup()
}

func startup() {
	go startupTracing()
	go startupMetrics()
	startupHttp()
}

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

func startupHttp() {
	cfg := config.Global()
	err := controller.InitControlServer(context.Background(), cfg.AwsConf, cfg.VpcConf)
	if err != nil {
		panic(fmt.Sprintf("init control server failed, err=%v", err))
	}
	ginEng := tigin.Default()
	api.RouteDataApiControlAPI(ginEng)
	addr := ":" + config.Global().ServerConf.HttpPort
	log.Info("serving HTTP Service", zap.String("address", addr))
	panic(fmt.Sprintf("HTTP Service died %+v", ginEng.Run(addr)))
}

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

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

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

func defaultKubeconfig() string {
	fname := os.Getenv("KUBECONFIG")
	if fname != "" {
		return fname
	}
	home, err := os.UserHomeDir()
	if err != nil {
		tilog.PlainWarn("failed to get home directory", zap.Error(err))
		return ""
	}
	return filepath.Join(home, ".kube", "config")
}
