package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	ctrl "sigs.k8s.io/controller-runtime"
	ctrlzap "sigs.k8s.io/controller-runtime/pkg/log/zap"

	"github.com/tidbcloud/cluster-service-ng/internal/dataplane-exporter/pkg/exporter/componentnodevcpu"
	"github.com/tidbcloud/cluster-service-ng/internal/dataplane-exporter/pkg/exporter/resourcepoolcpulimit"
	"github.com/tidbcloud/cluster-service-ng/internal/dataplane-exporter/pkg/runtime"
	"github.com/tidbcloud/cluster-service-ng/internal/dataplane-exporter/pkg/source/kubernetes"
)

const shutdownTimeout = 5 * time.Second

var buildSource = func(ctx context.Context) (kubernetes.NodePodSource, error) {
	return kubernetes.NewInClusterSource(ctx)
}

var setControllerRuntimeLogger = func() {
	ctrl.SetLogger(ctrlzap.New())
}

type httpServer interface {
	ListenAndServe() error
	Shutdown(context.Context) error
}

func main() {
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)

	cfg, err := LoadConfig(os.Args[1:])
	if err != nil {
		stop()
		log.Fatalf("load config: %v", err)
	}

	if err := run(ctx, cfg); err != nil {
		stop()
		log.Fatalf("%v", err)
	}

	stop()
}

func run(ctx context.Context, cfg Config) error {
	exporters, err := buildExporters(ctx)
	if err != nil {
		return err
	}

	server := runtime.NewServer(runtime.Config{
		MetricsPath: cfg.MetricsPath,
		HealthPath:  cfg.HealthPath,
	}, exporters)

	httpServer := &http.Server{
		Addr:              cfg.ListenAddr,
		Handler:           server.Handler(),
		ReadHeaderTimeout: 5 * time.Second,
	}

	return serveHTTP(ctx, httpServer)
}

func buildExporters(ctx context.Context) ([]runtime.Exporter, error) {
	setControllerRuntimeLogger()
	return mustBuildExporters(ctx)
}

func serveHTTP(ctx context.Context, srv httpServer) error {
	shutdownErrCh := make(chan error, 1)
	go func() {
		<-ctx.Done()
		shutdownCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), shutdownTimeout)
		defer cancel()
		shutdownErrCh <- srv.Shutdown(shutdownCtx)
	}()

	err := srv.ListenAndServe()
	if err == nil {
		return nil
	}
	if !errors.Is(err, http.ErrServerClosed) {
		return fmt.Errorf("serve HTTP: %w", err)
	}
	if ctx.Err() == nil {
		return nil
	}

	shutdownErr := <-shutdownErrCh
	if shutdownErr != nil && !errors.Is(shutdownErr, http.ErrServerClosed) {
		return fmt.Errorf("shutdown HTTP: %w", shutdownErr)
	}

	return nil
}

func mustBuildExporters(ctx context.Context) ([]runtime.Exporter, error) {
	source, err := buildSource(ctx)
	if err != nil {
		return nil, fmt.Errorf("build Kubernetes source: %w", err)
	}
	reader := componentnodevcpu.NewLiveReader(source)
	resourcePoolCPULimitReader := resourcepoolcpulimit.NewLiveReader(source)

	return []runtime.Exporter{
		componentnodevcpu.NewExporter(reader),
		resourcepoolcpulimit.NewExporter(resourcePoolCPULimitReader),
	}, nil
}
