package main

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

	awsconfig "github.com/aws/aws-sdk-go-v2/config"
	"go.uber.org/zap/zapcore"
	"google.golang.org/api/compute/v1"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/log"
	"sigs.k8s.io/controller-runtime/pkg/log/zap"

	cloudaws "github.com/tidbcloud/infra-provider/pkg/cloud/aws"
	"github.com/tidbcloud/infra-provider/quota-exporter/config"
	"github.com/tidbcloud/infra-provider/quota-exporter/exporter"
	"github.com/tidbcloud/infra-provider/quota-exporter/exporter/awsquota"
	"github.com/tidbcloud/infra-provider/quota-exporter/exporter/gcpquota"
)

var (
	setupLog = log.Log.WithName("Setup")
)

func main() {
	var (
		rootConfigPatch string
	)
	flag.StringVar(&rootConfigPatch, "root-config", "", "The path to the root config.")

	opts := zap.Options{
		Development: true,
		TimeEncoder: zapcore.RFC3339TimeEncoder,
	}
	opts.BindFlags(flag.CommandLine)
	flag.Parse()
	ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))

	rootConfig, err := config.LoadRootConfig(rootConfigPatch)
	exitIfErr(err, "unable to load root config")

	setupLog.Info("building dependencies and exporters")

	ctx := ctrl.SetupSignalHandler()
	registry := exporter.NewRegistry()
	deps := &exporter.Dependency{
		Logger: log.Log,
	}

	if rootConfig.EnabledForAWS {
		err = setupForAWS(deps, registry)
		exitIfErr(err, "failed to setup for AWS")
	}
	if rootConfig.EnabledForGCP {
		err = setupForGCP(deps, registry)
		exitIfErr(err, "failed to setup for GCP")
	}

	mux := http.DefaultServeMux
	err = registry.Expose(mux, rootConfig.Exporters, deps)
	exitIfErr(err, "failed to handle metrics")

	startHTTPServer(ctx, &http.Server{Addr: rootConfig.ListenAddr, Handler: mux})
}

func exitIfErr(err error, msg string) {
	if err != nil {
		setupLog.Error(err, msg)
		os.Exit(1)
	}
}

func startHTTPServer(ctx context.Context, server *http.Server) {
	closed := make(chan struct{})
	go func() {
		<-ctx.Done()
		if err := server.Shutdown(context.Background()); err != nil {
			setupLog.Error(err, "failed to shutdown http server")
		}
		close(closed)
	}()

	setupLog.Info("starting http server", "addr", server.Addr)
	err := server.ListenAndServe()
	if err != http.ErrServerClosed {
		exitIfErr(err, "failed to start metrics server")
	}

	<-closed
	setupLog.Info("closed http server")
}

func setupForAWS(deps *exporter.Dependency, registry *exporter.Registry) error {
	var err error

	deps.Clientset, err = initAWSClientSet()
	if err != nil {
		return err
	}

	registry.Merge(defaultRegistryForAWS())

	return nil
}

func initAWSClientSet() (cloudaws.Client, error) {
	cfg, err := awsconfig.LoadDefaultConfig(context.TODO())
	if err != nil {
		return nil, err
	}
	return cloudaws.NewClientSet(cfg), nil
}

func defaultRegistryForAWS() *exporter.Registry {
	return &exporter.Registry{
		Exporters: map[string]exporter.Factory{
			awsquota.Name: awsquota.NewExporter,
		},
	}
}

func setupForGCP(deps *exporter.Dependency, registry *exporter.Registry) error {
	var err error

	deps.ComputeService, err = compute.NewService(context.Background())
	if err != nil {
		return err
	}

	registry.Merge(defaultRegistryForGCP())

	return nil
}

func defaultRegistryForGCP() *exporter.Registry {
	return &exporter.Registry{
		Exporters: map[string]exporter.Factory{
			gcpquota.Name: gcpquota.NewExporter,
		},
	}
}
