package app

import (
	"os"

	"github.com/pingcap/log"
	"github.com/spf13/cobra"
	"github.com/tidbcloud/serverless-metering/pkg/config"
	"github.com/tidbcloud/serverless-metering/pkg/rcu"
	"github.com/tidbcloud/serverless-metering/pkg/report"
	"github.com/tidbcloud/serverless-metering/pkg/repository"
	"github.com/tidbcloud/serverless-metering/pkg/server"
	"github.com/tidbcloud/serverless-metering/pkg/store"
	"github.com/tidbcloud/serverless-metering/pkg/util"
	"go.uber.org/zap"
)

const defaultConfigPath = "/etc/serverless-metering/config.yaml"

var (
	cfgFile string

	rootCmd = &cobra.Command{
		Use:               "serverless-metering",
		CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true},
	}

	metricServerCmd = &cobra.Command{
		Use:   "metric-server",
		Short: "serve as a metrics push server",
		Run: func(cmd *cobra.Command, args []string) {
			go server.RunMonitorServer()
			storage := initStorage(false)
			metricServer := server.NewMetricServer(storage, &config.Global().Server)
			err := metricServer.Run()
			if err != nil {
				log.Fatal("failed to run metric server", zap.Error(err))
			}
		},
	}

	metaServerCmd = &cobra.Command{
		Use:   "meta-server",
		Short: "server as a meta data server",
		Run: func(cmd *cobra.Command, args []string) {
			go server.RunMonitorServer()
			repo, err := repository.NewCacheClusterBranchRepo()
			if err != nil {
				log.Fatal("failed to create repo", zap.Error(err))
			}
			svr := server.NewMetaServer(repo)
			err = svr.Run()
			if err != nil {
				log.Fatal("failed to run meta server", zap.Error(err))
			}
		},
	}

	workerCmd = &cobra.Command{
		Use:   "worker",
		Short: "metering worker statistic metering from s3 and report to billing",
		Run: func(cmd *cobra.Command, args []string) {
			go server.RunMonitorServer()
			storage := initStorage(true)
			repo := repository.NewMetaClientClusterBranchRepo()
			manager, err := report.NewMeteringManager(storage, repo)
			if err != nil {
				log.Fatal("failed to init metering manager", zap.Error(err))
			}
			manager.Run()
		},
	}

	rcuControllerCmd = &cobra.Command{
		Use:   "rcu-controller",
		Short: "rcu controller is responsible for updating rcu",
		Run: func(cmd *cobra.Command, args []string) {
			cfg := config.Global()
			logger, props, err := log.InitLogger(&log.Config{Level: cfg.LogLevel})
			if err != nil {
				log.Fatal("failed to init background logger", zap.Error(err))
			}
			log.ReplaceGlobals(logger, props)

			go server.RunMonitorServer()
			rcuStore, err := store.NewRcuStore()
			if err != nil {
				log.Fatal("failed to create rcu store", zap.Error(err))
			}
			repo := repository.NewPDMetaStoreClient()
			controller := rcu.NewRcuController(rcuStore, repo, util.NewLocalTime())
			go controller.Run()
			svr := server.NewRcuServer(rcuStore)
			err = svr.Start()
			if err != nil {
				log.Fatal("failed to run rcu server", zap.Error(err))
			}
		},
	}
)

func init() {
	cobra.OnInitialize(initGlobalConfig)
	rootCmd.PersistentFlags().StringVar(&cfgFile, "config", defaultConfigPath, "config file")
	rootCmd.AddCommand(metricServerCmd)
	rootCmd.AddCommand(workerCmd)
	rootCmd.AddCommand(metaServerCmd)
	rootCmd.AddCommand(rcuControllerCmd)
}

func initGlobalConfig() {
	cobra.CheckErr(config.InitConfig(cfgFile))
}

func initStorage(disableStorageTicker bool) store.Storage {
	cfg := config.Global()
	if disableStorageTicker {
		cfg.Store.TickSeconds = 0
		cfg.Store.Local.MaxRotateSeconds = 0
	}

	logger, props, err := log.InitLogger(&log.Config{Level: cfg.LogLevel})
	if err != nil {
		log.Fatal("failed to init background logger", zap.Error(err))
	}
	log.ReplaceGlobals(logger, props)

	var ls store.LocalStorage
	ft := store.GetFileType()
	switch ft {
	case store.FileTypeJson:
		ls, err = store.NewLocalJsonLogger()
	case store.FileTypeParquet:
		ls, err = store.NewLocalParquetLogger()
	default:
		log.Fatal("unknown file type")
	}
	if err != nil {
		log.Fatal("failed to init local storage", zap.Error(err))
	}

	hostname := os.Getenv("HOSTNAME")
	if hostname == "" {
		hostname = "default"
	}
	var rs store.RemoteStorage
	if cfg.CloudProvider == "aws" {
		rs, err = store.NewS3RemoteStorage(hostname)
	} else if cfg.CloudProvider == "alicloud" {
		rs, err = store.NewOssRemoteStorage(hostname)
	} else {
		log.Fatal("unknown cloud provider")
	}
	if err != nil {
		log.Fatal("failed to init remote storage", zap.Error(err))
	}

	storage, err := store.NewStorage(hostname, ls, rs)
	if err != nil {
		log.Fatal("failed to init storage", zap.Error(err))
	}

	return storage
}

func Execute() {
	if err := rootCmd.Execute(); err != nil {
		log.Fatal("failed to execute", zap.Error(err))
	}
}
