package main

import (
	"flag"
	"fmt"
	"os"

	"github.com/getsentry/sentry-go"
	"github.com/gin-gonic/gin"
	"github.com/pingcap/log"
	"github.com/spf13/viper"
	"github.com/tidbcloud/cost-collect/api"
	"github.com/tidbcloud/cost-collect/pkg/cloud/gcp"
	"github.com/tidbcloud/cost-collect/pkg/config"
	"github.com/tidbcloud/cost-collect/pkg/cost"
	"github.com/tidbcloud/cost-collect/pkg/global"
	"github.com/tidbcloud/pkgv2/zapext/zapsentry"
	"github.com/tidbcloud/tiinf/monitor/titracing"
	"github.com/tidbcloud/tiinf/transport/http/tigin"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

const (
	serverName = "provider-cost"
)

var addr string
var configPath string
var logLevel string

func initParse() {
	flag.StringVar(&addr, "listen-address", ":8080", "The address to listen on for HTTP requests.")
	flag.StringVar(&configPath, "config-file", "/etc/cost-collector.conf", "The file for config.")
	flag.StringVar(&logLevel, "log-level", "", "The config level.")
	flag.Parse()
}

func initManager() (*cost.Manager, error) {
	// init GCP BigQuery client for cost usage report of cloud provider
	awsBillTemplate, err := cost.GetAWSSQLTemplate()
	if err != nil {
		return nil, err
	}
	gcpBillTemplate, err := cost.GetGCPSQLTemplate()
	if err != nil {
		return nil, err
	}
	gcpClient, err := gcp.NewBigQueryClient(viper.GetString("gcp.provider_project_id"),
		viper.GetString("gcp.location"),
		gcp.Config{
			AWSBillTemplate:       awsBillTemplate,
			GCPBillTemplate:       gcpBillTemplate,
			PreAggregatedAWSTable: viper.GetString("gcp.pre_aggregated_aws_table"),
			PreAggregatedGCPTable: viper.GetString("gcp.pre_aggregated_gcp_table"),
		},
	)
	if err != nil {
		return nil, err
	}

	manager := cost.NewManager(gcpClient)

	return manager, nil
}

func main() {
	initParse()

	// init config
	err := config.InitViper(configPath)
	if err != nil {
		panic(err)
	}

	// setup
	if err := setup(); err != nil {
		panic(fmt.Sprintf("setup: %s", err))
	}

	manger, err := initManager()
	if err != nil {
		panic(err)
	}

	engine := tigin.DefaultWithServerName(serverName)

	engine.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	engine.GET("/version", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"version": global.Version,
			"gitHash": global.GitHash,
		})
	})

	v1 := engine.Group("/api/v1")
	gin.DefaultWriter = os.Stdout

	costAPI := api.NewProviderCostAPI(manger)
	costAPI.Load(v1)

	panic(fmt.Sprintf("run: %s", engine.Run(addr)))
}

func setup() error {
	if err := setupSentry(); err != nil {
		return fmt.Errorf("setupSentry failed: %w", err)
	}
	var sentryLogLevel zapcore.Level
	sentryLogLevelStr := viper.GetString("server.sentry_log_level")
	if err := sentryLogLevel.UnmarshalText([]byte(sentryLogLevelStr)); err != nil {
		return fmt.Errorf("invalid sentry.LogLevel: %s", sentryLogLevelStr)
	}
	sentryOpt := zap.WrapCore(func(core zapcore.Core) zapcore.Core {
		return zapcore.NewTee(core, zapsentry.NewCore(sentryLogLevel, sentry.CurrentHub().Client()))
	})

	if err := setupLogger(sentryOpt); err != nil {
		return fmt.Errorf("initialize logger failed: %w", err)
	}

	// init tracing
	if _, err := titracing.InitJaeger("provider-cost"); err != nil {
		log.Error("init tiinf jaeger provider failed", zap.Error(err))
		return err
	}

	bus := cost.NewEventbus(config.GetConfig().Server.Eventbus)
	return bus.Run()
}

func setupLogger(opts ...zap.Option) error {
	if logLevel == "" {
		viper.SetDefault("global.log_level", "info")
		logLevel = viper.GetString("global.log_level")
	}

	cfg := &log.Config{
		Level: logLevel,
	}
	gl, props, err := log.InitLogger(cfg, append(opts, zap.AddStacktrace(zapcore.ErrorLevel))...)
	if err != nil {
		return fmt.Errorf("init logger failed: %w", err)
	}
	log.ReplaceGlobals(gl, props)

	return nil
}

func setupSentry() error {
	dsn := viper.GetString("server.sentry_dsn")
	env := viper.GetString("server.sentry_env")
	opts := sentry.ClientOptions{
		Dsn:         dsn,
		Environment: env,
	}
	if err := sentry.Init(opts); err != nil {
		log.Error("setupSentry failed", zap.String("dsn", dsn), zap.String("env", env))
		return err
	}
	return nil
}
