package main

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

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/dynamic/dynamicinformer"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/clientcmd"

	"github.com/tidbcloud/eks-provider/infra-exporter/resources"
	"github.com/tidbcloud/eks-provider/infra-exporter/utils"
)

var (
	infraKubeconfig string
	resyncInterval  int
	provider        string
)

func init() {
	flag.StringVar(&infraKubeconfig, "infra-kubeconfig", "/kube/infra/config", "path to infra kubeconfig")
	flag.IntVar(&resyncInterval, "resync-interval", 4, "resync interval in hours")
	flag.StringVar(&provider, "provider", "", "provider name. empty means all providers, other values are 'eks', 'gke', 'aks'")
	flag.DurationVar(&resources.UpgradeOperationProgressTimeout, "upgrade-operation-progress-timeout", 20*time.Minute, "duration without phase progress before marking UpgradeOperation as timed out")
	flag.Parse()
}

func main() {
	if provider != "" && provider != "eks" && provider != "gke" && provider != "aks" {
		utils.Logger.Error(nil, "invalid provider", "provider", provider)
		os.Exit(1)
	}

	infraCfg, err := clientcmd.BuildConfigFromFlags("", infraKubeconfig)
	if err != nil {
		utils.Logger.Error(err, "failed to build config from flags infraKubeconfig")
		os.Exit(1)
	}

	infraCfg.UserAgent = "infra-exporter"

	dynamicCli, err := dynamic.NewForConfig(infraCfg)
	if err != nil {
		utils.Logger.Error(err, "failed to create dynamic client")
		os.Exit(1)
	}

	stopCh := make(chan struct{})

	// Split informer factory into two parts, one for dedicated resources, the other for crossplane resources.
	// Since there are many serverless cluster CRs, we need to use a label selector to filter.
	dedicatedFactory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(
		dynamicCli,
		time.Duration(resyncInterval)*time.Hour,
		metav1.NamespaceAll,
		func(opts *metav1.ListOptions) {
			opts.LabelSelector = "tags.tidbcloud.com/servicetype=dedicated"
		},
	)
	crossplaneFactory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(dynamicCli, time.Duration(resyncInterval)*time.Hour, metav1.NamespaceAll, nil)

	for _, collector := range resources.GetAllCollectors(provider) {
		utils.Logger.Info("start to watch resources", "resource", collector.Res.GVR())
		if collector.Res.HasDedicatedLabel() {
			newInformer(dedicatedFactory, collector)
		} else {
			newInformer(crossplaneFactory, collector)
		}
		_ = prometheus.Register(collector)
	}

	dedicatedFactory.Start(stopCh)
	crossplaneFactory.Start(stopCh)

	server := &http.Server{
		Addr: ":8090",
	}
	http.HandleFunc("/healthz", healthz)
	http.Handle("/metrics", promhttp.Handler())

	go func() {
		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			utils.Logger.Error(err, "Server closed unexpectly")
		}
	}()

	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
	sig := <-sigCh
	utils.Logger.Info("signal received", "signal", sig.String())
	close(stopCh)

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	if err := server.Shutdown(ctx); err != nil {
		utils.Logger.Error(err, "Server forced to shutdown")
	}
	utils.Logger.Info("Server exiting")
}

func healthz(w http.ResponseWriter, _ *http.Request) {
	w.WriteHeader(http.StatusOK)
}

func newInformer(fac dynamicinformer.DynamicSharedInformerFactory, c *resources.Collector) cache.SharedIndexInformer {
	informer := fac.ForResource(c.Res.GVR()).Informer()

	_, _ = informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			c.OnAdd(obj)
		},
		UpdateFunc: func(oldObj, newObj interface{}) {
			c.OnUpdate(oldObj, newObj)
		},
		DeleteFunc: func(obj interface{}) {
			c.OnDelete(obj)
		},
	})

	return informer
}
