package main

import (
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"time"

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

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() {
	rand.Seed(time.Now().UnixMilli())

	startWebServer()

	factories, err := pipeline.Components()
	if err != nil {
		log.Fatalf("failed to build components: %v", err)
	}

	info := component.BuildInfo{
		Command:     "tidb-cloud-metrics-push-pipeline",
		Description: "TiDB Cloud Metrics Push Pipeline",
		Version:     version.Version,
	}

	params := service.CollectorSettings{
		Factories: factories,
		BuildInfo: info,
	}
	if err = runInteractive(params); err != nil {
		log.Fatal(err)
	}
}

func runInteractive(params service.CollectorSettings) error {
	cmd := newCommand(params)
	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 service.CollectorSettings) *cobra.Command {
	// Build the Command we will use that only has config/set flags.
	rootCmd := &cobra.Command{
		Use:          params.BuildInfo.Command,
		Version:      params.BuildInfo.Version,
		SilenceUsage: true,
		RunE: func(cmd *cobra.Command, args []string) error {
			if err := featuregate.GetRegistry().Apply(config.GatesList); err != nil {
				return err
			}
			// Initialize provider after flags have been set
			params.ConfigProvider = aocconfig.GetConfigProvider()
			// Initialize the global dbaas config
			if err := dbaasconfig.InitConfig(*dbaasConfigPath); err != nil {
				return errors.Annotate(err, "Initialize configuration failed")
			}
			//Initialize the global metadb driver
			cmdhelper.InitMetadbDriver()
			col, err := service.New(params)
			if err != nil {
				return fmt.Errorf("failed to construct the application: %w", err)
			}
			return col.Run(cmd.Context())
		},
	}

	rootCmd.Flags().AddGoFlagSet(aocconfig.Flags())
	defineTiDBCloudExtraFlags(rootCmd)

	return rootCmd
}

func defineTiDBCloudExtraFlags(cmd *cobra.Command) {
	dbaasConfigPath = cmd.Flags().String("metrics-config", defaultDbaasConfigPath, "Path to the metrics config file")
}

func startWebServer() {
	prometheus.MustRegister(metrics.O11yServerRequestErrCnt, metrics.O11yServerRequestDuration, metrics.O11yServerQueryRequest)

	mux := http.NewServeMux()
	mux.Handle("/metrics", promhttp.Handler())
	// workaround for gray migration, should be called after
	// modifying the configmap and checking the logs of metrics-pipeline container
	mux.Handle("/internal/reload", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		err := dbaasconfig.NormalizeConfig()
		if err != nil {
			log.Printf("failed to normalize config: %v", err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		log.Printf("reload config successfully with new o11y config: %+v", dbaasconfig.Global().O11yGlobalServerConfig)
		w.WriteHeader(http.StatusOK)
	}))

	go func() {
		err := http.ListenAndServe(fmt.Sprintf("0.0.0.0:8080"), mux)
		if err != nil {
			panic(fmt.Sprintf("metrics server died %+v", err))
		}
	}()
}
