package main

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

	aocconfig "github.com/aws-observability/aws-otel-collector/pkg/config"
	pingcaperrs "github.com/pingcap/errors"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/spf13/cobra"
	dbaasconfig "github.com/tidbcloud/metrics-integration/pipeline/central/config"
	pipeline "github.com/tidbcloud/metrics-integration/pipeline/internal/service/pipeline"
	"go.opentelemetry.io/collector/component"
	"go.opentelemetry.io/collector/featuregate"
	"go.opentelemetry.io/collector/otelcol"
)

const (
	defaultDbaasConfigPath = "/etc/metrics/config.yaml"
)

var (
	dbaasConfigPath string
)

// Metric push-pipeline is built upon aws-otel-collector and opentelemetry-collector.
// In main() function, logging and configuration handling keep the same as aws-otel-collector.
// It supports the same components as aws-otel-collector, plus private components for TiDB Cloud.
func main() {
	info := component.BuildInfo{
		Command:     "pipeline",
		Description: "TiDB Cloud Metrics Push Pipeline",
		Version:     "v0.0.1",
	}

	flagSet, err := buildAndParseFlagSet(featuregate.GlobalRegistry())
	if err != nil {
		log.Fatal(err)
	}

	params := otelcol.CollectorSettings{
		Factories: pipeline.Components,
		BuildInfo: info,
	}
	if err = runInteractive(params, flagSet); err != nil {
		log.Fatal(err)
	}
}

// We parse the flags manually here so that we can use feature gates when constructing
// our default component list. Flags also need to be parsed before creating the config provider.
func buildAndParseFlagSet(featgate *featuregate.Registry) (*flag.FlagSet, error) {
	flagSet := aocconfig.Flags(featgate)
	flagSet.StringVar(&dbaasConfigPath, "metrics-config", defaultDbaasConfigPath, "config file path")

	// Causing help info reappear, don't know how to deal with.
	if err := flagSet.Parse(os.Args[1:]); err != nil {
		if !errors.Is(err, flag.ErrHelp) {
			return nil, err
		}
	}
	return flagSet, nil
}

func runInteractive(params otelcol.CollectorSettings, flagSet *flag.FlagSet) error {
	cmd := newCommand(params, flagSet)
	err := cmd.Execute()
	if err != nil {
		return fmt.Errorf("application run finished with error: %w", err)
	}
	return nil
}

// newCommand constructs a new cobra.Command using the given settings.
func newCommand(params otelcol.CollectorSettings, flagSet *flag.FlagSet) *cobra.Command {
	rootCmd := &cobra.Command{
		// otel
		Use:          params.BuildInfo.Command,
		Version:      params.BuildInfo.Version,
		SilenceUsage: true,
		RunE: func(cmd *cobra.Command, args []string) error {
			// Initialize provider after flags have been set
			params.ConfigProviderSettings = aocconfig.GetConfigProviderSettings(flagSet)
			// Initialize the global dbaas config
			if err := dbaasconfig.InitConfig(dbaasConfigPath); err != nil {
				return pingcaperrs.Annotate(err, "Initialize configuration failed")
			}
			mux := http.NewServeMux()
			mux.Handle("/metrics", promhttp.Handler())
			metricsServer, err := startMetricsServer(dbaasconfig.Global().ServerConfig.MetricsListenAddr, mux)
			if err != nil {
				return fmt.Errorf("failed to start metrics server: %w", err)
			}
			defer func() {
				shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
				defer cancel()
				if err := metricsServer.Shutdown(shutdownCtx); err != nil && !errors.Is(err, http.ErrServerClosed) {
					log.Printf("metrics server shutdown failed: %v", err)
				}
			}()
			col, err := otelcol.NewCollector(params)
			if err != nil {
				return fmt.Errorf("failed to construct the application: %w", err)
			}
			return col.Run(cmd.Context())
		},
	}

	rootCmd.Flags().AddGoFlagSet(flagSet)
	return rootCmd
}

func startMetricsServer(addr string, handler http.Handler) (*http.Server, error) {
	listener, err := net.Listen("tcp", addr)
	if err != nil {
		return nil, err
	}

	server := &http.Server{Handler: handler}
	go func() {
		if err := server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
			log.Printf("metrics server exited with error: %v", err)
		}
	}()

	return server, nil
}
