package main

import (
	"context"
	"flag"
	"net/http"
	"time"

	"github.com/pingcap/log"
	"github.com/pingcap/tidb-operator/pkg/client/clientset/versioned"
	tidbclusterInformers "github.com/pingcap/tidb-operator/pkg/client/informers/externalversions"
	"go.uber.org/zap"
	"k8s.io/apimachinery/pkg/util/wait"
	kubeinformers "k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"

	"github.com/tidbcloud/billing-reporter/conf"
	"github.com/tidbcloud/billing-reporter/handler"
	"github.com/tidbcloud/billing-reporter/pkg/version"
	"github.com/tidbcloud/billing-reporter/report"
	"github.com/tidbcloud/billing-reporter/scrape"
	"github.com/tidbcloud/billing-reporter/slprober"
)

var (
	resyncDuration = 30 * time.Second
	addr           string
)

func init() {
	flag.DurationVar(&conf.ScrapeInterval, "scrape-interval", 1*time.Minute, "the interval duration between each 2 scraping")
	flag.DurationVar(&conf.ServiceLevelProbeInterval, "service-level-probe-duration", 10*time.Second, "the interval duration between each 2 service level probe")
	flag.StringVar(&conf.FailureMessageFile, "failure-message-file", "/message", "this describe the file save the failure messages")
	flag.StringVar(&conf.KubeConfig, "kubeconfig", "", "the kubeconfig file path")
	flag.StringVar(&addr, "listen-address", ":8080", "The address to listen on for HTTP requests.")
	flag.Parse()
}

func main() {
	err := conf.InitConf()
	if err != nil {
		log.Fatal("init conf", zap.Error(err))
	}
	log.Info("reporter", zap.String("gitHash", version.GitHash))

	var cfg *rest.Config
	if conf.KubeConfig == "" {
		cfg, err = rest.InClusterConfig()
	} else {
		cfg, err = clientcmd.BuildConfigFromFlags("", conf.KubeConfig)
	}

	if err != nil {
		panic(err)
	}

	cli, err := versioned.NewForConfig(cfg)
	if err != nil {
		log.Fatal("failed to create Clientset", zap.Error(err))
	}
	var kubeCli kubernetes.Interface
	kubeCli, err = kubernetes.NewForConfig(cfg)
	if err != nil {
		log.Fatal("failed to get kubernetes Clientset", zap.Error(err))
	}

	var tidbclusterInformerFactory tidbclusterInformers.SharedInformerFactory
	var kubeInformerFactory kubeinformers.SharedInformerFactory
	tidbclusterInformerFactory = tidbclusterInformers.NewSharedInformerFactory(cli, resyncDuration)
	kubeInformerFactory = kubeinformers.NewSharedInformerFactory(kubeCli, resyncDuration)
	sqsManager := report.NewSqsManager()
	// Currently, billing don't need a pod event
	// pw := watcher.NewPodWatcher(kubeInformerFactory, tidbclusterInformerFactory, sqsManager)
	sm := scrape.NewScrapManager(kubeCli, kubeInformerFactory, tidbclusterInformerFactory, sqsManager)
	slp := slprober.NewServiceLevelProber(kubeCli, kubeInformerFactory, tidbclusterInformerFactory)

	onStarted := func(ctx context.Context) {
		tidbclusterInformerFactory.Start(ctx.Done())
		kubeInformerFactory.Start(ctx.Done())

		// Wait for all started informers' cache were synced.
		for v, synced := range tidbclusterInformerFactory.WaitForCacheSync(wait.NeverStop) {
			if !synced {
				log.Fatal("error syncing informer", zap.Any("value", v))
			}
		}
		for v, synced := range kubeInformerFactory.WaitForCacheSync(wait.NeverStop) {
			if !synced {
				log.Fatal("error syncing informer", zap.Any("value", v))
			}
		}
		log.Info("cache of informer factories sync successfully")
		go wait.Forever(func() { scrapeTidbCluster(sm) }, conf.ScrapeInterval)
		go wait.Forever(func() { slp.ProbeStatus() }, conf.ServiceLevelProbeInterval)
	}
	controllerCtx, cancel := context.WithCancel(context.Background())
	defer cancel()
	onStarted(controllerCtx)

	slprober.RegisterMetrics()
	handler.RegisterHandlers()
	sm.RegisterStatusHandler()

	err = http.ListenAndServe(addr, nil)
	if err != nil {
		log.Error("failed to listen and serve", zap.String("address", addr), zap.Error(err))
	}
}

func scrapeTidbCluster(sm *scrape.ScrapeManager) {
	log.Info("starting scrape TiDB cluster status")
	err := sm.ScrapeTidbClusterListInfo()
	if err != nil {
		log.Error("failed to scrape TiDB cluster status", zap.Error(err))
		// TODO: record the scrap failed record
	}
}
