package globalapi

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"time"

	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
	"github.com/pingcap/log"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/metrics"
	"github.com/tidbcloud/observability/pkg/common/structs"
	"github.com/tidbcloud/observability/pkg/globalapi/interceptor"
	"go.uber.org/zap"
)

func StartService(ctx context.Context, serviceInfo structs.ServiceInfo) {
	switch serviceInfo.Env {
	case constants.EnvDev:
		gin.SetMode(gin.DebugMode)
	case constants.EnvStaging:
		gin.SetMode(gin.TestMode)
	case constants.EnvProd:
		gin.SetMode(gin.ReleaseMode)
	}

	g := gin.New()
	g.Use(cors.New(corsConfig()))
	route(g)

	port := serviceInfo.Port
	addr := fmt.Sprintf(":%d", port)

	srv := &http.Server{
		Addr:              addr,
		Handler:           g,
		ReadHeaderTimeout: constants.GinReadHeaderTimeout,
	}

	// initial auth validator configuration
	if err := interceptor.InitAuthValidator(); err != nil {
		log.Fatal("initial auth validator failed", zap.Error(err))
	}

	// initial internal auth cache
	interceptor.InitInternalAuthCache()

	// allow skip prometheus handler, since we've served it in controller-runtime metrics
	if serviceInfo.MetricsPort != 0 {
		// initial boot prometheus client
		go prometheusBoot(serviceInfo.MetricsPort)
	}

	// initializing the server in a goroutine so that
	// it won't block the graceful shutdown handling below
	go func() {
		if serviceInfo.EnableHttps {
			if err := srv.ListenAndServeTLS(serviceInfo.Certificate.CrtFilePath, serviceInfo.Certificate.KeyFilePath); err != nil && errors.Is(err, http.ErrServerClosed) {
				log.Info("listen error", zap.Error(err))
			}
		} else {
			if err := srv.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) {
				log.Info("listen error", zap.Error(err))
			}
		}
	}()
	<-ctx.Done()
	timeoutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := srv.Shutdown(timeoutCtx); err != nil {
		log.Fatal("Server forced to shutdown", zap.Error(err))
	}
	log.Info("Wait for all graceful shutdown", zap.Duration("Shutdown_Wait", constants.GracefulShutDownWait))
	t := time.NewTimer(constants.GracefulShutDownWait)
	<-t.C
	log.Info("Server exiting")
}

// prometheusBoot
// @Description: boot prometheus
// @Parameter metricsPort
func prometheusBoot(metricsPort int) {
	// add boot_time metrics
	metrics.GetMetrics().BootTimeGaugeMetric.
		With(prometheus.Labels{metrics.ServiceLabel: constants.ObservabilityGlobalServer}).
		SetToCurrentTime()

	mux := http.NewServeMux()
	mux.Handle("/metrics", promhttp.Handler())

	if metricsPort <= 0 {
		metricsPort = constants.DefaultObservabilityGlobalServerMetricsPort
	}
	log.Info("prometheus listen address", zap.String("Address", "0.0.0.0:"+strconv.Itoa(metricsPort)))
	srv := &http.Server{
		Addr:              "0.0.0.0:" + strconv.Itoa(metricsPort),
		Handler:           mux,
		ReadHeaderTimeout: constants.GinReadHeaderTimeout,
	}
	err := srv.ListenAndServe()
	if err != nil {
		log.Fatal("prometheus listen and serve error", zap.Error(err))
	}
}

// corsConfig
// @Description: build cors config
// @return cors.Config
func corsConfig() cors.Config {
	config := cors.DefaultConfig()
	config.AllowHeaders = []string{"*"}
	config.AllowCredentials = true
	config.AllowOriginFunc = func(origin string) bool {
		return true
	}
	return config
}
